End to End (e2e) testing is a technique that helps ensure the quality of mobile applications in an environment as close to real life as possible, testing the continuous integration of all the pieces that integrate a software automatically. On a mobile app, this could be particularly useful given the diversity of real devices and platforms our software is running on top of.
Due to the cross-platform nature of React Native, e2e testing proves to be particularly messy to work on. As a result, we have to write all of our tests bearing this in mind, changing the way we access to certain properties or query elements no matter the tool we use for connecting to it. Still, automation testing tools like Appium and WebdriverIO allow us to work over a common and somewhat standard interface.
The following instructions assume we already have React applications built with expo, and use Jest for our unit-testing solution.
Disclaimer: The following instructions are based on a Windows machine running an android emulator. output/commands may vary slightly on different architectures.
Setting Up Appium
- Install required dependencies
$ npm i -D webdriverio babel-plugin-jsx-remove-data-test-id concurently
WebdriverIO will work as our “client” for the appium server in the case of JS. There is more to come regarding how to use other clients such as python.
babel-plugin-jsx-remove-data-test-id
will help us remove unwanted accessibilityLabels from our mobile app, since that’s the preferred way of targeting elements for both IOS and Android platforms
concurrently
will help us automate the running of appium server and jest to do our e2e tests
- Install Appium Doctor
$ npm install appium-doctor -g
This will help us identify if we have all of the needed dependencies to correctly run appium in an emulator.
- Run Appium Doctor
Depending on the host OS we want to test in, we could run:
$ appium-doctor --android
or
$ appium-doctor --ios
For this particular case I’ll be running the android version. This will prompt some output on the console. If we have all the required dependencies installed we should see a message similar to the following
If not all necessary dependencies are met at this point, instead of checkmarks before any given item you’ll see a red X symbol. Check the end of the input for more information on how to fix the particular Issues you’re prompted.
We’re not going to fix the optional requirements that appium-doctor prompts for the time being, feel free to go over those once you have the testing solution working.
- Run Appium
By this point, you should be able to run your appium server without any issues, in order to do so just type
$ appium
You should see something similar to
If you do so, congrats! you have correctly set up appium.
Now, let’s set up our tests.
Write tests once, run in any platform
One of the key features of React Native is its ability to write code once and run it in both iOS and Android, that is what we want our mobile tests to behave in the same way. There are some limitations for this, since the only way we can write a selector for both platforms is through the accessibilityLabel attribute in React Native.
This may become an issue if your mobile app depends on accessibility features. Make sure to use correct, semantic and descriptive accessibility labels at any place you intend to use them.
If a great accessibility is not on the scope of your current project (it should), you can use accessibilityLabel as a perfect target for querying your elements, just make sure you don’t accidentally worsen the experience of people using screen readers or any other assistive technology.
In order to do this, we’re going to configure our babel setup to remove the accessibility labels whenever we build for production:
/// babel.config.js
module.exports = function() {
return {
presets: ['babel-preset-expo'],
env: {
production: {
plugins: [
[
'babel-plugin-jsx-remove-data-test-id',
{ attributes: 'accessibilityLabel' },
],
],
},
},
};
};
Let’s write our first test script now:
I’ve created a called LoginTest.spec.js
inside a new folder called e2e
. Inside the file you can find the following:
// myapp/e2e/LoginTest.spec.js
import wdio from 'webdriverio';
jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000;
const opts = {
path: '/wd/hub/',
port: 4723,
capabilities: {
platformName: 'android',
deviceName: 'emulator-5554',
app: 'my-app-name.apk',
automationName: 'UiAutomator2',
},
};
describe('Expo test example', function() {
let client;
beforeAll(async function() {
client = await wdio.remote(opts);
await client.pause(3000);
const pack = await client.getCurrentPackage();
const activity = await client.getCurrentActivity();
await client.closeApp();
await client.startActivity(pack, activity); //Reload to force update
await client.pause(3000);
});
afterAll(async function() {
await client.deleteSession();
});
it('should allow us to input username', async function() {
// Arrange
const field = await client.$('~username');
const visible = await field.isDisplayed();
// Act
await field.addValue('testUsername');
// Assert
expect(visible).toBeTruthy();
expect(await field.getText()).toEqual('testUsername');
});
});
That may be a lot of new code to digest at once, so let’s go line by line:
import wdio from 'webdriverio';
First, we import the WebdriverIO client. This is the main package that will include the functionality we need to query elements from the react app and simulate events on the emulator.
jasmine.DEFAULT_TIMEOUT_INTERVAL = 60000;
This will tell our test runner (in this case jest) to make the tests error after a certain number of ms have passed. Here we’re setting it explicitly in the test, but if you’re using jest you can modify the testTimeout property on your jest configuration. If you’re using any other test runner, I’d recommend going through their documentation, most of them have a similar property.
const opts = {
path: '/wd/hub/',
port: 4723,
capabilities: {
platformName: 'android',
deviceName: 'emulator-5554',
app: 'my-app-name.apk',
automationName: 'UiAutomator2',
},
};
These are the configurations for our driver to know what to look for when using the appium interface to query and save elements.
You can get the device name going on your emulator > help > about
In order to generate an app from expo, you have to run the command:
expo build:android
And wait in the queue for it to build.
In this case, I placed the downloaded apk in the root folder for my project, and renamed it my-app-name.apk.
Since we’re using WebdriverIO, the automationName will be UiAutomator2, as that’s how appium recognizes it.
Since lines 18-33 are mostly about setup, we won’t focus on that for now. The next part focuses on line 34 and forward.
Writing the actual test
The idea of this test is just to showcase a normal flow on a test, therefore we will be dealing with a fairly simple use case: Checking that we have a valid username input:
const field = await client.$('~username');
const visible = await field.isDisplayed();
The first line allows us to query an item by accesibilityLabel. As I have previously mentioned, for more information about specific selectors go to the WebdriverIO documentation.
The second line checks whether our previously selected item is visible on the current screen, more information here.
await field.addValue('testUsername');
This line simulates user typing into the selected field. In this case, we’re inserting the ‘testUsername’ text inside the previously selected username field:
expect(visible).toBeTruthy();
expect(await field.getText()).toEqual('testUsername');
Lastly, we use Jest to check that the field is indeed visible on our Login Screen, and that the text on the given username field is the same as the one we wrote in it.
Running the test
Since we’re using Jest as our test runner on our React Native app, I’ve set up a command on my package.json to run the appium server and to run Jest in watch mode at the same time. It looks like this:
Here we’re using concurrently
, a simple npm package that allows us to run several npm scripts at the same time. In this case we run the appium server and jest in watch mode, add their names and different colors to easily recognize them in the console, and pass the standard input to the jest command. This way we can narrow down our tests or do things like run coverage reports.
With this done, we simply have to run npm run test:e2e on our console, and expect something like this:
to be run, and something like this:
to be the output. If so, congratulations, you’ve correctly set up your integration tests for your react native app.
Wrapping up
While we’re far away from calling it a day on our e2e react app testing solution, the main automation testing setup it’s done. Next steps include integrating it with a CI/CD pipeline and making it work on IOS platforms.
Further Reading
https://webdriver.io/
https://discuss.appium.io/
http://appium.io/