If you are developing iOS and Android applications with React Native you might have encountered Detox. It's end-to-end testing and automation toolbox written and maintained by the Wix company. You can find some articles on dev.to about writing tests with Detox:
- React Native End to End Testing with Detox by Spencer Carli https://dev.to/spencercarli/react-native-end-to-end-testing-with-detox-56bk
- End-to-end testing in React Native with Detox by Brian Neville-O'Neill https://dev.to/bnevilleoneill/end-to-end-testing-in-react-native-with-detox-5736
When I start a new react-native project I want to have a test environment ready. You are more likely to test your code or your product if it's easy to write and run tests. I wanted to document how to setup Detox tests for React Native. Moreover, how to set up a workflow that on every push to Github I would get test runner within Github Actions.
Starter Shell Repository
I have created a starter shell repository with the empty react-native project (with a single test) that has a Detox package installed.
https://github.com/edvinasbartkus/react-native-detox-github-actions
The project includes two workflows: iOS and Android. Both workflows are using macOS-latest environments to run the tests. Important to know, every second spent on MacOS machines is equal to 10 seconds on Linux. As of right now, Github provides 2000 minutes for free every month. That translates to 200 free minutes on MacOS machines.
I tried to get sdkmanager and adkmanager to Linux environment. However, that does not seem to be so easy. Emulators are not that easy to launch and run as you can imagine. If someone gets to set up Android emulators on Github CI Linux machines, I am more than welcome to get tips on how to do that.
Steps
I go through all the steps to describe what's being done and why some options matter. The steps are based on iOS workflow: https://github.com/edvinasbartkus/react-native-detox-github-actions/blob/master/.github/workflows/ios.yml
- Simple name with a definition of hook - I want to run tests on every push.
name: iOS
on: [push]
- Build environment: run tests on MacOS latest and if the tests don't finish in 15 minutes - kill it. I had a few problems in my side projects where tests would hung up and would spend hours not doing anything just wasting valuable minutes.
jobs:
build:
runs-on: macos-latest
timeout-minutes: 15
- It's worth to have
DEVELOPER_DIR
defined pointing to an active installation of XCode. This changes with new versions of XCode. From time to time you have to go and look it up what's the directory for the latest version of XCode. Here is the page at Github Actions Help where all environment details are listed out: https://help.github.com/en/actions/automating-your-workflow-with-github-actions/virtual-environments-for-github-hosted-runners
env:
DEVELOPER_DIR: /Applications/Xcode_11.2.app
- First actionable thing, let's get our source code of the project. We will not work with history so it's worth to checkout only the head of the repository.
steps:
- name: Checkout
uses: actions/checkout@v1
with:
fetch-depth: 1
- We need Node to get npm packages installed.
- name: Node
uses: actions/setup-node@v1
- I do cache node_modules/. While the project is young and empty, there is not much time saved with cached modules. As the project grows you will start to see bigger time wins because of caching.
- name: Cache node modules
uses: actions/cache@v1
id: cache
with:
path: node_modules
key: node-modules-${{ hashFiles('**/yarn.lock') }}
- When we restore node modules from the cache, we need to rebuild the Detox Framework. Otherwise, we might get an error that the Detox Framework is missing when we want to run tests.
- name: Rebuild detox
if: steps.cache.outputs.cache-hit == 'true'
run: detox clean-framework-cache && detox build-framework-cache
- In case if there is no cache we run regular
yarn install
.
- name: Install Dependencies
if: steps.cache.outputs.cache-hit != 'true'
run: yarn install
- Since we cache
node_modules/
, it's worth to cache pods directory. It's the next big thing that has to be installed every time we prepare the environment for testing.
- name: Cache Pods
uses: actions/cache@v1
id: podcache
with:
path: ios/Pods
key: pods-${{ hashFiles('**/Podfile.lock') }}
- This could be avoided since we restore pods from the cache but it's rather a fast operation if pods are already in place.
- name: Update Pods
run: |
gem update cocoapods xcodeproj
cd ios && pod install && cd ..
- Finally, we install applesumutils (a requirement by detox) and we run tests.
- run: brew tap wix/brew
- run: brew install applesimutils
- run: yarn detox build e2e --configuration ios.sim.release
- run: yarn detox test e2e --configuration ios.sim.release --cleanup --debug-synchronization 200
Android Differences
In Android workflow, we follow the same sequence. There is only few steps that are specific to Android:
https://github.com/edvinasbartkus/react-native-detox-github-actions/blob/master/.github/workflows/android.yml
- We use an action to set up Java in MacOS environment.
- name: Use specific Java version for sdkmanager to work
uses: joschi/setup-jdk@v1
with:
java-version: 'openjdk8'
architecture: 'x64'
- Next, we download Android Emulator and we create an instance of emulator that is called
emu
- name: Download Android Emulator Image
run: |
echo "y" | $ANDROID_HOME/tools/bin/sdkmanager --install "system-images;android-29;google_apis;x86"
echo "no" | $ANDROID_HOME/tools/bin/avdmanager create avd --force --name emu --device "Nexus 5X" -k 'system-images;android-29;google_apis;x86'
$ANDROID_HOME/emulator/emulator -list-avds
- We launch the emulator and wait until it is started
- name: Android Emulator
timeout-minutes: 10
continue-on-error: true
run: |
echo "Starting emulator"
nohup $ANDROID_HOME/emulator/emulator -avd emu -no-audio -no-snapshot -no-window &
$ANDROID_HOME/platform-tools/adb wait-for-device shell 'while [[ -z $(getprop sys.boot_completed | tr -d '\r') ]]; do sleep 1; done; input keyevent 82'
$ANDROID_HOME/platform-tools/adb devices
echo "Emulator started"
- Finally, we launch
webpack
server and run the tests
- name: Android Detox
run: yarn start & detox test -c android.emu.debug
In packcage.json we have the following configuration for Android:
"detox": {
"configurations": {
"android.emu.debug": {
"binaryPath": "android/app/build/outputs/apk/debug/app-debug.apk",
"build":
"cd android && ./gradlew assembleDebug assembleAndroidTest -DtestBuildType=debug && cd ..",
"type": "android.emulator",
"name": "emu"
}
},
"test-runner": "jest"
}
Here we specify where our .apk file is placed and tell Detox what is the name of our emulator (it's emu
).
Feel free to use the repo as a reference to set up the tests. If you see where it can be improved I would love to get pull requests and improve the workflows.
Top comments (3)
Great article. Currently i am working on a mono-repo with a team and we are using Travis CI as a build tool.
Thank you for very useful article. I have tried to change build configuration from Release to Debug and it stopped working in Github Actions. Do you have any idea why is it not working?
@martin Cerny, sorry for the late response. Do you have any error message that you are seeing?