Get your setup working faster. Join our Discord for optimisation tips from elite testers.Join 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.
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:
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:
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.
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.
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_.
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:
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:
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:
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:
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:
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:
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: