Skip to main content
No Result Found
Master this framework faster. Get optimization tips from elite testers. Join our DiscordJoin our Discord

Integrate your Maestro test suite with App Percy

Run App Percy visual tests on your Maestro mobile flows using the @percy/maestro-app SDK on BrowserStack or a self-hosted runtime.

Use the @percy/maestro-app SDK to add App Percy visual testing to your Maestro flows. You add a screenshot step to a flow, then run that flow with App Percy, which captures the screen and uploads it to your App Percy project.

The SDK is the same on both runtimes. The difference is where the tests run and who manages the infrastructure. Choose the tab that matches where you run your Maestro tests:

  • BrowserStack SDK: Tests run on BrowserStack infrastructure. Your Maestro flow runs on a BrowserStack App Automate session, and BrowserStack provides the device, manages the runtime, and injects the device metadata for you.
  • Self-hosted: Tests run on your own infrastructure, such as a local machine, a CI runner, or your own device lab. You run maestro test, wrap the run with percy app:exec, and supply the device metadata yourself.

With the BrowserStack SDK, your Maestro flow runs on a BrowserStack App Automate session. To set it up, you install the SDK, add a screenshot step to your flow, and run a build on App Automate with your App Percy token.

Prerequisites

Before you begin, ensure you have the following:

Install the SDK

Install the SDK and the Percy CLI as development dependencies:

Terminal
Copy icon Copy

The SDK ships a percy/ directory that contains the flow and script files your Maestro flow references at runtime. Reference this directory in either of these ways before you zip your test suite for upload to BrowserStack:

  • Reference under node_modules: Keep the SDK in node_modules and include the node_modules/@percy/maestro-app/percy directory when you zip your test suite.
  • Copy into your workspace (recommended): Copy the SDK’s percy/ directory into your workspace to produce a smaller zip.

To copy the directory into your workspace, run the following command:

Terminal
Copy icon Copy

Your workspace then has the following layout:

Workspace layout

Capture a screenshot

To capture a screenshot at any point in your flow, add a runFlow step that references the SDK’s percy-screenshot.yaml and sets the SCREENSHOT_NAME environment variable:

your-flow.yaml
Copy icon Copy

The first screenshot step initializes App Percy, so you don’t need to configure it separately.

SCREENSHOT_NAME must match the pattern ^[a-zA-Z0-9_-]+$. Names with spaces, dots, or slashes are rejected with an HTTP 400 response.

Run your tests

Run your flow through the Percy CLI:

Terminal
Copy icon Copy

App Percy starts, creates a build, captures each screenshot in the flow, and uploads them to your project.

Configuration

Set the following environment variables to control how App Percy captures each screenshot. On BrowserStack Maestro sessions, the device variables are injected for you, so you only set the ones you want to override.

Variable Required Default Purpose
SCREENSHOT_NAME Yes N/A Name for the screenshot. Must match ^[a-zA-Z0-9_-]+$.
PERCY_DEVICE_NAME No Injected on BrowserStack Device identifier, such as Pixel 7. BrowserStack sets this from the session device.
PERCY_OS_VERSION No Injected on BrowserStack OS version, such as 13 on Android or 17 on iOS. BrowserStack sets this from the session device.
PERCY_ORIENTATION No portrait Screen orientation, either portrait or landscape.
PERCY_REGIONS No N/A JSON array of region masks.
PERCY_SYNC No false Set to true to wait for comparison results.
PERCY_FULLSCREEN No false Set to true when the screen has no system chrome.
PERCY_STATUS_BAR_HEIGHT No Android 120, iOS 100 Height in image pixels to mask at the top of the screen.
PERCY_NAV_BAR_HEIGHT No Android 100, iOS 80 Height in image pixels to mask at the bottom of the screen.

Status bar and navigation bar masking

App Percy masks the device status bar and navigation bar so that the clock, battery, and other dynamic system elements don’t trigger false visual differences. The defaults differ by platform:

Variable Android default iOS default
PERCY_STATUS_BAR_HEIGHT 120 100
PERCY_NAV_BAR_HEIGHT 100 80

Device pixel densities vary, so the default heights don’t fit every device. Override PERCY_STATUS_BAR_HEIGHT and PERCY_NAV_BAR_HEIGHT per device when the mask doesn’t cover the full bar. For example, newer iPhone models have a taller status bar area at the top of the screen, so they need a taller status bar mask than the iOS default.

Regions

Use the PERCY_REGIONS environment variable to mark areas of a screenshot for a specific comparison algorithm. Set it to a JSON array, where each object defines a region and the algorithm to apply.

The following algorithms are available:

  • ignore: Excludes the region from comparison. Any change inside the region is ignored.
  • standard: Compares the region with the standard sensitivity.
  • intelliignore: Uses AI to ignore dynamic content such as advertisements and carousels.
  • layout: Checks structural layout with a pixel-level tolerance.

Coordinate-based regions

Define a region by its pixel boundaries:

YAML
Copy icon Copy

Element-based regions

Define a region by a UI element. The supported locators differ by platform.

On Android, use resource-id, text, content-desc, or class:

YAML
Copy icon Copy

On iOS, use id, the accessibility identifier, or class:

YAML
Copy icon Copy

Run on BrowserStack

When you trigger a Maestro build through the BrowserStack App Automate API, pass your App Percy token in the appPercy object of the build payload. This field is the same on both Android and iOS.

Use the appPercy field to pass your Percy token. The percyOptions field is silently dropped, so screenshots won’t reach your project if you use it.

The following request triggers an Android build:

Terminal
Copy icon Copy

The following request triggers an iOS build and passes an extra Percy variable through the env sub-object:

Terminal
Copy icon Copy

Pass any PERCY_* variable through the appPercy.env sub-object.

Unsupported features

The following features aren’t supported in version 1.0.0 of the SDK:

  • Full-page or scrollable capture.
  • Animation freezing and Percy-specific CSS.
  • XPath region selectors on Android.
  • Automatic device-metadata detection on iOS.

The following features are planned for a later release:

  • The PERCY_IGNORE_ERRORS and PERCY_ENABLED switches.
  • The text and xpath element selectors on iOS.
  • Rendering of PERCY_LABELS on the dashboard.

On the self-hosted runtime, you run maestro test and wrap it with percy app:exec. This command starts the Percy CLI on your machine, creates the App Percy build, runs your test, and finalizes the build when the test exits. App Percy captures full-screen and region-masked screenshots on Android or iOS, on real devices and on simulators or emulators, and uploads them to your project.

Prerequisites

Before you begin, ensure you have the following:

  • A Percy account with an App Percy project, and its project token. The token starts with app_.
  • Node.js and npm installed. Node.js 14 or higher.
  • The Percy CLI, version 1.32.3-beta.3 or later. Self-hosted Maestro support, including screenshot directory auto-resolution, isn’t in the stable @percy/cli yet, so install this version explicitly. @percy/cli@beta also resolves to it.
  • Maestro 2.0 or higher, installed and on your PATH.
  • A connected Android or iOS device, emulator, or simulator that maestro test can drive.
  • For Android element regions, adb on your PATH.

Install and copy the SDK files

Install the SDK and the Percy CLI as development dependencies, then copy the SDK’s percy/ directory into your workspace:

Terminal
Copy icon Copy

The copy step keeps your runFlow paths short. Alternatively, reference the SDK’s percy/ directory under node_modules. Your workspace then has the following layout:

Workspace layout

Capture a screenshot

To capture a screenshot at any point in your flow, add a runFlow step that references the SDK’s percy-screenshot.yaml and sets the SCREENSHOT_NAME environment variable:

your-flow.yaml
Copy icon Copy

SCREENSHOT_NAME is the only required input per snapshot. The first screenshot step initializes App Percy, so you don’t need to configure it separately.

SCREENSHOT_NAME must match the pattern ^[a-zA-Z0-9_-]+$. Names with spaces, dots, or slashes are rejected with an HTTP 400 response.

Run your tests

Set your App Percy token as an environment variable:

export PERCY_TOKEN=<your-percy-token>
$Env:PERCY_TOKEN="<your-percy-token>"
set PERCY_TOKEN=<your-percy-token>

Run your flow through percy app:exec and pass the device metadata as Maestro -e flags:

Terminal
Copy icon Copy

On the self-hosted runtime, you supply the device tags yourself, unlike on BrowserStack sessions where they’re injected for you. Because they can’t be derived from an arbitrary device, PERCY_DEVICE_NAME and PERCY_OS_VERSION are required.

The command resolves the screenshot output directory for you, points both Maestro and the Percy CLI at the same path, and creates it if needed. The following table describes how it resolves the directory:

Your setup What the command does
You set nothing Uses ./.percy-out and creates it. If that path isn’t writable, the command falls back to a temporary directory and logs a warning.
You export PERCY_MAESTRO_SCREENSHOT_DIR Uses your value and injects a matching --test-output-dir.
You pass --test-output-dir to maestro test Uses your flag and mirrors it to PERCY_MAESTRO_SCREENSHOT_DIR for the CLI.
You set both Uses your paths and changes nothing.

Your explicit value always takes precedence. You only need to set the directory to pin a specific location, for example to keep artifacts or give each device its own directory when running on multiple devices.

Where to set each environment variable

Maestro and the Percy CLI read environment variables from different places:

  • Device tags, such as PERCY_DEVICE_NAME, PERCY_OS_VERSION, and the per-snapshot variables, are read by the SDK inside the Maestro flow. Pass them as Maestro -e flags.
  • Device addressing for element-region resolution, such as PERCY_IOS_DRIVER_HOST_PORT, PERCY_IOS_DEVICE_UDID, and ANDROID_SERIAL, is read by the Percy CLI. Export these in the shell before percy app:exec. A Maestro -e flag only reaches Maestro, not the CLI.

Configuration

Set the following environment variables to control how App Percy captures each screenshot:

Variable Required Where you set it Purpose
PERCY_TOKEN Yes Shell export Your App Percy project token. Starts with app_. Source it from your CI secret store and never commit it.
PERCY_DEVICE_NAME Yes Maestro -e flag Device name for the Percy tag, such as Pixel 7 or iPhone 15 Pro.
PERCY_OS_VERSION Yes Maestro -e flag OS version, such as 13 on Android or 17 on iOS.
SCREENSHOT_NAME Yes, per snapshot Per-runFlow env Screenshot name. Use only letters, digits, hyphens, and underscores (^[a-zA-Z0-9_-]+$). Names with spaces or other characters, such as Home Screen or login@1, aren’t supported. Use Home_Screen or login-1 instead.
PERCY_MAESTRO_SCREENSHOT_DIR No Shell export Directory the Percy CLI reads screenshots from. Auto-resolved to ./.percy-out. Set it only to pin a specific location.
PERCY_SERVER No Maestro -e flag Percy CLI address. If snapshots log not reachable, set -e PERCY_SERVER=http://localhost:5338 explicitly.
PERCY_ORIENTATION No Flow env Screen orientation, either portrait or landscape. Defaults to portrait.
PERCY_REGIONS No Per-runFlow env JSON array of region masks.
PERCY_TEST_CASE No Per-runFlow env Logical test name shown alongside the snapshot.
PERCY_LABELS No Per-runFlow env Comma-separated labels for dashboard filtering.

Device addressing

Export the following variables in the shell. They apply only to element regions:

Variable Platform When you need it
PERCY_IOS_DRIVER_HOST_PORT iOS Real devices, sharded runs, or Maestro versions that don’t bind 7001. Match your --driver-host-port.
PERCY_IOS_DEVICE_UDID iOS Optional. A single connected device auto-selects.
ANDROID_SERIAL Android When more than one Android device or emulator is connected.

Status bar and navigation bar masking

App Percy masks the device status bar and navigation bar so that the clock, battery, and other dynamic system elements don’t trigger false visual differences. The defaults differ by platform:

Variable Android default iOS default
PERCY_STATUS_BAR_HEIGHT 120 100
PERCY_NAV_BAR_HEIGHT 100 80

Device pixel densities vary, so the default heights don’t fit every device. Override PERCY_STATUS_BAR_HEIGHT and PERCY_NAV_BAR_HEIGHT per device when the mask doesn’t cover the full bar.

Regions

Use the PERCY_REGIONS environment variable to mask areas of a screenshot so that dynamic content, such as timestamps, animations, and A/B-tested layouts, doesn’t trigger false visual differences. Set it to a JSON array, where each object defines a region and the algorithm to apply.

The following algorithms are available:

  • ignore: Excludes the region from comparison. Any change inside the region is ignored.
  • standard: Compares the region with the standard sensitivity.
  • intelliignore: Uses AI to ignore dynamic content such as advertisements and carousels.
  • layout: Checks structural layout with a pixel-level tolerance.

Coordinate-based regions

Define a region by its pixel boundaries. Coordinates are native PNG pixels, where 0,0 is the top-left corner:

YAML
Copy icon Copy

The SDK auto-masks the device status bar and the bottom navigation bar, so you usually only need manual coordinate regions for in-content masking.

Element-based regions

Define a region by a UI element. The Percy CLI resolves the element’s bounding box at snapshot time by running maestro hierarchy. The supported locators differ by platform.

On Android, use resource-id, text, content-desc, class, or id:

YAML
Copy icon Copy

On iOS, use only the id selector:

YAML
Copy icon Copy

Each platform resolves element regions differently on the self-hosted runtime:

  • On Android, element regions work when adb is on your PATH and exactly one device is connected, or when ANDROID_SERIAL is exported. The CLI runs maestro hierarchy to resolve selectors.
  • On iOS, the CLI auto-discovers the Maestro driver port on current Maestro versions, up to 2.4.0. For real iOS devices, sharded runs, or Maestro versions on an ephemeral port, export PERCY_IOS_DRIVER_HOST_PORT to match your --driver-host-port in the shell before percy app:exec.

If no driver is reachable, App Percy drops the element regions with a warning and still uploads the snapshot with any coordinate regions intact. If a selector matches nothing in the live hierarchy, App Percy skips that one region and uploads the snapshot.

Run on multiple devices

Each device is one percy app:exec invocation, on its own port and its own output directory. Because the default output directory is per working directory, concurrent runs in the same directory collide. Give each invocation its own --test-output-dir and matching PERCY_MAESTRO_SCREENSHOT_DIR. To merge concurrent sessions into a single App Percy build, set a shared parallel nonce:

Terminal
Copy icon Copy

Each invocation needs its own --port, its own --test-output-dir, and its own device. Omit the nonce to ship each session as a separate build.

Troubleshooting

The following table lists common issues and their fixes:

Symptom Likely cause Fix
A snapshot logs that the Percy CLI is not reachable The SDK didn’t pick up the Percy CLI address from the environment. Add -e PERCY_SERVER=http://localhost:5338 to maestro test.
HTTP 400 Invalid screenshot name SCREENSHOT_NAME has a space or punctuation. Use letters, numbers, _, and - only, such as Home_screen.
A warning that screenshots fell back to a temporary directory ./.percy-out isn’t writable. Export PERCY_MAESTRO_SCREENSHOT_DIR to a writable path.
HTTP 400 Missing required env: PERCY_MAESTRO_SCREENSHOT_DIR Your @percy/cli predates self-hosted support. Install @percy/cli@1.32.3-beta.3 or later.
HTTP 404 Screenshot not found A custom --test-output-dir doesn’t match PERCY_MAESTRO_SCREENSHOT_DIR. Set both to the same path, or set neither and let the command resolve it.
The Percy build finalizes with Snapshot command was not called The flow never called runFlow: percy/flows/percy-screenshot.yaml. Confirm your flow invokes the Percy sub-flow.
iOS element regions skipped with no Maestro driver found You’re running a real device or a sharded setup. Export PERCY_IOS_DRIVER_HOST_PORT to match your --driver-host-port.
Android element regions skipped with an adb warning adb isn’t on the PATH, or zero or multiple devices are connected. Install Android Platform Tools. Connect one device or export ANDROID_SERIAL.
An element region logs not found - skipping The selector doesn’t match the live hierarchy. Run maestro hierarchy to verify. iOS supports only id. Android supports resource-id, text, content-desc, class, and id.

We're sorry to hear that. Please share your feedback so we can do better

Contact our Support team for immediate help while we work on improving our docs.

We're continuously improving our docs. We'd love to know what you liked





Thank you for your valuable feedback

Is this page helping you?

Yes
No

We're sorry to hear that. Please share your feedback so we can do better

Contact our Support team for immediate help while we work on improving our docs.

We're continuously improving our docs. We'd love to know what you liked





Thank you for your valuable feedback!

Talk to an Expert
Download Copy Check Circle