maestrostack
v0.2.0
Published
Config-driven CLI for running Maestro mobile tests on BrowserStack App Automate.
Maintainers
Readme
MaestroStack
MaestroStack is a config-driven CLI for running Maestro mobile tests on BrowserStack App Automate.
It packages Maestro flow folders, uploads app and test artifacts, and starts BrowserStack Maestro builds across one or more real devices from a single command:
maestrostack runUnder the hood BrowserStack's Maestro workflow is several manual REST calls: upload
the app, upload a zipped suite, copy the returned URLs, hand-build a JSON payload
with the right devices and execute paths, and POST to the correct Android or iOS
build endpoint. MaestroStack turns that into one repeatable command you can run
locally or in CI.
Install
npm install -g maestrostackRequires Node.js 18+. Works on macOS, Linux and Windows.
Quick start
The fastest way to see it work is the clone-and-run example. The maestrostack-demo repo ships a config, flows and a sample app, so a dry run needs no credentials:
git clone https://github.com/RohanImmanuel/maestrostack-demo
cd maestrostack-demo
npm install -g maestrostack
maestrostack run --dry-run # preview the payload, no credentials needed
maestrostack run # the real build (needs BrowserStack credentials)Or start from scratch in your own project:
# 1. Scaffold a config
maestrostack init --android
# 2. Provide BrowserStack credentials (see Authentication below)
export BROWSERSTACK_USERNAME=...
export BROWSERSTACK_ACCESS_KEY=...
# 3. Check everything before touching the network
maestrostack validate
maestrostack run --dry-run
# 4. Run on BrowserStack
maestrostack runProject layout
MaestroStack encourages this structure:
mobile-tests/
├── maestrostack.config.yml # tool config (NOT a Maestro flow)
├── smoke/
│ ├── login.yml # Maestro flow
│ └── signup.yml
├── regression/
│ └── checkout.yml
└── apps/
└── app-release.apkKeep your config out of the suite, and your flows in subfolders. BrowserStack runs everything in the uploaded zip as your Maestro suite, so the tool config must not be packaged with it. MaestroStack keeps config files out of the package automatically: name yours
*.config.yml(for examplemaestrostack.config.yml) to make its role explicit. Any*.config.ymlfile and any root-level.yml/.yamlis excluded from the suite, so a plainmaestrostack.ymlworks too.
Configuration
maestrostack init writes a starter maestrostack.config.yml (a plain
maestrostack.yml is also recognized). A full example:
version: 1
auth:
username: ${BROWSERSTACK_USERNAME}
accessKey: ${BROWSERSTACK_ACCESS_KEY}
platform: android # android | ios
app:
source: upload # upload | app_url
path: ./apps/app-release.apk
customId: SampleApp
suite:
root: .
packageName: Flows.zip
customId: SampleTest
include:
- smoke/**/*.yml
- regression/**/*.yml
exclude:
- apps/**
run:
project: Maestro_Test
devices:
- Samsung Galaxy S20-10.0
- Google Pixel 7-13.0
executeMode: explicit # explicit | main
execute:
- smoke/login.yml
- smoke/signup.yml
- regression/checkout.yml
options:
networkLogs: true
deviceLogs: trueApp source
| app.source | Required fields | Notes |
|---|---|---|
| upload | path | Uploads a local .apk (Android) / .ipa (iOS). |
| app_url | appUrl | Reuses an existing bs:// app reference. |
| custom_id | customId | References the latest app uploaded with that custom id; no upload. |
Execute modes
explicit(recommended): list flow files inrun.execute. MaestroStack sends them as the BrowserStackexecutearray - no rootmain.yamlneeded.main: omitrun.executeand provide amain.yamlat the suite root. BrowserStack runs it as the entrypoint.
Parallel execution
Set run.maxParallel to cap how many of your listed devices run at once.
MaestroStack splits run.devices into sequential batches of at most maxParallel
devices, submits one build per batch, and waits for each build to finish before
starting the next:
run:
maxParallel: 2
devices:
- Samsung Galaxy S20-10.0
- Google Pixel 7-13.0
- iPhone 15-17.0
- OnePlus 9-11.0With four devices and maxParallel: 2, that runs two builds of two devices each,
one after the other.
- Every device runs the full suite; nothing is assigned randomly.
- BrowserStack has no native per-build concurrency cap, so MaestroStack enforces it by submitting batched builds sequentially (polling each build's status until it completes).
- How many sessions can actually run at once still depends on your BrowserStack
plan's parallel limit.
maxParallellets you cap concurrency at or below that; if a batch is bigger than your available parallels, BrowserStack queues the extra sessions until a slot frees up. - Because it waits for completion, a batched
runis long-running (it blocks until every batch finishes). WithoutmaxParallel,runsubmits a single build and returns immediately as before. - Override per run with
--max-parallel <n>.
Authentication
Credentials are read from environment variables referenced as ${VAR} in the
config. MaestroStack loads a .env file from the working directory if present:
BROWSERSTACK_USERNAME=my_username
BROWSERSTACK_ACCESS_KEY=my_access_keyIf a referenced variable is missing, MaestroStack fails before any upload. Secrets are never printed.
Commands
| Command | Description |
|---|---|
| maestrostack init | Create a starter config (--android, --ios, --force). |
| maestrostack validate | Validate config, suite structure, app and devices. |
| maestrostack package | Discover flows and build the suite zip (no upload). |
| maestrostack upload-app | Upload only the app; print its app_url. |
| maestrostack upload-suite | Package and upload only the suite; print its test_suite_url. |
| maestrostack run | Package, upload and trigger a build. |
Global options
-c, --config <path>- use a specific config file (e.g.maestrostack.staging.config.yml).--debug- verbose logging.
run options
--dry-run- validate, show the files that would be zipped and the exact BrowserStack payload + endpoint, without making any API calls.--device <name>- overriderun.devices(repeatable).--execute <path>- overriderun.execute(repeatable; forces explicit mode).--max-parallel <n>- overriderun.maxParallel(run at most N listed devices at once).
maestrostack run -c maestrostack.android.smoke.yml
maestrostack run --device "Google Pixel 7-13.0" --device "Samsung Galaxy S20-10.0"
maestrostack run --execute smoke/login.yml --dry-runHow it works
maestrostack run:
- Loads
.envand the config, substitutes${VAR}tokens, validates everything. - Discovers flow files (ignoring
*.config.yml, root-level YAML,node_modules,.git). - Zips the suite under a
Flows/prefix preserving folder structure. - Resolves the app (uploads the local binary or uses an existing
bs://URL). - Uploads the suite zip.
- POSTs the build to
.../maestro/v2/{android|ios}/buildand prints the build id plus its BrowserStack dashboard URL.
Example output:
MaestroStack run started
Project: Maestro_Test
Platform: android
Devices:
- Samsung Galaxy S20-10.0
- Google Pixel 7-13.0
App: bs://...
Test suite: bs://...
Build ID: 5c5ab4338cec13aeb78f7a6977344556ac00bccd6
Build URL: https://app-automate.browserstack.com/dashboard/v2/builds/5c5ab4338cec13aeb78f7a6977344556ac00bccd6Development
npm install
npm run build # bundle with tsup -> dist/cli.js
npm run typecheck # tsc --noEmit
npm test # vitestThe BrowserStack integration is covered by tests with mocked HTTP, so the full package to payload path runs without a live account.
Acknowledgements
This project is tested with BrowserStack.
License
MIT
