Maestro
The @archibald/maestro package wraps Maestro for native end-to-end testing of Archibald's React Native / Expo builds. It is invoked through the archibald maestro CLI command.
Installation
From the root of an Archibald project:
archibald add maestro
This:
- Adds
@archibald/maestroas a dev dependency. - Scaffolds a
maestro/directory at the project root (config.yaml+flows/smoke.yaml). - Registers a
maestronpm script that invokesarchibald maestro.
Installing the Maestro CLI
Maestro itself is a JVM-based CLI distributed outside of npm. You need to install it once on your host machine:
- macOS (Homebrew):
brew tap mobile-dev-inc/tapbrew install maestro
- Linux / WSL:
curl -fsSL "https://get.maestro.mobile.dev" | bashexport PATH="$PATH":"$HOME/.maestro/bin"
- Windows: use WSL with the Linux instructions, or follow the manual setup in the Maestro docs.
The archibald maestro command preflights maestro --version and prints platform-specific install instructions if the CLI is missing, so you do not need to memorize the above.
Use Maestro >= 2.5. Maestro <= 2.1 does not reliably trigger Pressable components under React Native's New Architecture (Fabric) on iOS, which the shop template uses.
Usage
Overview
Maestro flows are declarative YAML files. Each flow declares a target appId, then a sequence of commands (launchApp, tapOn, assertVisible, inputText, …). Flows live in maestro/flows/ by default; the workspace-level maestro/config.yaml declares the default appId and the flow glob.
The scaffold added by archibald add maestro looks like:
maestro/
├── .gitignore
├── config.yaml
└── flows/
└── smoke.yaml
Building the app first
Maestro drives an installed binary on a simulator/emulator — it does not connect to a Metro dev server. Before running flows, build and install the app:
pnpm ios # builds + installs on iOS simulator
pnpm android # Android equivalent (requires Android SDK)
Executing flows
archibald maestro # whole suite (config.yaml's flow glob)
archibald maestro -f maestro/flows/smoke.yaml # single flow
archibald maestro -e MAESTRO_USERNAME=alice # inject env into flows
archibald maestro -a com.example.app.preview # override appId for a preview build
See the full flag list under archibald maestro.
When cli.e2e.framework is "maestro" (set automatically by archibald add maestro), the unified archibald e2e command dispatches to the Maestro runner — a peer of Cypress and Playwright. You can also select it ad-hoc with archibald e2e --runner=maestro. Either way it runs the default flow suite (maestro/); for -f, -e, or -a overrides, use archibald maestro directly.
App ID
The default appId in the scaffolded config.yaml matches what archibald build --platform=app --native=ios --run installs locally — the unsuffixed bundle identifier. Override per run for preview / development builds with --appId:
archibald maestro -a net.netconomy.archibald.template.shop.preview
archibald maestro -a net.netconomy.archibald.template.shop.dev
Environment variables
Pass per-flow environment with -e KEY=value. The flag can be repeated:
archibald maestro \
-e MAESTRO_USERNAME="$MAESTRO_USERNAME" \
-e MAESTRO_PASSWORD="$MAESTRO_PASSWORD"
Inside a flow, reference an injected value with ${KEY}.
Selector strategy
Maestro's iOS visibility heuristic uses layout bounds (not viewport clipping) and its text: selector matches the iOS text attribute, not accessibilityText. As a result:
- Prefer
id:selectors (testID / resource-id) for anything you want a flow to interact with reliably. Instrument Pressables, inputs, and tab labels withtestIDprops in the production component. - Visible English copy from
messages.jsonis generally not searchable on iOS; do not rely on it for taps or assertions on RN screens. - For elements rendered below a sticky element (e.g. a floating tab bar), assert on a stable element near the top of the screen rather than scrolling the off-screen target into view — Maestro's "visible" heuristic may report a partially-occluded element as visible without actually being tappable.
API
The package also exposes a Node API used by the archibald maestro command. You can call it directly from scripts or custom runners:
import { assertMaestroInstalled, runMaestro } from '@archibald/maestro';
assertMaestroInstalled(); // throws MaestroNotInstalledError if the CLI is missing
const { exitCode } = await runMaestro({
flows: 'maestro/flows',
flowEnv: { USERNAME: process.env.MAESTRO_USERNAME ?? '' }
});
Public exports: verifyMaestroInstall, assertMaestroInstalled, MaestroNotInstalledError, runMaestro, getInstallInstructions, formatInstallInstructions, detectHostPlatform.