How to run Detox tests on GitHub Actions

remarkablemark
5 min readFeb 20, 2023
Detox logo
Detox

This post goes over how to set up and run React Native Detox tests on GitHub Actions.

Prerequisites

You have a React Native project with Detox set up.

iOS

Create a GitHub Actions workflow named e2e-ios.yml:

mkdir -p .github/workflows && touch .github/workflows/e2e-ios.yml

Name your workflow, set up the event that triggers the workflow, and create a job that runs on macOS:

name: e2e-ios
on: push
jobs:
e2e-ios:
runs-on: macos-latest
steps:
# ...

iOS Steps

Check out the repository:

- name: Checkout repository
uses: actions/checkout@v3

Set up Node.js:

- name: Setup Node.js
uses: actions/setup-node@v3
with:
cache: yarn
node-version-file: .node-version

If you’re using NVM, replace .node-version with .nvmrc.

Install node_modules with Yarn:

- name: Install Yarn dependencies
run: yarn --frozen-lockfile --prefer-offline

Install applesimutils with Homebrew:

- name: Install macOS dependencies
run: |
brew tap wix/brew
brew install applesimutils

If you want to speed up the step, you can disable Homebrew’s auto update and install cleanup:

env:
HOMEBREW_NO_AUTO_UPDATE: 1
HOMEBREW_NO_INSTALL_CLEANUP: 1

Set up Ruby, run bundle install, and cache gems:

- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true

ruby/setup-ruby will get the Ruby version from .ruby-version.

Install and cache CocoaPods:

- name: Cache CocoaPods
id: cache-cocoapods
uses: actions/cache@v3
with:
path: ios/Pods
key: ${{ runner.os }}-pods-${{ hashFiles('ios/Podfile.lock') }}
restore-keys: |
${{ runner.os }}-pods-

- name: Install CocoaPods
if: steps.cache-cocoapods.outputs.cache-hit != 'true'
run: cd ios ; pod install ; cd -

Optionally, you can clean and build Detox framework cache:

- name: Detox rebuild framework cache
run: yarn detox rebuild-framework-cache

Build and cache Detox iOS (release) build:

- name: Cache Detox build
id: cache-detox-build
uses: actions/cache@v3
with:
path: ios/build
key: ${{ runner.os }}-detox-build
restore-keys: |
${{ runner.os }}-detox-build

- name: Detox build
run: yarn detox build --configuration ios.sim.release

This builds the iOS app using the ios.sim.release configuration in .detoxrc.js.

Run Detox tests:

- name: Detox test
run: yarn detox test --configuration ios.sim.release --cleanup --headless --record-logs all

If you get the error Exceeded timeout of 120000ms while setting up Detox environment, then increase the testRunner.jest.setupTimeout in .detoxrc.js.

If there’s a failure, upload the Detox artifacts so you can download them after the workflow ends:

- name: Upload artifacts
if: failure()
uses: actions/upload-artifact@v3
with:
name: detox-artifacts
path: artifacts

iOS Workflow

Here’s the full E2E workflow for iOS (see code):

# .github/workflows/e2e-ios.yml
name: e2e-ios
on: push

jobs:
e2e-ios:
runs-on: macos-latest

steps:
- name: Checkout repository
uses: actions/checkout@v3

- name: Setup Node.js
uses: actions/setup-node@v3
with:
cache: yarn
node-version-file: .node-version

- name: Install Yarn dependencies
run: yarn --frozen-lockfile --prefer-offline

- name: Install macOS dependencies
run: |
brew tap wix/brew
brew install applesimutils
env:
HOMEBREW_NO_AUTO_UPDATE: 1
HOMEBREW_NO_INSTALL_CLEANUP: 1

- name: Setup Ruby
uses: ruby/setup-ruby@v1
with:
bundler-cache: true

- name: Cache CocoaPods
id: cache-cocoapods
uses: actions/cache@v3
with:
path: ios/Pods
key: ${{ runner.os }}-pods-${{ hashFiles('ios/Podfile.lock') }}
restore-keys: |
${{ runner.os }}-pods-

- name: Install CocoaPods
if: steps.cache-cocoapods.outputs.cache-hit != 'true'
run: cd ios ; pod install ; cd -

- name: Detox rebuild framework cache
run: yarn detox rebuild-framework-cache

- name: Cache Detox build
id: cache-detox-build
uses: actions/cache@v3
with:
path: ios/build
key: ${{ runner.os }}-detox-build
restore-keys: |
${{ runner.os }}-detox-build

- name: Detox build
run: yarn detox build --configuration ios.sim.release

- name: Detox test
run: yarn detox test --configuration ios.sim.release --cleanup --headless --record-logs all

- name: Upload artifacts
if: failure()
uses: actions/upload-artifact@v3
with:
name: detox-artifacts
path: artifacts

Android

Before you start, make sure you first patch your project with the additional Android configuration.

Create a GitHub Actions workflow named e2e-android.yml:

mkdir -p .github/workflows && touch .github/workflows/e2e-android.yml

Name your workflow, set up the event that triggers the workflow, and create a job that runs on macOS:

name: e2e-android
on: push
jobs:
e2e-android:
runs-on: macos-latest
steps:
# ...

Android Steps

Check out the repository:

- name: Checkout repository
uses: actions/checkout@v3

Set up Node.js:

- name: Setup Node.js
uses: actions/setup-node@v3
with:
cache: yarn
node-version-file: .node-version # .nvmrc

Install node_modules with Yarn:

- name: Install Yarn dependencies
run: yarn --frozen-lockfile --prefer-offline

Set up Java:

- name: Setup Java
uses: actions/setup-java@v3
with:
cache: gradle
distribution: temurin
java-version: 17

Build and cache Detox Android (release) build:

- name: Cache Detox build
id: cache-detox-build
uses: actions/cache@v3
with:
path: ios/build
key: ${{ runner.os }}-detox-build
restore-keys: |
${{ runner.os }}-detox-build

- name: Detox build
run: yarn detox build --configuration android.emu.release

This builds the Android app using the android.emu.release configuration in .detoxrc.js.

Get the Android Virtual Device (AVD) name from .detoxrc.js:

- name: Get device name
id: device
run: node -e "console.log('AVD_NAME=' + require('./.detoxrc').devices.emulator.device.avdName)" >> $GITHUB_OUTPUT

Run Detox tests using reactivecircus/android-emulator-runner:

- name: Detox test
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 31
arch: x86_64
avd-name: ${{ steps.device.outputs.AVD_NAME }}
script: yarn detox test --configuration android.emu.release --headless --record-logs all

Make sure not to pass the --cleanup option in the script or else it will throw an error:

detox[27246] i adb: error: device 'emulator-5554' not found

If there’s a failure, upload the Detox artifacts so you can download them after the workflow ends:

- name: Upload artifacts
if: failure()
uses: actions/upload-artifact@v3
with:
name: detox-artifacts
path: artifacts

Android Workflow

Here’s the full E2E workflow for Android (see code):

# .github/workflows/e2e-android.yml
name: e2e-android
on: push

jobs:
e2e-android:
runs-on: macos-latest

steps:
- name: Checkout repository
uses: actions/checkout@v3

- name: Setup Node.js
uses: actions/setup-node@v3
with:
cache: yarn
node-version-file: .node-version

- name: Install Yarn dependencies
run: yarn --frozen-lockfile --prefer-offline

- name: Setup Java
uses: actions/setup-java@v3
with:
cache: gradle
distribution: temurin
java-version: 17

- name: Cache Detox build
id: cache-detox-build
uses: actions/cache@v3
with:
path: android/app/build
key: ${{ runner.os }}-detox-build
restore-keys: |
${{ runner.os }}-detox-build

- name: Detox build
run: yarn detox build --configuration android.emu.release

- name: Get device name
id: device
run: node -e "console.log('AVD_NAME=' + require('./.detoxrc').devices.emulator.device.avdName)" >> $GITHUB_OUTPUT

- name: Detox test
uses: reactivecircus/android-emulator-runner@v2
with:
api-level: 31
arch: x86_64
avd-name: ${{ steps.device.outputs.AVD_NAME }}
script: yarn detox test --configuration android.emu.release --headless --record-logs all

- name: Upload artifacts
if: failure()
uses: actions/upload-artifact@v3
with:
name: detox-artifacts
path: artifacts

--

--