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, WebdriverIO and Jasmine 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
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
to test the Android side. If I also have a web frontend, I'd need to use Selenium Webdriver 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.
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.
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:
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.
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.
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).
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. 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:
{
"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
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:
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.
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, preferably by using 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.
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:
Using Jasmine:
Running the automation commands synchronously:
Accepting the default location for the tests:
I'll let WebdriverIO to create some example tests for me:
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:
Using the default location for the page objects:
Selecting Appium as the service for my test setup:
And using the default base url:
Just that. WebdriverIO will install some packages:
And we'll be ready to go.
Project Configuration
Now, If you open the generated project using Visual Studio Code: code .
There are some important things to notice:
- Inside the
test
folder, you havepageobjects
andspecs
. - 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!
But what we need to change first is the capabilities
key:
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:
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
.
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.
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:
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:
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. These folks have really useful content. My favorites so far are:
Top comments (4)
Thanks for the wonderful write up. I am facing issue while I bring up appium service and automatically starting the webdriver agent on the iPad. Do you have any troubleshooting steps handy?
Right now, I am working that around by starting the appium server in the background and webdriver agent manually on the iPad, then passing on the webdriver agent URL in the capabilities.
In this blog, I want to include good information on how to incorporate automated tests for React native apps using Appium and WebdriverIO with a Node.js framework. fall out of love spell
can we achieve cross platform test with WebdriverIo and Appium.
like single test can run on both IOS and Andriod(Assuming both have same accessibilityID's)
Can you help me configuration wdio with iOS. The configuration above did not run in simulator iOS in my machine.