11min read

How to Apply UI Test Automation in React Native Apps?

React Native has changed the way we think about the mobile apps development process but didn’t change the main goal of software development teams—delivering the best quality products as soon as possible. However, it definitely comes with its own set of challenges every developer needs to conquer. I will do my best to outline the most import ones that come with React Native testing, along with some tips on how to solve them. I have also prepared a short guide to React Native with Detox (in my opinion the best hybrid testing framework) and how to set it up for Continuous Integration systems.

Challenges for React Native UI testing

More complex setup

For React Native debug apps the entire JavaScript code is bundled via a React Native packager. It means that we have to take care of launching and terminating the packager too. Some frameworks often require running their own services in the background—e.g Appium depends on Appium Server, which makes test setup quite complicated.

In one of the frameworks I’ve evaluated, React Native Detox code that is used for UI automation should be placed both in Javascript files and platform-specific files. It leads to the configuration mess when you have to handle npm / yarn dependencies and resolve conflicts in platform-specific libraries.

Check if React Native
is the right tech for your project

View elements recognition

The main feature of React Native is that all views are generated automatically for both platforms using JavaScript code. That’s convenient, but if we decide to write some test scripts using native frameworks, view hierarchy inspected on iOS and Android can surprisingly differ…

For example, let’s consider a text view from a sample app defined like:

accessible= {true}
testID= {"ButtonText"}
accessibilityLabel= {"ButtonTextDesc"}
{!this.state.clicked? defaultButtonText: "Polidea"}
view raw CustomButton.js hosted with ❤ by GitHub

As on the screenshots below, for iOS testID is interpreted as a React Native accessibility identifier but for Android, it’s missing. Hopefully, accessibilityLabel and the text are interpreted almost the same for both platforms.



Platform-specific issues


The first thing you will surely come up against is dealing with app permissions. Make sure that each test can be launched separately and that a permission pop up will not ruin the execution. For native scripts, there are a lot of plugins and ways to sort out this problem. Some hybrid frameworks can also take care of this (warning: Cavy doesn’t!) so don’t be afraid to check them out.

Shared state

Some app states and local databases are sometimes shared between separated tests. This problem really affects everyone who works on UI testing automation. In order to reduce the risk of shared app states between tests, you can use platform specific approaches—e.g Android Test Orchestrator (described in more detail in one of our blog posts) or implement custom methods that clear cache and memory files or remove any kind of data. React Native dedicated frameworks that I’ve checked (Cavy, Detox) also cover that, together with implemented cleanup methods.


Native frameworks

Native approaches give more white-box solutions for common problems. What’s also really helpful, is that the open source community gives more tools and frameworks that make testing easier, e.g. mocking tools, simulator hacks. Native frameworks make deeper integration with the app possible. By adding extra identifiers to view elements, you can make all of them as accessible as the native apps.


Judging by popularity, speed and entry level, I recommend using Espresso (for a single-package testing) and UIAutomator (for a multiple-package testing). As I mentioned before, React Native doesn’t support setting up resource-id attribute for view elements from JavaScript code (only by setting it up directly in R.xml file). It’s a problem as matching by resource-id is a standard practice when using Espresso and using it with React Native means limiting yourself to using only a text and content-description selectors.


There are few ways of doing native iOS automation. You can use XCUITest if you are looking for a low-cost setup solution (provided directly from Xcode), but depending on your needs, you can also use more powerful, mature frameworks that are based on XCUITest.

Google also remembers about iOS testers and released the EarlGrey framework—a tool similar to Espresso but running on iOS. Fast, reliable and powerful tool deeply integrated within the app source code that allows many users actions.

Cross-platform frameworks

There are ways to avoid writing a code for each platform that we develop—just like we do by using React Native.

I did a research on React Native dedicated tools and it turns out there are plenty of frameworks that allow for writing one code for both platforms:

I’ve decided not to write more about Appium because of its very poor performance and complex setup. Another framework I considered was Cavy. All Cavy matchers are based on custom testHook identifiers (cannot refer to text values), which makes Cavy very fast but they need to be put in each view element. What’s more, Cavy affects the index file and it’s a big change when it comes to apps. Finally, I discovered Detox—automation framework for React Native and Native applications. Detox combines EarlGrey and Espresso—native automation stack for iOS and Android. Native stack makes Detox very fast, powerful and easy for CI integration.

I’ve prepared a simple guide on how to use Detox with CI server (Gitlab CI).

Test automation guide with Detox

I’ve created a very primitive app, which transforms a button into the Polidea logo. It was simple, but enough for this guide’s purpose.

I’ve created a sample test class where I’ve defined 4 test methods which use Detox API and access views by text and ID. Notice how easily you can check the appearing views.

describe('Example', () => {
beforeEach(async () => {
await device.reloadReactNative();
await waitFor(element('ButtonText'))).toBeVisible().withTimeout(10000);
it('should Press me text be displayed', async () => {
await waitFor(element(by.text("Press Me"))).toBeVisible().withTimeout(100);
it('should ButtonText id be displayed', async () => {
await expect(element('ButtonText'))).toBeVisible();
it('should Polidea logo be displayed after click on button', async () => {
await element('ButtonText')).tap();
await waitFor(element('ButtonImage'))).toBeVisible().withTimeout(2000);
it ('should Press me text be morphed in Polidea title after click on button',async () =>{
await element('ButtonText')).tap();
await waitFor(element(by.text("Press Me"))).toNotExist().withTimeout(2000);
await waitFor(element(by.text("Polidea"))).toBeVisible().withTimeout(100);
view raw firstTest.spec.js hosted with ❤ by GitHub

I’ve created a configuration for running Detox both on Android and iOS as below:

"detox": {
"configurations": {
"ios.sim.debug": {
"binaryPath": "ios/build/Build/Products/Debug-iphonesimulator/",
"build": "xcodebuild -project ios/PolideaSample.xcodeproj -scheme PolideaSample -configuration Debug -sdk iphonesimulator -derivedDataPath ios/build",
"type": "ios.simulator",
"name": "iPhone 6s Plus"
"android.emu.debug": {
"binaryPath": "android/app/build/outputs/apk/debug/app-debug.apk",
"cd android && ./gradlew assembleRelease assembleAndroidTest -DtestBuildType=debug && cd ..",
"type": "android.emulator",
"name": "Pixel_API_26"
view raw package.json hosted with ❤ by GitHub

React Native app with tests configured as above can be built by using these commands:

detox build -c ios.sim.debug & detox build -c android.emu.debug

For running those tests on Android and iOS just execute:

detox test -c ios.sim.debug & detox test -c android.emu.debug

As you can see, running Detox tests is very simple and can be easily moved to the CI system. Let’s take a look at a plan for CI integration—all we need is to include the following steps inside our test job:

  • Install missing project dependencies and Detox
  • Launch React Native packager
  • Build an app using Detox
  • Run Detox tests
  • Terminate the packager

You can check it on my simplified (no caching, no separate steps) iOS and Android snippet for Gitlab CI setup:

- build
- test
- deploy
- (if [ "$(lsof -n -i4TCP:2137)" != "" ]; then kill -9 $(lsof -n -i4TCP:2137); else echo "Cleaned"; exit 33; fi);
stage: test
- brew tap wix/brew
- brew install --HEAD applesimutils
- npm install -g detox-cli
- npm install -g react-native-cli
- npm install
- react-native start --port 2137 &
- detox build -c ios.sim.debug
- detox test -c ios.sim.debug
- kill -9 $(lsof -n -i4TCP:2137)
- xcode-9.2
stage: test
- name: android-emulator:latest
alias: pixel
entrypoint: ["/", "android-23", "x86", "pixel"]
- adb connect pixel:5555; sh ./scripts/ pixel
- mkdir -p ./detox_node/
- npm install --prefix ./detox_node/ -g detox-cli
- npm install --prefix ./detox_node/ -g react-native-cli
- npm install
- ./detox_node/bin/react-native start --port 2137 &
- ./detox_node/bin/detox build -c android.emu.debug
- ./detox_node/bin/detox test -c android.emu.debug
- kill -9 $(lsof -n -i4TCP:2137)
- android-emu
view raw .gitlab-ci.yml hosted with ❤ by GitHub

As the result for detox_test:iOS you’ll get the output:



In my article, I’ve presented approaches for React Native UI test automation and explained how easily it can be achieved with a Detox framework. The solution I’ve provided is dedicated for Gitlab CI that is used by Polidea for Continuous Integration, but you can easily adapt those steps to your needs. You can find my sample project on Polidea github.


AdamTest Engineer


Sign in and expect sharp insights, recommendations, ebooks and fascinating project stories delivered to your inbox

The controller of the personal data that you are about to provide in the above form will be Polidea sp. z o.o. with its registered office in Warsaw at ul. Przeskok 2, 00-032 Warsaw, KRS number: 0000330954, tel.: 0048 795 536 436, email: (“Polidea”). We will process your personal data based on our legitimate interest and/or your consent. Providing your personal data is not obligatory, but necessary for Polidea to respond to you in relation to your question and/or request. If you gave us consent to call you on the telephone, you may revoke the consent at any time by contacting Polidea via telephone or email. You can find detailed information about the processing of your personal data in relation to the above contact form, including your rights relating to the processing, HERE.

Data controller:

The controller of your personal data is Polidea sp. z o.o. with its registered office in Warsaw at ul. Przeskok 2, 00-032 Warsaw, KRS number: 0000330954, tel.: [0048795536436], email: [] (“Polidea”)

Purpose and legal bases for processing:


Used abbreviations:

GDPR – Regulation (EU) 2016/679 of the European Parliament and of the Council of 27 April 2016
on the protection of natural persons with regard to the processing of personal data and on the free movement
of such data, and repealing Directive 95/46/EC (General Data Protection Regulation)

ARES – Polish Act on Rendering Electronic Services dated 18 July 2002

TL – Polish Telecommunications Law dated 16 July 2004

1)        sending to the given email address a newsletter including information on Polidea’s new projects, products, services, organised events and/or general insights from the mobile app business world |art. 6.1 a) GDPR, art. 10.2 ARES and art. 172.1 TL (upon your consent)

Personal data:name, email address

2)       statistical, analytical and reporting purposes |art. 6. 1 f) GDPR (based on legitimate interests pursued by Polidea, consisting in analysing the way our services are used and adjusting them to our clients’ needs, as well as developing new services)

Personal data:name, email address

Withdrawal of consent:

You may withdraw your consent to process your personal data at any time.

Withdrawal of the consent is possible solely in the scope of processing performed based on the consent. Polidea is authorised to process your personal data after you withdraw your consent if it has another legal basis for the processing, for the purposes covered by that legal basis.

Categories of recipients:

Your personal data may be shared with:

1)       authorised employees and/or contractors of Polidea

2)       persons or entities providing particular services to Polidea (accounting, legal, IT, marketing and advertising services) – in the scope required for those persons or entities to provide those services to Polidea


Retention period:

1)       For the purpose of sending newsletter to the given email address – for as long as the relevant consent is not withdrawn

2)       For statistical, analytical and reporting purposes – for as long as the relevant consent is not withdrawn

Your rights:


Used abbreviation:

GDPR – Regulation (EU) 2016/679 of the European Parliament and of the Council of 27 April 2016
on the protection of natural persons with regard to the processing of personal data and on the free movement
of such data, and repealing Directive 95/46/EC (General Data Protection Regulation)

According to GDPR, you have the following rights relating to the processing of your personal data, exercised by contacting Polidea via [e-mail, phone].

1)       to access to your personal data (art. 15 GDPR) by requesting sharing and/or sending a copy of all your personal data processed by Polidea

2)       to request rectification of inaccurate personal data
(art. 16 GDPR) by indicating the data requiring rectification

3)       to request erasure of your persona data (art. 17 GDPR); Polidea has the rights to refuse erasing the personal data in specific circumstances provided by law

4)       to request restriction of processing of your personal data (art. 18 GDPR) by indicating the data which should be restricted

5)       to move your personal data (art. 20 GDPR) by requesting preparation and transfer by Polidea of the personal data that you provided to Polidea to you or another controller in a structured, commonly used machine-readable format

6)       to object to processing your personal data conducted based on art. 6.1 e) or f) GDPR, on grounds relating to your particular situation (art. 21 GDPR)

7)       to lodge a complaint with a supervisory authority,
in particular in the EU member state of your habitual residence, place of work or place of the alleged infringement if you consider that the processing
of personal data relating to you infringes the GDPR
(art. 77.1 GDPR)

No obligation to provide data:

Providing your personal data is not obligatory, but necessary for Polidea to provide you the newsletter service

Refusal to provide the above data will result in inability to receive the newsletter service.


In the process of providing the newsletter service, we make decisions in an automated way, including profiling, based on the data you provide.


“Profiling” means automated processing of personal data consisting of the use of your personal data to evaluate certain personal aspects relating to you, in particular to analyze or predict aspects concerning your personal preferences and interests.


The automated decisions are taken based on the analysis of clicked and viewed content. They affect the targeting of specific newsletter content to selected users registered to receive the newsletter service, based on the anticipated interests of the recipient.