Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions .github/workflows/detox-android-emulator.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
name: Detox on Android emulator (real Percy build)
on:
workflow_dispatch:
inputs:
avd-api-level:
description: 'Android API level'
required: false
default: '34'
type: string
schedule:
# Nightly at 04:00 UTC — confirms the SDK still produces real Percy builds.
- cron: '0 4 * * *'

jobs:
e2e:
name: Build APK + run Detox + capture Percy
runs-on: ubuntu-latest
timeout-minutes: 45
steps:
- uses: actions/checkout@v4

- name: Set up Node 20
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'

- name: Set up JDK 17
uses: actions/setup-java@v4
with:
distribution: 'temurin'
java-version: '17'

# AVD creation is expensive — cache the snapshot so subsequent runs boot in seconds.
- name: Cache AVD
uses: actions/cache@v4
id: avd-cache
with:
path: |
~/.android/avd/*
~/.android/adb*
key: avd-${{ inputs.avd-api-level || '34' }}

- name: Create AVD and generate snapshot for caching
if: steps.avd-cache.outputs.cache-hit != 'true'
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: ${{ inputs.avd-api-level || '34' }}
arch: x86_64
target: google_apis
avd-name: Pixel_7_API_34
force-avd-creation: false
emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: false
script: echo "Generated AVD snapshot for caching."

- name: Install dependencies
run: npm install --legacy-peer-deps

- name: Build Android APKs (debug + androidTest)
run: npm run build:android

- name: Run Detox tests under Percy
uses: reactivecircus/android-emulator-runner@v2
env:
PERCY_TOKEN: ${{ secrets.PERCY_TOKEN }}
with:
api-level: ${{ inputs.avd-api-level || '34' }}
arch: x86_64
target: google_apis
avd-name: Pixel_7_API_34
force-avd-creation: false
emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none
disable-animations: true
script: npx percy app:exec -- npm run test:android

- name: Upload Detox artifacts on failure
if: failure()
uses: actions/upload-artifact@v4
with:
name: detox-artifacts
path: |
artifacts
android/app/build/outputs/logs

Check warning

Code scanning / CodeQL

Workflow does not contain permissions Medium

Actions job or workflow does not limit the permissions of the GITHUB_TOKEN. Consider setting an explicit permissions block, using the following as a minimal starting point: {contents: read}
Comment on lines +16 to +84
17 changes: 12 additions & 5 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
name: Validate
on: [push, pull_request]
name: Tests
on:
push:
pull_request:
jobs:
validate:
runs-on: ubuntu-latest
Expand All @@ -8,9 +10,14 @@ jobs:
- uses: actions/setup-node@v4
with:
node-version: 20
- name: package.json parses
run: node -e "require('./package.json')"
- name: detox config parses
cache: 'npm'
- name: Install dependencies
run: npm install --legacy-peer-deps
- name: Lint
run: npx eslint --ignore-path .gitignore .
- name: Detox config parses
run: node -e "require('./detox.config.js')"
- name: Shell scripts are executable
run: test -x scripts/upload-apk-to-browserstack.sh
- name: TypeScript compiles
run: npx tsc --noEmit -p .
43 changes: 38 additions & 5 deletions App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -69,10 +69,43 @@ const styles = StyleSheet.create({
header: {backgroundColor: '#4f46e5', padding: 16},
headerTitle: {color: '#fff', fontSize: 20, fontWeight: '700'},
body: {flex: 1, justifyContent: 'center', alignItems: 'center', padding: 20},
welcome: {fontSize: 32, marginBottom: 40, color: '#0f172a', fontWeight: '700'},
greeting: {fontSize: 28, marginBottom: 40, color: '#0f172a', fontWeight: '600'},
btn: {backgroundColor: '#4f46e5', paddingHorizontal: 24, paddingVertical: 12, borderRadius: 8, marginBottom: 16, minWidth: 200, alignItems: 'center'},
btnWorld: {backgroundColor: '#22c55e', paddingHorizontal: 24, paddingVertical: 12, borderRadius: 8, marginBottom: 16, minWidth: 200, alignItems: 'center'},
btnGoodbye: {backgroundColor: '#ef4444', paddingHorizontal: 24, paddingVertical: 12, borderRadius: 8, minWidth: 200, alignItems: 'center'},
welcome: {
fontSize: 32,
marginBottom: 40,
color: '#0f172a',
fontWeight: '700',
},
greeting: {
fontSize: 28,
marginBottom: 40,
color: '#0f172a',
fontWeight: '600',
},
btn: {
backgroundColor: '#4f46e5',
paddingHorizontal: 24,
paddingVertical: 12,
borderRadius: 8,
marginBottom: 16,
minWidth: 200,
alignItems: 'center',
},
btnWorld: {
backgroundColor: '#22c55e',
paddingHorizontal: 24,
paddingVertical: 12,
borderRadius: 8,
marginBottom: 16,
minWidth: 200,
alignItems: 'center',
},
btnGoodbye: {
backgroundColor: '#ef4444',
paddingHorizontal: 24,
paddingVertical: 12,
borderRadius: 8,
minWidth: 200,
alignItems: 'center',
},
btnText: {color: '#fff', fontSize: 16, fontWeight: '600'},
});
165 changes: 88 additions & 77 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,112 +1,123 @@
# example-percy-detox

Real React Native + Detox example wired to [`@percy/detox`](https://www.npmjs.com/package/@percy/detox) for visual regression testing.
Example app demonstrating Percy's [Detox](https://wix.github.io/Detox/) integration via [`@percy/detox`](https://github.com/percy/percy-detox).

Based on a clean `@react-native-community/cli init --version 0.74.5` scaffold with Detox 20.38 patched for BrowserStack cloud (`npm:@browserstack/detox`). The app is a small welcome → hello/world/goodbye flow with `testID`s on every interactive element.
A small React Native welcome → hello / world / goodbye app with `testID`s on every interactive element. Built from the stock `@react-native-community/cli init --version 0.74.5` scaffold with Detox 20.38 wired up.

## Percy capture points
## Detox Tutorial

| Spec | Snapshot |
|---|---|
| `e2e/suite1.test.ts` | `Welcome screen` (device) |
| `e2e/suite1.test.ts` | `Hello state` (device) |
| `e2e/suite1.test.ts` | `Element \| welcome` (element-level) |
| `e2e/suite2.test.ts` | `Goodbye state` (device, with `ignoreRegionIds`) |
The tutorial assumes you're already familiar with React Native and
[Detox](https://wix.github.io/Detox/) and focuses on using it with Percy. You'll still
be able to follow along if you're not familiar with Detox, but we won't
spend time introducing Detox concepts.

## Prerequisites
The tutorial also assumes you have [Node 18+ with
npm](https://nodejs.org/en/download/),
[git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git), JDK 17,
Android SDK 34, and an Android emulator named `Pixel_7_API_34` available on your machine.

- Node 20, JDK 17, Android SDK 34 + build-tools 34.0.0, `watchman`
- `detox-cli` globally: `npm i -g detox-cli`
- A Percy **App-type** project token (`app_...`)
### Step 1

## Run locally on an Android emulator (recommended for development)
Clone the example application and install dependencies:

This path exercises `@percy/detox` end-to-end against a real Detox runtime with working `device.takeScreenshot`.

```sh
npm install --legacy-peer-deps
npm run build:android
export PERCY_TOKEN=<your app token>
percy app:exec -- npm run test:android
```bash
$ git clone https://github.com/percy/example-percy-detox.git
$ cd example-percy-detox
$ npm install --legacy-peer-deps
$ npm run build:android
```

Requires an AVD named `Pixel_7_API_34` (adjust `detox.config.js` `devices.emulator.device.avdName` to match what you have).
The example app and its tests will now be ready to go.

### Step 2

## Run on BrowserStack App Automate (Detox Android)
Sign in to Percy and create a new **App**-type project. After
you've created the project, you'll be shown a token environment variable that starts with `app_`.

> **⚠️ Known BrowserStack limitation (as of April 2026):**
> BrowserStack's cloud Detox driver (`@browserstack/detox@20.38.0-cloud.1`) ships a **stub** implementation of `device.takeScreenshot` that always returns an empty string — see `node_modules/detox/src/devices/runtime/drivers/android/cloud/cloudAndroidDriver.js:101`. No `browserstack_executor`-style fallback is exposed server-side either. Until BrowserStack implements on-demand screenshot capture in their cloud driver, `@percy/detox` cannot capture mid-test screenshots on BS cloud. This repo's `android.cloud.debug` config + CI workflow are wired correctly — they'll start producing Percy builds automatically once BS adds the driver support.
### Step 3

Full BS cloud flow (works end-to-end except the takeScreenshot step):
In the shell window you're working in, export the token environment variable:

```sh
# 1. Build real APKs locally
npm install --legacy-peer-deps
npm run build:android
**Unix**

# 2. Set credentials
source ~/.zshrc && app-t # exports PERCY_TOKEN (your app-t zsh fn)
export BROWSERSTACK_USERNAME=<...>
export BROWSERSTACK_ACCESS_KEY=<...>
``` shell
$ export PERCY_TOKEN="<your token here>"
```

# 3. Upload both APKs to BS's Detox-specific endpoints
bash scripts/upload-apk-to-browserstack.sh
# → exports BS_APP_URL, BS_TEST_URL as bs:// URIs
**Windows**

# 4. Generate cloud config with those URIs baked in
node scripts/write-cloud-config.js
``` shell
$ set PERCY_TOKEN="<your token here>"

# 5. Run
npx percy app:exec -- \
npx detox test --configuration android.cloud.debug \
--config-path ./detox.config.cloud.js --headless
# PowerShell
$ $Env:PERCY_TOKEN="<your token here>"
```

### CI
Note: Usually this would only be set up in your CI environment, but to keep things simple we'll
configure it in your shell so that Percy is enabled in your local environment.

### Step 4

`.github/workflows/detox-bs-android.yml` automates steps 1–5. Set these repo secrets:
- `PERCY_TOKEN` (App-project)
- `BROWSERSTACK_USERNAME`, `BROWSERSTACK_ACCESS_KEY`
Check out a new branch for your work in this tutorial (we'll call this branch
`tutorial-example`), then run tests & take snapshots:

Trigger via `Actions` tab → `Run workflow`.
``` shell
$ git checkout -b tutorial-example
$ npx percy app:exec -- npm run test:android
```

This will run the app's Detox tests, which contain calls to create Percy snapshots. The snapshots
will then be uploaded to Percy for comparison. Percy will use the Percy token you used in **Step 2**
to know which organization and project to upload the snapshots to.

## What actually works on BS cloud today
You can view the screenshots in Percy now if you want, but there will be no visual comparisons
yet. You'll see that Percy shows you that these snapshots come from your `tutorial-example` branch.

| Stage | Status |
|---|---|
| APK build (gradle assembleDebug + assembleAndroidTest) | ✅ |
| APK upload to BS `/app-automate/detox/v2/android/app` | ✅ |
| Test APK upload to `/detox/v2/android/app-client` | ✅ |
| `.detoxrc.js` cloud config → BS WebSocket handshake | ✅ |
| `device.launchApp` on real BS Android device | ✅ |
| Detox element matchers / tap / expect | ✅ |
| `device.takeScreenshot()` returning a readable PNG path | ❌ BS driver limitation |
### Step 5

The first five rows constitute the complete integration path. The sixth is a gap in BS's patched Detox, not in `@percy/detox`. The SDK itself is validated against real Detox via the local emulator path.
Use your text editor to edit `App.tsx` and introduce some visual changes. For example, you can
change the welcome text styling to bold. After the change, that line looks
like this:

``` tsx
<Text testID="welcome" style={{ fontSize: 24, fontWeight: 'bold' }}>Welcome</Text>
```

## Why Android only
### Step 6

Commit the change:

``` shell
$ git commit -am "Bold welcome text"
```

### Step 7

Run the tests with snapshots again:

``` shell
$ npx percy app:exec -- npm run test:android
```

Detox does not currently support BrowserStack real iOS devices ([wix/Detox#4694](https://github.com/wix/Detox/issues/4694)). iOS Detox tests can only run on local simulators.
This will run the tests again and take new snapshots of our modified application. The new snapshots
will be uploaded to Percy and compared with the previous snapshots, showing any visual diffs.

## Android build notes (for maintainers)
At the end of the test run output, you will see logs from Percy confirming that the snapshots were
successfully uploaded and giving you a direct URL to check out any visual diffs.

Key patches applied on top of the stock RN 0.74.5 scaffold to support Detox + BS cloud:
### Step 8

- `minSdkVersion: 24` (Detox requires ≥ 24)
- `testBuildType 'debug'`, `testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'`
- `missingDimensionStrategy 'detox', 'full'` (Detox ships `coreNative` + `full` flavors)
- `debuggableVariants = []` in `react { }` block so the debug APK bundles JS (BS devices can't reach local metro)
- `include ':detox'` + `project(':detox').projectDir = ...node_modules/detox/android/detox` in `settings.gradle`
- Kotlin opt-in `-opt-in=kotlin.ExperimentalStdlibApi` on the `:detox` subproject
- `android/app/src/androidTest/AndroidManifest.xml` to force `android:exported=true` on androidx.test's `InstrumentationActivityInvoker*` activities
- `DetoxTest.java` in `androidTest/java/com/percydetoxapp/` as the Detox runner entry
Visit your project in Percy and you'll see a new build with the visual comparisons between the two
runs. Click anywhere on the Build 2 row. You can see the original snapshots on the left, and the new
snapshots on the right.

## Known issues / SDK-level notes
Percy has highlighted what's changed visually in the app! Snapshots with the largest changes are
shown first You can click on the highlight to reveal the underlying screenshot.

- `element.takeScreenshot` misses TextureView, GLSurfaceView, Skia canvases on Android ([wix/Detox#4489](https://github.com/wix/Detox/issues/4489)). Use `device.takeScreenshot` for these.
- `atIndex(n).getAttributes()` silently ignored on iOS ([wix/Detox#4633](https://github.com/wix/Detox/issues/4633)); `@percy/detox` handles the `.elements` multi-match defensively.
- Detox tmp-file lifetime: `@percy/detox` copies Detox's returned PNG to an SDK-owned tmp path immediately to avoid races with Detox's artifact cleanup ([wix/Detox#3311](https://github.com/wix/Detox/issues/3311), [#4824](https://github.com/wix/Detox/issues/4824)).
If you scroll down, you'll see that no other test cases were impacted by our changes to the welcome
text. The unchanged snapshots appear grouped together at the bottom of the list.

## License
### Finished! 😀

MIT
From here, you can try making your own changes to the app and tests, if you like. If you do, re-run
the tests and you'll see any visual changes reflected in Percy.
Loading
Loading