Mobile e2e tests using WebdriverIO and Appium
Tags: appium, automation, ios, testing
Published: 2021-02-28 17:16:44

# Context
So this is new for me. I normally write posts explaining things I've been doing in a daily basis for many years. However, I started working in a new project something like a month ago.
This post is about end-to-end tests. In the team I'm now taking part of, everyone is responsible for writing e2e tests for iOS/Android/Web. I've never done that before, I have to confess. I've done Unit tests many many times, but I've never written so many UI tests.
[Appium](http://appium.io/), [WebdriverIO](https://webdriver.io/) and [Jasmine](https://jasmine.github.io/) are the tools this team has chosen to write these tests, and for good reasons. Let's first distinguish briefly among the different types of automated tests. Particularly between e2e and unit tests.

## Unit tests
These tests are responsible for validating that a single unit is working properly. You can think of a unit as a class or function. These tests are written in an isolated fashion. I mean, if the rest of the system is full of bugs and nothing else work, if this *unit* work, the test will pass. They are also repeatable. They don't depend on anything else, really. Anytime you run the test, if the code hasn't changed, the test will report the same result.
These tests are intimately related to the code quality of your project. If your code is clean, these tests should be relatively easy to write. When writing unit tests in iOS, you usually use `XCTest` or [`Quick`](https://github.com/Quick/Quick)

## End to end tests
These tests are based on a user's perspective. You put in the shoes of the final user and start the app, and tap on the buttons, and fill in textfields, everything a final user would do when using your app. You test flows and everything in a real world scenario. 
These are the tests we will be talking about in this article. Xcode introduces a framework for writing e2e tests (also known as UI tests), called `XCUITest`.

## Why not XCUITest?
`XCUITest` is a Xcode-only framework. I could write tests in `XCUITest` and run them to validate my app is working as intented, but I would need to also write tests, maybe in [`Espresso`](https://developer.android.com/studio/test/espresso-test-recorder) to test the Android side. If I also have a web frontend, I'd need to use [Selenium Webdriver](https://www.selenium.dev/) or any other library to write test for that side of the app.
That's why `Appium` and `WebdriverIO` come handy. They abstract the differences among the different e2e testing libraries, allowing us to write all the test in Javascript (a very well known language), and only writing platform-specific code when needed.

## Appium and WebdriverIO
Here is where the things became a bit cloudy for me at the beginning so I'll be adding links along the process. `Appium` is basically a service that connects a testing process with a device. It installs an application in the device, called `Webdriveragent`, and then communicates with the device using HTTP calls. So `Appium` is the service that allows the communication in the first place.
`WebdriverIO` is the framework itself that we use to write tests for the different platforms. We write code for `WebdriverIO` and then `Appium` executes those tests in the devices specified in the configuration.

![appium-webdriverio-arch](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/m5uya9lt495eehf7oc0l.png)
 
# An example
Well, enough theory for now. I've prepared a quick, small example so you can understand hands-on how all of this work.
So, go and clone/download the code in [this repo](https://github.com/fmo91/ToBeAutomated).
This is the simplest app you could think of. It's a Counter app, that has a label in the center of the screen, a button to "add" and a button to "sub". As simple as that.
A think that could draw your attention is this fragment:

```swift
valueLabel.accessibilityIdentifier = "value_label"
addButton.accessibilityIdentifier = "add_button"
subButton.accessibilityIdentifier = "sub_button"
```

We're setting `accessibilityIdentifier` to all of these views. Although not needed, it's convenient when it comes to automation.

## The Appium inspector

Now, download Appium Desktop from [this link](https://github.com/appium/appium-desktop/releases/download/v1.20.2/Appium-mac-1.20.2.dmg).

Appium Desktop is a very convenient app that can launch an Appium server, install and connect to the app running in your device and simulator and inspect it. It's not absolutely needed, but it's convenient.
Once you've downloaded it, you can go and launch it.

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/z8ks6gn510lslmdqz647.png)

Click on **Start Server**  with the default arguments. It will start the Appium server in the default host and port `http://localhost:4723`.

Then, click on **Start Inspector Session** (the small magnifying glass icon in the top right corner).

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/f6k3eg7tdt8menp0posh.png)

It will open a new window where you can set the "Desired Capabilities" for the test session. The Desired Capabilities documentation is in [this link](https://appium.io/docs/en/writing-running-appium/caps/). They are basically the configuration that says in which device the tests would need to run, which platform is it, the testing framework (XCUITest in our case), etc.
Here is a basic example of it, you can replace the empty JSON for this:
```json
{
  "platformVersion": "14.4",
  "autoGrantPermissions": "true",
  "udid": "AD7D6FF7-9B4C-4AB5-AD63-39736DA357F6",
  "automationName": "XCUITest",
  "deviceName": "iPod touch (7th generation)",
  "bundleId": "fmo.Testable",
  "platformName": "iOS",
  "app": "/Users/macbook/Developer/pruebas/appium_nodejs/__test__/app/Testable.app"
}
```

Of course you will need to replace the "app" path (more on this later).
For the `udid` and `deviceName`, open a terminal and run this:

`xcrun xctrace list devices`

You'll be provided with a list of devices on which you can run your tests

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ismfjw88djn9ufzqhaxd.png)

That's my list. In my case, I just focused on running the tests on an iPod Touch Simulator:

`iPod touch (7th generation) (14.4) (AD7D6FF7-9B4C-4AB5-AD63-39736DA357F6)`

Where `AD7D6FF7-9B4C-4AB5-AD63-39736DA357F6` is the `udid`, `iPod touch (7th generation)` is the `deviceName`, and `14.4` is the `platformVersion`.
The `bundleId` is the bundle identifier of your iOS project.
So, what is that `app` thing? Well, it's the `.app` file that is the product, result of building your project. If you build (Cmd + B) the project I've linked above using Xcode, you'll end by having a `.app` file in the `products` group, see:

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0wfbey6ofrv3dgo1380t.png)

You need to point to that file.

Now, open an iPod Touch or the simulator you would like to run your tests on, and start the session in the Appium Desktop app.

This should install the `WebDriverAgent` app in the simulator and install the `.app` in the simulator. In the Appium Desktop app, you should be able to see the app running in your simulator and you should be able to inspect what's being rendered. You are also able to record tests using this visual environment, but that isn't what we're doing in this example.

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fg431k90fuit6put3wud.png)

## WebdriverIO configuration
 
If you were able to follow up until now, well done! You've just run Appium for the first time, and inspected your app in the Appium Inspector. Now, stop the Appium Desktop server, and let's start with the actual tests. 
Before going on, you'll need to install [nodejs](https://nodejs.org/en/), preferably by using [nvm](https://github.com/nvm-sh/nvm), although that's out of the scope for this tutorial.
Once you've that installed, let's create a new folder and open the terminal inside it.
We need to start a new Nodejs app, so I'll run `npm init -y` to accept the default arguments.

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jcq1m1xe8fru2dca7frd.png)

Then, let's install the dependencies we'll need: `npm i --save-dev @wdio/cli appium`

We'll use the `@wdio/cli` package to run the configure the project. So, after the npm install finishes, let's run the wdio (WebdriverIO) cli: `npx wdio config`

I'll run the tests on my local machine:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ytqedmrhdd0n45nfu5nn.png)
 
Using [Jasmine](https://jasmine.github.io/):
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7w2oys82d1db333jvi6j.png)
 
Running the automation commands synchronously:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bhp8ydm39mz0wbp72kff.png)

Without any compiler step:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0ts7s72eu1fu2ox3jnw1.png)

Accepting the default location for the tests:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/isyumc7ck3c94rgio8dv.png)

I'll let WebdriverIO to create some example tests for me:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/u6mnpoc281q6a7b8jsjw.png)
    
I will be using Page objects! In case you didn't know this pattern, I'll absolutely recommend you to go and read Martin Fowler's [article](https://martinfowler.com/bliki/PageObject.html):
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/jfg76r5bccl3912zf0o3.png)
 
Using the default location for the page objects:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d37xenvaggq59p6itxah.png)
 
Using the Spec reporter:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p6ynnjudg0kun9qrddyt.png)

Selecting Appium as the service for my test setup:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/8v17n5lobimbwknlx3c9.png)

And using the default base url:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kql2gcci1du2cggttseh.png)

Just that. WebdriverIO will install some packages:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mzq36g7b7muyej7r9hii.png)

And we'll be ready to go. 

## Project Configuration

Now, If you open the generated project using Visual Studio Code: `code .`

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/otq6c14yki2w9zldcr2b.png)

There are some important things to notice:

- Inside the `test` folder, you have `pageobjects` and `specs`.
- As said, Page Objects are a pattern described by Martin Fowler. They are basically, objects that will let us access to the visual components in our app, and interact with them from inside our actual tests. **You don't assert from these objects!** This is important!
- The specs are the actual tests. Here you can ask your page objects to do the interactions and then you put the assertions inside the specs to validate if the app is working as intended.
- `wdio.conf.js` is the configuration file for your tests. Let's enter on this file.

There are a looot of configurations you can modify on the `wdio.conf.js` file, and they are well documented!

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l7jq0b8l4vnv9bstuafu.png)

But what we need to change first is the `capabilities` key: 
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/qxmsy0vpc1j6xtmg6f9r.png)

This is sooo web. Let's remove this configuration and replace it with the JSON configuration we used in Appium Desktop. But first let's add the `.app` file in a folder inside our new project, and replace the path in the `app` key in `wdio.conf.js`.
So this is how the `capabilities` key will look like:
![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/69yc295rubxch0mcidlu.png)

## The actual test
Finally! This has been a lot of configuration steps and theory but at the end, we're ready to write our actual tests.
First, let's remove our existing Page Object, and create a new Page Object in a file called `counter.page.js`.

```javascript
class CounterPage {
    get subButton() { return $("~sub_button"); }
    get addButton() { return $("~add_button"); }
    get valueLabel() { return $("~value_label"); }
    get value() { return this.valueLabel.getText(); }

    sum() {
        this.addButton.click();
    }

    sub() {
        this.subButton.click();
    }
}

module.exports = new CounterPage();
```

The `CounterPage` class is basically a couple of getters, for different views and values, and some functions to interact with those views.

That `$(...)` jQuery-like selector is what we use to access to the elements in the screen. If we write `$("~SomeIdentifier")`, we're searching for an element in the screen with the `accessibilityIdentifier` attribute with value `"SomeIdentifier"`, and that's exactly what we've done in our project. You can check for other ways to access these views using the Appium Desktop Inspector and checking [the official documentation](https://appium.io/docs/en/commands/element/find-elements/).

The `.click()` function is self-explanatory, but we've a lot of different ways to interact with our views.

Now, let's remove the example spec and write a new one called `counter.e2e.js`, with this content:

```javascript
const CounterPage = require('../pageobjects/counter.page');

describe('The counter screen', () => {
    it('should sum correctly', () => {
        CounterPage.sum();
        CounterPage.sum();
        CounterPage.sub();
        CounterPage.sum();

        expect(CounterPage.value).toBe("2");
    });
});
```

This is also self-explanatory if you've written js tests in the past. If not, this is similar to also the `Quick` framework in Swift. What we're doing is creating a test suite called `The counter screen` where we have a single test case called `should sum correctly`.

Inside this test case, we'll ask the `CounterPage` object to click on the "sum" button twice, then on the "sub" button and a last time in the "sum" button again. At the end, if we summed three and subbed one, the value label should have the text "2".

Let's run this using `wdio`:

`npx wdio wdio.conf.js`

This will start the app in your simulator and run the commands one by one, and finally show a report in the console:

![image](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/dsdbf7v3dwsjc2gx267c.png) 

# To sum up

This has been longer than what I initially thought. And again, I'm not an expert at all, I'm just describing what I've learned this week, and it blowed my mind, to be honest. I think it's fascinating and really interesting and I wanted to share what I know with you. If you have any suggestion or any question or comment, please ask them in the comments section, I'll be pleased to answer what I know (or researching with you).

# Interesting links
Apart from the official docs I've linked along the article, I'd really recommend you to go and check the courses in [the Test Automation University](https://testautomationu.applitools.com/). These folks have really useful content. My favorites so far are:

- [Mobile Automation with Appium in JavaScript](https://testautomationu.applitools.com/appium-javascript-tutorial/)
- [UI Automation with WebdriverIO]
(https://testautomationu.applitools.com/webdriverio-tutorial/)