Skip to content

TomKalina/maestro-parallel

Repository files navigation

maestro-parallel

JSR npm Docs License: Apache 2.0

One command. Every device. Real release builds. Run Maestro E2E flows on every connected Android + iOS device in parallel — zero config in Expo / React Native projects.

📚 Full docs: maestro-parallel.pages.dev

Quickstart

# JSR (Deno >=2.0)
deno install -g -A -n maestro-parallel jsr:@kaln/maestro-parallel/cli

# or npm (Node >=18.17)
npm i -g maestro-parallel

# In your app folder (needs .maestro/ flows + a connected device or booted sim)
maestro-parallel

The CLI discovers devices, asks which to use, auto-detects the right build strategy, builds a release artifact (or hits the build cache), runs flows in parallel, prints pass / fail. Picker is auto-skipped when only one device is present.

After the picker, mp renders a device-centric live checklist — one row per selected device, status flips on the same row through pending → building / waiting → installing → preparing → running maestro · <flow> · N ✓ M ✗ → done. Raw build / Maestro output is captured into per-run log files; the terminal stays clean. In non-TTY (CI), the checklist degrades to forward-only lines.

maestro-parallel                              # auto-detect, build + run
maestro-parallel --all                        # every device, no picker
maestro-parallel --skip-build flow.yaml       # one flow, no rebuild
maestro-parallel --shard-split --all          # split flows across devices for speed
maestro-parallel setup-ios-sim                # one-off sim setup (also runs automatically)

Coverage vs. speed: shardMode

By default every device runs the entire flow set (shardMode: 'full') — full coverage across OS versions / form factors. Opt into shardMode: 'split' (CLI: --shard-split) to distribute flows across devices via Maestro's --shard-split=N: wall time drops ~linearly, but each flow runs on only one device. Use split for fast PR feedback, full for nightly regression. See docs/configuration#shardmode.

Auto-detected build defaults

When no build.* is configured, the runner picks the first strategy that fits:

Trigger in cwd Strategy Command
rock.config.{js,ts,mjs,mts,cjs,cts} Rock <pm> exec rock run:<platform> --configuration Release (fingerprint cache → seconds on hit)
eas.json with profile e2e-test / e2e / e2e-tests / preview EAS local <pm> exec eas build --profile <p> --platform <p> --local
expo dep + app.config.{ts,js} or app.json expo run: <pm> expo run:ios --configuration Release / --variant release
(none) skip build use whatever is already installed

<pm> is detected from the lockfile (pnpm > yarn > npm).

After the build, the artifact is reuse-installed on the rest of the group via adb install -r (Android) or xcrun simctl install (iOS sim). Physical iOS reuse-install isn't supported, so the build hook is invoked per device for that group.

Dev / dev-client builds are intentionally not supported — they're structurally flaky for E2E (dev-launcher picker, dev-menu onboarding, Fast Refresh races, adb reverse decay). Build a release artifact and run flows against it.

Custom build hooks

When you need full control — different scheme, productFlavors, signing override — supply your own hook. Auto-detection is skipped per-platform when you provide one.

// maestroparallel.config.ts
import { defineConfig } from 'jsr:@kaln/maestro-parallel/config';

export default defineConfig({
  bundleId: 'com.your.app', // enables app-data clearing between runs
  maestroEnv: { API_URL: '...' }, // forwarded to maestro -e
  appleTeamId: 'ABCDE12345', // physical iOS only

  build: {
    android: {
      async buildAndInstallFirst({ device, cwd, log }) {
        // Build + install on `device`. Return the artifact path so the
        // runner can reuse-install on the rest of the group.
        return { path: '/path/to/app.apk' };
      },
    },
    // ios: { … }
  },
});

Full schema: src/config.ts. Working example: examples/expo.config.ts.

iOS simulator auto-preflight

Every selected sim gets configured before flows run — no opt-in needed:

  • AutoFillPasswords = false in both relevant defaults domains. Kills the "Save Password?" / "AutoFill Passwords" SpringBoard overlay that otherwise blocks Maestro after a credentials submit.
  • Keychain reset via xcrun simctl keychain <udid> reset.
  • SBIdleTimerDisabled = true — sim doesn't auto-lock mid-test.

Physical iOS can't be configured programmatically. The CLI prints a one-line reminder to disable AutoFill manually under Settings → Passwords → Password Options → AutoFill Passwords.

Physical iOS — Apple Team ID

Maestro 2.5+ builds an on-device WebDriver and must code-sign it. Without a 10-character team ID it fails with "Apple account team ID must be specified to build drivers for connected iPhone". Source priority (highest wins):

maestro-parallel --apple-team-id ABCDE12345   # CLI
defineConfig({ appleTeamId: 'ABCDE12345' })   # config file
export MAESTRO_APPLE_TEAM_ID=ABCDE12345        # env var (recommended steady-state)

Find it at developer.apple.com/account → Membership Details → Team ID, or in Xcode → Settings → Accounts.

Requirements

  • Deno ≥ 2.0 (brew install deno) or Node ≥ 18.17 via npm i -g maestro-parallel.
  • Maestro CLI on PATH.
  • adb (Android), xcrun (iOS, ships with Xcode).
  • Apple Developer Team ID for physical iPhones.

Library API

import { runMaestroParallel } from 'jsr:@kaln/maestro-parallel';
const code = await runMaestroParallel({ bundleId: 'com.your.app' });
Deno.exit(code);

Development

deno task check    # type-check
deno task lint     # lint
deno task fmt      # format
deno task run      # run CLI locally

Roadmap

Not promises — directions. PRs welcome.

  • Prebuilt artifact mode: maestro-parallel --apk path.apk --app path.app to skip the build hook entirely and just install + run. Closes the CI gap where the build is a separate step.
  • Distributable smoke artifact: optional --upload <provider> after a green run (Firebase App Distribution / TestFlight).
  • Flow sharding by name pattern: --shard "auth/*" to split flows across devices by glob.
  • Retry-flaky: --retry-failed 1 re-runs only failing flows once on the same device.
  • Per-device flow filter: flowsForDevice(device) hook for platform-specific flows.
  • Physical iOS reuse-install via xcrun devicectl device install app instead of per-device build.
  • WiFi Android discovery: adb connect <host>:<port> auto-join.
  • Android emulator boot: --boot <avd-name> cold-start an emulator for the run.
  • Cloud device pools: BrowserStack / Sauce Labs adapter alongside local devices.
  • HTML report: roll JUnit + Maestro debug bundles into a single browsable index.html.
  • Slack / Discord webhook: summary post after the run.
  • maestro-parallel doctor: pre-flight check (CLI versions, adb auth, sim state, Team ID).
  • Watch mode: maestro-parallel --watch flow.yaml re-runs on file change.
  • Node distribution: dnt build so npx maestro-parallel works without Deno.

License

Apache-2.0

About

Run Maestro E2E flows in parallel on multiple Android and iOS devices. Interactive picker, automatic build/install, JUnit aggregation.

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors