E2E Testing React Native with Detox + Screenshots

Carlo
Async
Published in
4 min readMar 14, 2018

Note: This tutorial uses create-react-native-app and Expo, but the approach will work on any react-native app.

Wouldn’t it be nice to run your app through its paces without having to manually tap through all the screens? After creating a new feature or fixing a bug, we usually install the app on our device and test it out manually. But this process can get out of hand when we want to verify all our features, new and old, before tagging a release.

This is the dream that Detox provides, a Selenium like interface for testing React Native apps. With Detox’s focus on synchronization (we’ll see an example of this later), it lets you write cleaner and less flaky specs. Other tools that rely on naive solutions like adding sleep(2000) to wait for a request to the backend to finish often break on slower CI machines or networks. But with Detox, along with stuff that Google developed (EarlGrey for iOS and Espresso for Android) under the hood, it automatically waits not only for requests to finish, but also animations, timers, and more.

Sample app with Detox

So let’s see how writing Detox specs looks like with a sample app. This sample app is nothing more than a create-react-native-app that has a button and pressing that button five times will show a cool fact about cats. Also, we’ll add testID props to any component we’d like to interact with our tests.

App.js

And here are the specs. Aside from the normal setup from the Detox docs, we can use detox-expo-helpers to have Detox play nicely with this CRNA app.

The first test checks the initial state of the app and makes sure that a component with testId="button-tap" is visible.

The second one tests that the number of taps will be shown on the first and third tap.

And the third one tests that a cat fact shows up on the fifth tap. Note that the cat fact comes from the internet and we’re not waiting for the request to finish here. We simply expect that the element is visible and Detox will wait for the request and the resulting render to finish before making the expectation.

So let’s pull the sample repo and see it in action.

git clone git@github.com:cjcaj/detox-sample.git
cd detox-sample
# Install our dependencies
npm install
npm install -g detox-cli
brew update
brew tap wix/brew
brew install --HEAD applesimutils

# Run the packager and get a build for detox to use
npm run ios

# In a new window, copy over the build when you see the app running
cp -R ~/.expo/ios-simulator-app-cache/Exponent-2.3.0.app e2e/

# Run the tests
detox test

And once the tests start with detox test , Detox will start up a simulator, install the app, and run the tests. Here is what the output looks like:

Well, what happens when the automatic synchronization doesn’t work?
As good as Detox is, it won’t cover all the cases synchronizing with the app and you might be stuck with a flaky test or a test that just never passes. Here are some escape hatches that Detox provides.

waitFor can poll for a certain element to appear or disappear. So for example:

await waitFor(element(by.id('text-fact')))
.toBeVisible()
.withTimeout(2000)
await expect(element(by.id('text-fact')))
.toBeVisible()

This will poll for the text-fact component to be visible for 2 seconds then move on to the expect statement. It’s important that we run expectations afterwards since waitFor won’t throw if it never found the element we were looking for.

We can also tell Detox to forget about automatic synchronization altogether with device.disableSynchronization. This is useful when there are looping animations (which Detox will wait for to finish…) or when we just want to do things the old fashioned way. You can also enable it again with device.enableSynchronization to get things back to how they were.

Bonus: Screenshots
If you looked at the spec file and the output logs closely, you’ll notice that we’ve been taking screenshots of the app. This is helpful when someone wants to spot check the app or debug specs when they fail in a CI. In the afterEach hook of our spec, we’re calling takeScreenshot where we use Xcode’s xcrun to grab a screenshot and save it to /tmp/screenshots. We’ve also added a timeout for execSync just in case taking the screenshot hangs on us.

Screenshots

Links:
You can find the detox-sample repo here: https://github.com/cjcaj/detox-sample
The Detox repo: https://github.com/wix/detox
And the helper for getting Detox running with CRNA apps: https://github.com/expo/detox-expo-helpers

Async builds high performance, reliable, and cost-effective applications by combining technical expertise and deep knowledge of industry trends.

For more information on development services, visit asy.nc

--

--