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

Integrate your Storybook React Native components with App Percy

Run App Percy visual tests on your Storybook React Native components using the @percy/storybook-react-native SDK, captured on real devices through BrowserStack App Automate.

Use the @percy/storybook-react-native SDK to add App Percy visual testing to your Storybook React Native components. The SDK discovers the stories in your React Native project, captures one snapshot per story, and uploads them to your App Percy project.

You can run your visual tests in either of two modes. The SDK is the same in both. The difference is where the tests run and who manages the device:

  • BrowserStack App Automate, or library mode: You run your components on a real device in a BrowserStack App Automate session. You provision a Storybook host app, and BrowserStack manages the device.
  • Local, or CLI mode: You run your components on a local emulator or simulator that you manage.

Choose the tab that matches where you want to run your tests:

In library mode, the SDK runs against BrowserStack App Automate from inside your existing WebdriverIO Appium tests. You build a Storybook-enabled app, upload it to App Automate, and run a test that discovers your stories and captures a snapshot of each on a real device.

Prerequisites

Before you begin, ensure you have the following:

Install the SDK

Install the SDK, the App Percy Appium SDK, and the Percy CLI as development dependencies:

Terminal
Copy icon Copy

Set your environment variables

Export your BrowserStack credentials and your App Percy token. The token has an app_ prefix:

Terminal
Copy icon Copy

Build a Storybook app

Build a Storybook-enabled release .apk for Android. This app is separate from your production app. For the recommended Expo and bare React Native build setup, see the STORYBOOK_HOST_APP.md guide in the SDK repository.

Terminal
Copy icon Copy

Build with assembleRelease, not assembleDebug. A debug build expects Metro to serve the JavaScript bundle from localhost:8081. A BrowserStack cloud device has no Metro server, so a debug build fails with a loadJSBundleFromAssets redbox. A release build embeds the JavaScript bundle in the .apk.

For iOS, App Automate needs a distribution-signed .ipa, which the SDK cannot produce for you. See the Build a signed .ipa for iOS section below.

Upload the app to App Automate

Call provisionApp with the path to your built app. It uploads the app to App Automate and returns a bs:// reference:

upload.js
Copy icon Copy

provisionApp computes a content hash as the BrowserStack custom_id, so re-running with the same app returns the existing bs:// reference instead of uploading again. Set the reference as an environment variable for your test to read:

Terminal
Copy icon Copy

Point the SDK at your project

The SDK discovers stories from your React Native project’s .rnstorybook/main.ts configuration and .stories files. Set the project root so discoverStories can read them:

Terminal
Copy icon Copy

Add the snapshot calls to your test

In your WebdriverIO spec, start an App Automate session, then wrap your story iteration in runSession. percyStorybookSnapshot is the default export, and discoverStories and runSession are named exports:

storybook.spec.js
Copy icon Copy

runSession(driver, fn) runs driver.deleteSession() even when the test throws, so a failed run doesn’t leave a dangling App Automate session that burns your quota.

Run your tests

Run your test through the Percy CLI using the command:

Terminal
Copy icon Copy

App Percy starts, creates a build, captures one snapshot per story on a real BrowserStack device, and uploads them to your project. When the run finishes, the CLI prints the build URL:

Open the build URL to see one snapshot per story, captured on the device.

Use percyStorybookSnapshot(driver, story, options) to control how the SDK captures each story. Pass options in a flat object. The SDK handles the navigation keys itself and forwards every other key to the @percy/appium-app percyScreenshot call, so you can use the full App Percy Appium option surface:

storybook.spec.js
Copy icon Copy

Performance options

These navigation options control how long the SDK waits at each stage. Override them per snapshot call:

Option Default Purpose
coldBootMaxMs 30000 Real BrowserStack Android devices cold-boot between 4 and 20 seconds under load.
globalNavigationBudgetMs 8000 Hard ceiling on the total time the SDK spends navigating to a story per snapshot call.
renderMs 1500 Fallback delay the SDK waits after the stability poll runs out.
stabilitySettleMs 200 Defers the first stability capture to avoid catching the pre-render frame as stable.
cacheNavigatorState false When true, the SDK keeps the drawer open across stories, saving about 500 ms per intra-component story.

The SDK navigates to each story before it captures a snapshot. Set the navigationStrategy option to choose how it navigates:

  • ui-tap: Navigates by tapping through the Storybook UI. This is the default and works on all supported platforms with no extra setup.
  • deeplink: Opens each story through a deep link, which is faster at about 500 ms per story compared to about 2 seconds. Your app must register a URL scheme, and you pass appScheme and appPackage in the snapshot options. For the scheme setup on Expo and bare React Native, see the APP_AUTOMATE.md guide in the SDK repository.

Deep-link navigation is unreliable below iOS 16.4. On earlier versions, the SDK fails with deep_link_unsupported_platform. Drop navigationStrategy: 'deeplink' to use the default UI-tap path, which works on all iOS versions.

App Automate installs a distribution-signed .ipa on iOS devices. The SDK cannot sign the app for you, because Apple’s signing flow needs your Apple Developer account, distribution certificate, and provisioning profile. After you have a signed .ipa, pass it to provisionApp the same way as an Android .apk:

upload.js
Copy icon Copy

For the full signing walkthrough, which covers the distribution certificate, the Ad Hoc provisioning profile, the xcodebuild archive and export steps, and CI integration, see the IOS_SIGNING.md guide in the SDK repository.

Set your BrowserStack credentials, Percy token, and app details through environment variables:

Variable Mode Purpose
PERCY_TOKEN Both App-type Percy project token. Starts with app_.
BROWSERSTACK_USERNAME Library App Automate authentication for provisionApp and the driver session.
BROWSERSTACK_ACCESS_KEY Library App Automate authentication for provisionApp and the driver session.
PERCY_APP_URL Library The bs:// reference returned by provisionApp.
PERCY_RN_PROJECT_DIR Library React Native project root for story discovery, where .rnstorybook/ lives.
PERCY_APP_SCHEME Library URL scheme for navigationStrategy: 'deeplink'.
PERCY_APP_PACKAGE Library Android package ID for navigationStrategy: 'deeplink'.
DEBUG Both Set to 1 for verbose per-story logging.

For CLI mode, the SDK also reads a storybook-rn: block in .percy.yml. The following keys are available:

Key Default Notes
appium.server http://localhost:4723 Must be an http: or https: URL, or the run fails with invalid_config.
appium.capabilities {} The SDK merges these over its per-platform defaults. Set platformName here.
storybook.websocketHost localhost Pin to 127.0.0.1 on macOS to avoid the IPv6 mismatch.
storybook.websocketPort 7007 Storybook React Native channel server port.
storybook.waitForReadyMs 4000 Backstop wait for the device render acknowledgment. Raise for stories with slow data or image loads.
storybook.settleMs 250 Extra delay after the render acknowledgment, for animations or image decode.
include ['**/*'] Story-ID globs to keep. The CLI --include flag overrides this.
skip [] Story-ID globs to drop. Wins over include.

Every failure produces a structured error code in the message. Look up the code in the following table:

Error code What it means What to do
bs_credentials_missing BROWSERSTACK_USERNAME or BROWSERSTACK_ACCESS_KEY is not set. Add the environment variables, or pass credentials to provisionApp(path, { credentials }).
bs_upload_failed BrowserStack rejects the upload. Check the response body in the error message. A 401 or 403 means bad credentials.
bs_upload_too_large The app exceeds your BrowserStack plan’s size limit, which defaults to 1 GB. Strip native modules from your Storybook .apk, or contact BrowserStack support.
bs_app_not_ready The first Appium session install fails because BrowserStack is still processing the upload. The SDK retries once after a 5-second pause. If you see this repeatedly, file a Percy issue.
nav_navigator_not_detected The SDK can’t find the Storybook React Native navigator within coldBootMaxMs. Confirm your .apk is a Storybook-enabled build, not the production app. Check the session video.
nav_element_not_found A story’s component group or leaf isn’t tappable. Verify the component title and story name match the on-device sidebar exactly.
nav_render_timeout The story renders but the screenshot-stability poll doesn’t converge within 8 seconds. Likely a permanent animation. Set parameters.percy.waitFor per story.
app_cold_boot_timeout The cold-boot poll exceeds 30 seconds. The device is slow. Increase coldBootMaxMs in the snapshot options.
bs_app_reference_stale The bs:// reference points to an upload that BrowserStack no longer has. Uploads expire after about 30 days. Re-run provisionApp to get a fresh bs:// URL.
bs_quota_exhausted Your BrowserStack plan’s App Automate session quota is used up. Wait for running sessions to finish, or raise the plan limit.
invalid_app_reference The app reference does not start with bs://. Pass the bs:// URL returned by provisionApp, not a local file path.
build_is_debug_variant The uploaded app is a debug build, which expects Metro on localhost:8081 and fails on a cloud device. Build the release variant with ./gradlew assembleRelease, which embeds the JavaScript bundle.
build_failed The SDK-driven Gradle or EAS build command exits with a non-zero code. Read the build output above the error, fix the native build failure, and re-run.
build_toolchain_missing The build command isn’t available on this machine. Install the missing toolchain, or build the artifact yourself and pass its path to provisionApp.
build_artifact_not_found The build succeeds but no .apk is at the expected output path. Locate the artifact and pass it to provisionApp directly.
apple_signing_required iOS on App Automate needs a distribution-signed .ipa, which the SDK cannot produce. Follow the iOS signing guide, then call provisionApp('./MyApp.ipa').
unsupported_project_type The project path is neither an Expo nor a bare React Native root. Pass the React Native project root, the directory whose package.json lists react-native.
unsupported_platform The Appium session’s platform is neither Android nor iOS. Set platformName to Android or iOS in your capabilities.
deep_link_unsupported_platform Deep-link navigation doesn’t settle in time on this platform. Verify the URL scheme is registered, or drop navigationStrategy: 'deeplink' to use the default UI-tap path.
url_scheme_silent_failure The SDK sends the deep link but the app doesn’t surface the story. Check that the scheme in app.json, AndroidManifest.xml, or Info.plist matches appScheme exactly.
nav_state_diverged The on-device navigator ends up somewhere other than the requested story. Usually a duplicate story title. Make the CSF title and export names unique across the project.
no_stories_found Story discovery finds a .rnstorybook config but no .stories files match its glob. Check the stories glob in .rnstorybook/main.ts, set PERCY_RN_PROJECT_DIR, or pass --stories explicitly.
include_zero_match The include or skip patterns filter out every discovered story. Loosen the include and skip globs in .percy.yml.
invalid_descriptor The Appium session has no platformName capability. Set platformName to iOS or Android in the .percy.yml appium.capabilities.
invalid_config A .percy.yml storybook-rn: value fails validation. Fix the value named in the message, for example appium.server: http://localhost:4723.
appium_unreachable There is no Appium session, or the server is down. Start the Appium server and check appium.server. In library mode, pass a live driver.
percy_appium_app_missing The @percy/appium-app peer dependency is not installed. Run npm install --save-dev @percy/appium-app.
percy_cli_unreachable The Percy CLI server is not running. Wrap the run with npx percy app:exec -- <command> and set PERCY_TOKEN.
token_missing PERCY_TOKEN is not set. Export the app-type project token, which starts with app_, from your Percy project settings.
screenshot_failed Appium screenshot capture fails mid-run. The device lost focus or the session expired. Check the session video and re-run with DEBUG=1.
story_render_timeout The story doesn’t acknowledge render. Confirm Metro is running and the device is connected. Increase storybook.waitForReadyMs for slow stories.
storybook_ws_unreachable Storybook’s channel server is up but WebSockets are disabled. Add enableWebsockets: true to getStorybookUI() in .rnstorybook/index.
all_snapshots_failed Every story in the run fails to capture. Read the per-story errors above, and re-run with DEBUG=1 for full detail.

In CLI mode, the npx percy storybook-rn command drives a local iOS Simulator or Android emulator through Appium. The SDK discovers your stories, renders each on the device, and uploads one snapshot per story to your App Percy project.

Prerequisites

Before you begin, ensure you have the following:

  • A Percy account with an app-type App Percy project. The project token has an app_ prefix.
  • Node.js version 20.19 or later, or 22.12 or later.
  • Appium 2.x, with the uiautomator2 driver for Android or the xcuitest driver for iOS.
  • For iOS, Xcode and CocoaPods on macOS.
  • For Android, Android Studio with an emulator.
  • A React Native project that uses Storybook for React Native.

Install Percy

Install the Percy CLI and the SDK as development dependencies:

Terminal
Copy icon Copy

Run the scaffolder to create a starting .percy.yml and metro.config.js, both pre-configured to avoid common setup issues:

Terminal
Copy icon Copy

Enable WebSockets in Storybook

@storybook/react-native version 10 ships with the on-device WebSocket connection disabled. Add enableWebsockets: true to your getStorybookUI() call so the SDK can tell the device which story to render:

.rnstorybook/index.ts
Copy icon Copy

Build a native dev binary

Build a native dev binary rather than running in Expo Go. Storybook React Native needs native modules that Expo Go doesn’t include. On iOS, the build boots a Simulator with your app:

Terminal
Copy icon Copy

The first build takes 5 to 15 minutes. Later builds use Xcode’s cache and finish in seconds. When it completes, the Simulator shows your app with Storybook’s component picker.

On Android, the emulator doesn’t share the host’s localhost. After the app boots, forward the channel and Metro ports so the device can reach them:

adb reverse tcp:7007 tcp:7007
adb reverse tcp:8081 tcp:8081

Start the Appium server

In a separate terminal, start Appium:

Terminal
Copy icon Copy

Run Percy

Set your App Percy token, then run the health check and the snapshot command:

Terminal
Copy icon Copy

The command auto-discovers your stories, renders each on the device, and uploads the snapshots. A build URL prints when the run finishes. Open it to see one snapshot per story.

Use these flags to control which stories run:

Terminal
Copy icon Copy

The SDK reads a storybook-rn: block in .percy.yml. The following keys are available:

Key Default Notes
appium.server http://localhost:4723 Must be an http: or https: URL, or the run fails with invalid_config.
appium.capabilities {} The SDK merges these over its per-platform defaults. Set platformName here.
storybook.websocketHost localhost Pin to 127.0.0.1 on macOS to avoid the IPv6 mismatch.
storybook.websocketPort 7007 Storybook React Native channel server port.
storybook.waitForReadyMs 4000 Backstop wait for the device render acknowledgment. Raise for stories with slow data or image loads.
storybook.settleMs 250 Extra delay after the render acknowledgment, for animations or image decode.
include ['**/*'] Story-ID globs to keep. The CLI --include flag overrides this.
skip [] Story-ID globs to drop. Wins over include.

Every CLI failure produces a structured error code in the message. Look up the code in the following table:

Error code What it means What to do
no_stories_found The Storybook config is missing, or no .stories files match the glob. Run npx storybook init --type react_native, or check the stories glob in main.ts.
appium_unreachable The Appium server doesn’t accept a connection. Start Appium with npx appium --port 4723, and verify with curl http://localhost:4723/status.
storybook_ws_unreachable The channel server on port 7007 isn’t responding. Verify withStorybook pins host: '127.0.0.1' and port: 7007 in metro.config.js, and check the Metro log.
percy_cli_unreachable The Percy CLI’s local server on port 5338 isn’t running. Wrap your command with npx percy exec -- <command>.
token_missing PERCY_TOKEN isn’t set. Run export PERCY_TOKEN=app_... with a token from your Percy project settings.
story_render_timeout The device doesn’t acknowledge render within waitForReadyMs. Increase waitForReadyMs to 6000 to 8000, or verify enableWebsockets: true in getStorybookUI().
screenshot_failed Appium’s screenshot capture returns an error. The device lost focus or the session expired. Re-run with DEBUG=1 for details.
include_zero_match Your include or skip patterns match no stories. Run with --dry-run to see all discovered stories, then adjust the patterns.

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