Skip to main content

Configuration

maestro-parallel reads an optional maestroparallel.config.{ts,mts,js,mjs,cjs,json} from the project root.

Minimal

maestroparallel.config.ts
export default {
bundleId: 'com.example.app',
};

Full example

maestroparallel.config.ts
export default {
bundleId: 'com.shoptet.admin',
flowsDir: '.maestro/flows',
appleTeamId: 'MTHKX3U9QF',

buildStrategy: 'expo', // override auto-detect
buildEnv: { SENTRY_DISABLE_AUTO_UPLOAD: 'true' }, // env for build child

maestroEnv: { APP_BASE_URL: 'https://779779.myshoptet.com' },

iosSequential: true, // default true — XCTestService races on per-sim ports
iosShardAll: false, // mutually exclusive with iosSequential

processStartStaggerMs: 2000, // workaround for Maestro 2.5.x log-dir race
keepRuns: 3,
};

Fields

bundleId?: string

App bundle identifier (com.example.app). Required for clearAppState (skipped silently when absent).

flowsDir?: string

Maestro flows directory or single flow file, relative to cwd. Default .maestro.

outputDir?: string

Where to write per-run output. Default .maestro/output.

keepRuns?: number

Past parallel-* runs kept in outputDir. Default 3. Must be a positive integer (≥ 1) — otherwise prune would delete the current run directory and config load fails fast.

maestroConfigPath?: string

Path to project's existing Maestro config. Used by single-process shard runs (iosShardAll or shardMode: 'split') — executionOrder.flowsOrder is stripped from a temp copy so --shard-all / --shard-split work (sharding is incompatible with a fixed flow order). Default .maestro/config.yaml.

maestroEnv?: Record<string, string>

Extra env vars forwarded to Maestro via -e KEY=VALUE. Host env wins.

build?: { android?: PlatformBuildHooks; ios?: PlatformBuildHooks }

Per-platform build hooks. Omit a platform to disable it. When you set these, auto-detect is skipped for that platform. See Build strategies → Custom hooks.

hooks?: { preTest?: (devices: Device[]) => Promise<void> }

Run between build/install and Maestro. Useful for permission grants, keychain reset, etc.

iosSequential?: boolean

Default true. Maestro 2.5.x races on per-sim XCTestService ports when more than one iOS device is driven concurrently.

iosShardAll?: boolean

Default false. Run one Maestro process with --shard-all=N instead of one process per device. Mutually exclusive with iosSequential.

appleTeamId?: string

10-char Apple Developer Team ID. Required for physical iOS — Maestro builds its iOS WebDriver against the device and needs a team to code-sign it. CLI --apple-team-id and env MAESTRO_APPLE_TEAM_ID both override this.

processStartStaggerMs?: number

Delay (ms) between starting consecutive Maestro processes. Workaround for the Maestro 2.5.x per-second session-log-dir race. Default 2000. Set to 0 to disable.

idleKillMs?: number

Idle-kill watchdog (ms). Maestro can hang in DebugLogStore.finalizeRun after the last flow; the runner SIGTERMs (then SIGKILLs) the child after this many ms with no output. Must stay above the longest output-silent stretch inside a flowextendedWaitUntil (commonly 30 000 ms) plus a login network round-trip go silent well past 30 s, so too low a value truncates the suite mid-run. Default 120000 (2 min). Set 0 to disable. Only applies to the live (checklist) path.

grpcBailThreshold?: number

Number of "cannot reach the gRPC server" errors after which the runner kills a device's Maestro process early. An incompatible device (e.g. an Android too old for the Maestro driver) otherwise burns ~17 s per flow re-failing the whole suite. Default 10. Set 0 to disable the fail-fast.

buildMode?: 'release' | 'skip'

Pre-selected build mode (release = run the build hook, skip = assume the app is already installed). CLI --skip-build overrides to skip. In a non-TTY environment with no explicit choice, the default is release.

buildStrategy?: 'auto' | 'rock' | 'eas' | 'expo'

Pin the auto-detect choice. Default 'auto' (priority: rock > eas > expo). Use when your project has artifacts of more than one strategy.

buildEnv?: Record<string, string>

Env merged into the auto-detected build child (Rock / EAS / expo run:*). Has no effect on user-defined build.* hooks — those control their own env. Common use: SENTRY_DISABLE_AUTO_UPLOAD: 'true'.

concurrentBuilds?: boolean

Run platform groups (android, ios-sim, ios-usb) in parallel instead of sequentially. Wall time drops to max(group) instead of sum. Default false. RAM/CPU/disk pressure multiplies — three xcodebuild/gradle/Metro pipelines at once can OOM a 16 GB Mac and the Pods cache may race. Enable only on beefy machines (M-series with ≥ 32 GB).

shardMode?: 'full' | 'split'

How flows are distributed across devices in a platform group. Default 'full'.

  • 'full' — every device runs the entire flow set (current default). Verifies each flow on every OS version / form factor in the pool. Wall time ≈ longest flow set on the slowest device.
  • 'split' — flows are distributed across devices via Maestro --shard-split=N. Each device runs a slice. Wall time drops ~linearly with device count, but each flow runs on only one device — coverage trade-off.

CLI flag --shard-split overrides to 'split'. Notes:

  • Single-process model: one maestro test --shard-split=N --device id1,id2,… per platform group. Maestro 2.5+ handles per-device distribution internally — there is no separate --shard-index flag.
  • Mutually exclusive with iosShardAll — set one or the other, not both.
  • Platform groups with only 1 device fall back to 'full' silently (no --shard-split=1 flag emitted).

Fingerprint-based build cache

mp uses @expo/fingerprint — the same library Rock and Expo Build Cache use internally — to skip the build hook entirely when nothing native has changed. State stored at .maestro/output/.fingerprint-<group>.json (per platform group).

On cache hit:

  • Build hook is not invoked — saves the full Rock/EAS/expo run:* spawn.
  • Reuse-install still runs on the rest of the group to bring devices into a known state.
  • Device row shows built cache hit.

Cache invalidated on any native change tracked by @expo/fingerprint (native deps, ios/ and android/ files, expo plugins, package.json scripts). JS-only edits don't bust it. Applies to android and ios-sim groups only; iOS USB always runs a fresh build (signing complications).