> ## Documentation Index
> Fetch the complete documentation index at: https://momentic.ai/docs/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> The canonical docs host is https://momentic.ai/docs. Append .md to any page URL for clean Markdown. Fetch https://momentic.ai/docs/llms.txt for the page index and https://momentic.ai/docs/llms-full.txt for every page in one file.
> For an unattended setup, run `npx --yes @momentic/wizard@latest -y --platform web --editor-tools skills` (use `--platform ios` or `--platform android` for mobile). Never run an interactive command in a non-TTY shell.
> Mo is the AI QA engineer, a separate product from the YAML test framework: its npm package and CLI are `qa` (not `mo` — `npx mo` is a different project). Install with `npm install -g qa`, authenticate the same MOMENTIC_API_KEY way, install repo skills with `qa skills -y`, and start a session with `qa start "<brief>"` or `qa <url>`. Docs: https://momentic.ai/docs/mo.md.
> You cannot complete the browser sign-in yourself. Read the API key from the exact MOMENTIC_API_KEY environment variable (similarly named vars like BILT_MOMENTIC_API_KEY are ignored), or ask the user to create one at https://app.momentic.ai/settings/api-keys and paste it back.
> Momentic's capability summary for agents is at https://momentic.ai/docs/skill.md. Install the skills with `npx momentic skills --yes` (or `npx momentic-mobile skills --yes` for mobile).
> Tests are YAML files in the user's repository. Use only the step names listed at https://momentic.ai/docs/reference/commands.md and the file structure at https://momentic.ai/docs/core-concepts/file-format.md. Do not invent step names, config keys, or CLI flags.
> Web tests run on Chromium, iOS tests on simulators, and Android tests on emulators. Physical devices are not supported.

# From Maestro

> Port Maestro flows to momentic-mobile, including flows, subflows, scripts, and environment variables.

Translate Maestro flows into `momentic-mobile` tests while preserving the
behavior each flow checks. Both use YAML, but launch behavior, selectors,
variables, subflows, and assertions need review rather than a key rename.

## Prepare the project

Complete the [iOS](/docs/quickstart/ios) or [Android](/docs/quickstart/android) setup and
keep your Maestro files and CI job. Momentic uses a separate CLI and
`momentic.config.yaml`; it does not execute Maestro flow files.

* Android needs an APK, `adb`, and `ANDROID_HOME` or `ANDROID_SDK_ROOT`, even
  for remote emulators. Local emulators also need an installed AVD. See
  [Android app setup](/docs/platforms/android/app-setup).
* iOS needs a simulator `.app` build, not a device `.ipa`. Local simulators
  require macOS, Xcode, and idb. Remote simulators use an uploaded build. See
  [iOS app setup](/docs/platforms/ios/app-setup).
* Momentic supports emulators and simulators, not physical devices. Keep Maestro
  coverage that requires device hardware or an unsupported OS version.
* Record the selected flow's build, initial app state, permissions, test data,
  assertions, and hooks. Use the same conditions when comparing results.

## Before and after

A representative Maestro flow:

```yaml checkout.yaml theme={null}
appId: com.example.shop
env:
  EMAIL: jeff@example.com
---
- launchApp
- tapOn: "Add to cart"
- tapOn:
    id: "cart-icon"
- assertVisible: "Gravity Blanket"
- tapOn: "Checkout"
- tapOn: "Email"
- inputText: ${EMAIL}
- tapOn: "Place order"
- assertVisible: "Order confirmed"
```

The Android version force-stops the named package before opening it and keeps
the expected text in its visibility checks:

```yaml checkout.test.yaml theme={null}
fileType: momentic/mobile-test/v2
id: guest-checkout
description: Complete guest checkout and verify confirmation
platform: android
defaultEnv: staging
steps:
  - adb:
      command: shell
      args: '["am", "force-stop", "com.example.shop"]'
  - openApp: com.example.shop
  - tap: the Add to cart button
  - tap: the cart icon
  - checkElementVisible: the text 'Gravity Blanket' in the cart
  - tap: Checkout
  - type:
      text: "{{ env.EMAIL }}"
      into: the Email field
      clearContent: false
  - tap: Place order
  - checkElementVisible: the text 'Order confirmed'
```

Replace the package ID and targets with your app's values. The example assumes
the app's starting state has an empty cart and a guest checkout flow.

Add `EMAIL` to the existing `staging` environment in `momentic.config.yaml`. Do
not replace other environments or settings:

```yaml momentic.config.yaml theme={null}
environments:
  - name: staging
    envVariables:
      EMAIL: jeff@example.com
```

`defaultEnv: staging` selects that environment. A shell variable only becomes
available through an explicit variable reference or `inheritFromShell: true`;
shell inheritance defaults to `false`. Put secrets in an ignored environment
file or your CI secret store. See
[environment variables](/docs/configuration/environment-variables).

For remote Android execution, upload the APK to a channel and pin the build tag
when running the file:

```bash theme={null}
npx momentic-mobile assets upload ./build/app.apk --channel staging --tag migration-1
npx momentic-mobile lint mobile-tests/checkout.test.yaml
npx momentic-mobile run mobile-tests/checkout.test.yaml \
  --channel staging --tag migration-1 --upload-results
```

Save the example as `mobile-tests/checkout.test.yaml` under the project's
configured `include` paths, and replace the APK path with your build. Without a
channel or an explicit installation step, `openApp` assumes the app is already
installed. For local execution, pass an AVD and APK, or an iOS simulator type
and `.app` path. See
[mobile run options](/docs/cli-reference/momentic-mobile/commands/run).

## Command mapping

| Maestro | Momentic |
| - | - |
| `appId: com.x` + `launchApp` | Restart the intended app, then `openApp: com.x`. Use the named-package Android restart above; translate reset options separately. |
| `killApp` / `stopApp` | `killApp` stops the foreground app. Translate stops that target a different package separately. |
| `tapOn: <text>` / `tapOn: {id:}` | `tap: <natural language>`; prefer visible text over test IDs |
| `tapOn: {point: "x%,y%"}` | `tap: "50%, 50%"` percent-pair target |
| `longPressOn` | `tap: { on: ..., longPress: true }` |
| `doubleTapOn` | `tap: { on: ..., iterations: 2 }` |
| `inputText` / `inputRandomText` | `type: { text: ..., into: <field> }`; use `faker` in `javascript` for random data |
| `eraseText` | `type: { text: "", into: <field>, clearContent: true }`; check the resulting value |
| `assertVisible` / `assertNotVisible` | `checkElementVisible` / `checkElementNotVisible` for element visibility; preserve selector constraints |
| `assertTrue` | `javascript` step with `assert` |
| `scroll` / `swipe` | `swipe` for a gesture; `scrollTo` to reach a named target |
| `scrollUntilVisible` | `scrollTo: { on: <target>, in: <container> }` |
| `back` / `pressKey` | `press` / `pressKey` |
| `runFlow: path` | `module: ./path.module.yaml` with `inputs`; flatten nested subflow calls because modules cannot call modules |
| `runScript` | `javascript` step (Node sandbox) or `appium` step for `mobile:` calls |
| `evalScript` | `javascript` step |
| `setLocation` | Use an `appium` script supported by the target platform; `toggleSettings: location` toggles Android location services, not coordinates |
| `setAirplaneMode` / `toggleAirplaneMode` | `toggleSettings: airplane_mode` on Android toggles the setting; explicitly setting a known value needs a state check |
| `openLink` / deep links | `appium` step (`mobile: deepLink` with `url`) |
| `startRecording` / `stopRecording` | `--video true` or `recordVideo: true` records the test run; custom start/stop boundaries do not transfer |
| `waitForAnimationToEnd` | Review whether auto-waiting covers the transition; retain a readiness condition when an animation or asynchronous operation matters |
| `extendedWaitUntil` | any condition step with `timeout:` |
| `takeScreenshot` | automatic per-step screenshots; `assertVisually` for visual checks |
| `retry` | `retries` on the step payload or test; preserve the retried sequence and make its side effects safe to repeat |
| `when:` conditions | `if:` with nested `then:` steps |
| `repeat` / `while` | `while:` with `do:` and a condition or `maxIterations` |
| `env:` block + `${VAR}` | `envVariables` in config, `{{ env.VAR }}` in strings, `env.VAR` in JS |
| `labels:` | `labels:` (same name) |
| `onFlowStart` / `onFlowComplete` hooks | `before:` / `after:` sections |

## Preserve selectors, checks, and waits

Maestro's
[text and ID selectors](https://docs.maestro.dev/maestro-flows/flow-control-and-logic/how-to-use-selectors)
use regular expressions. Momentic target descriptions are natural language, not
regular expressions or Maestro selector objects. Translate the intended target,
including any parent, position, index, or state constraint. Do not paste an ID
regex into `tap` and assume it keeps the original match.

Use [element visibility checks](/docs/reference/mobile-commands/check-element) for
visible or hidden elements. Use
[element content checks](/docs/reference/mobile-commands/check-element-content) for
exact text. `checkScreenContains` searches the screen hierarchy or WebView
content and can match offscreen content; it does not replace `assertVisible`.
Use [AI assertions](/docs/reference/mobile-commands/assert) when the intended check
is semantic, and review whether that changes the original contract.

Preserve `extendedWaitUntil` timeouts on a condition step in milliseconds. For
example, wait up to 30 seconds for the confirmation to become visible:

```yaml theme={null}
- checkElementVisible:
    on: the text 'Order confirmed'
    timeout: 30000
```

Auto-waiting handles target readiness, but it does not establish that a
background save or network request has finished. Keep those checks explicit.
Retries rerun actions; timeouts wait for a condition. Do not replace a wait with
a retry when repeating the action creates duplicate data.

## Subflows, variables, and hooks

Extract a reusable sequence into a mobile module. Map `runFlow.env` values to
module inputs instead of putting every flow's variables in the global project
environment. Keep defaults local to the module, and use `setVariable` or
`saveAs` for values created during a test. See
[module parameters](/docs/core-concepts/modules#parameters) and
[variable scope](/docs/core-concepts/variables#scope).

This module takes one input and has no nested module calls:

```yaml modules/enter-email.module.yaml theme={null}
fileType: momentic/mobile-module/v2
id: migrated-enter-email
name: Enter email
platform: android
parameters:
  parameterNames: [EMAIL]
steps:
  - type:
      text: "{{ env.EMAIL }}"
      into: the Email field
      clearContent: false
```

Replace the inline email-entry step in the checkout test with this invocation:

```yaml theme={null}
- module:
    path: ../modules/enter-email.module.yaml
    inputs:
      EMAIL: env.EMAIL
```

`env.EMAIL` is an expression; use `{ string: "jeff@example.com" }` to pass a
literal. Flatten nested `runFlow` calls into the module's steps, or invoke each
module from the test. A module cannot call another module. Module paths are
relative to the calling file, so `mobile-tests/checkout.test.yaml` uses
`../modules/enter-email.module.yaml` for the module shown above.

Translate `when` conditions to an assertion-based `if` where possible. Split
platform-specific behavior into iOS and Android tests or modules; the mobile
`if` condition is not a Maestro `platform` selector. Check loop bounds and retry
boundaries rather than renaming `repeat` or `retry` mechanically.

Move each top-level flow's `onFlowStart` and `onFlowComplete` behavior into
`before` and `after`. Modules do not have Maestro flow hooks. Inline a subflow's
setup at its original call site. Put cleanup that must survive a subflow failure
in the test's `after`, and check that its ordering and scope still match the
original flow. A failed `before` skips the main steps; `after` still runs, so
cleanup must tolerate incomplete setup. Running one step in the editor skips
test hooks. Validate them with a whole-test run. See
[before and after sections](/docs/core-concepts/file-format#before-and-after-sections).

Check
[Maestro launch options](https://docs.maestro.dev/reference/commands-available/launchapp)
separately. Its default `launchApp` restarts the app, and options can clear app
data or the iOS keychain and set permissions. Momentic's `killApp` stops the
current foreground app. Use it before `openApp` only when the intended app is
already in the foreground; it does not target the identifier passed to a later
`openApp`. The Android example uses `shell am force-stop` to stop that package
regardless of which app is active. Neither method clears app data. To reset app
data on Android, use an [`adb`](/docs/reference/mobile-commands/adb) step:

```yaml theme={null}
- adb:
    command: shell
    args: '["pm", "clear", "com.example.shop"]'
```

Preserve iOS keychain, permissions, and other reset behavior using a verified
setup method, or retain the Maestro test until you have one.

Use independent backend records for each test and clean up only the records it
created. A new emulator session does not clear your application's backend.

## What does not map

* Momentic has no separate inspector app like Maestro Studio: the local editor's
  element picker plus `debugState` cover element inspection.
* Maestro `runScript` and `evalScript` code needs adaptation. Momentic's
  `javascript` step runs in a Node sandbox with its own globals; Maestro's
  `output` object and UI commands do not become Momentic APIs. Use `env`,
  `setVariable`, and `saveAs` for data, and preset or AI steps for UI actions.
* Physical devices are not supported: remote execution is emulators and
  simulators only.
* Maestro's tolerance and optional-element flags have no equivalent: Momentic
  resolves target descriptions rather than Maestro selector flags. For an
  optional action, use an `if` step with an element check and nested `then`
  steps. Mobile `if` has no `else` branch. See
  [mobile conditionals](/docs/reference/mobile-commands/if).
* A `maestro cloud` command and its workspace configuration do not transfer to
  Momentic. Configure build upload, test selection, parallelism, reporting, and
  scheduling explicitly in your CI provider.

## Incremental strategy

1. Port one bounded flow end to end, including its hooks, subflows, inputs,
   waits, and cleanup. Start with a flow whose repeated locator failures you can
   compare. Keep Maestro installed and preserve the `.maestro` directory.
2. Lint and run the file on the intended target. Compare both runners on the
   same build and starting state. Inspect screenshots at assertions, including a
   case where the expected outcome is absent.
3. Separate environment values from module parameters. Pin the app build tag and
   check the OS version, permissions, and reset behavior before comparing
   stability.
4. Add a separate Momentic CI job with its own test data. Do not run both
   runners concurrently on the same local emulator. Remote Momentic tests use
   independent emulator sessions; local Android parallel tests need distinct
   AVDs, and local iOS runs should use `--parallel 1`.
5. Replace gates flow by flow after reviewing equivalent coverage and repeated
   results. Retain legacy flows for hardware, OS, selectors, or reset behavior
   you have not migrated. Remove their CI jobs and files only after that
   coverage is accounted for.

In CI, install `momentic-mobile` from the lockfile, set `MOMENTIC_API_KEY` from
the CI secret store, and provide the platform prerequisites. Upload the build
with a unique tag, then use that same tag when running tests. Add
`--reporter junit` to generate reports under `./reports`, and preserve the run
command's exit status as the job result. See
[GitHub Actions](/docs/running-tests/ci/github-actions) or the
[other CI guides](/docs/guides).

## Porting at scale with a coding agent

Paste this prompt into your coding agent after installing Momentic and providing
the app build:

```text theme={null}
Port every flow under .maestro/ to momentic-mobile tests under mobile-tests/.
Read the momentic-mobile-test skill first. Convert runFlow targets to
*.module.yaml files, keep the directory structure, move shared env: values into
environments[].envVariables, preserve flow-local values with setVariable,
pass runFlow.env as module inputs, and run each ported test with
npx momentic-mobile run <file> on an emulator.
```

## Related

* [Test portability](/docs/get-started/test-portability)
* [Mobile steps reference](/docs/reference/mobile-commands/index)
* [Emulators and simulators](/docs/running-tests/hosted-test-environments)
* [Modules](/docs/core-concepts/modules)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.