
Hi, Habr!
My name is Nikolai Abalov. I work at Badoo’s London office on the Mobile QA Automation team. My colleague
Rajdip Varma talked about how to make Appium tests faster and more reliable. Below is a translation of his article.
')
In recent years, Appium has become one of the most popular automation tools in mobile development. However, along with the advantages, it has some limitations, in particular due to the fact that the test code is completely separated from the application code. In this article, Rajdip tells how you can give your code a super power and solve the most unpleasant automation problems of end-to-end tests for Android.
Problem number 1: capricious application

Imagine that when you run a test in your application, a pop-up message appears (as in the picture on the left). What if the logic that controls the display of the message is not testable to the tester? How in this case to ensure the reliability of the tests? Or, say, you want to click on the moving banner in your application, but the test fails, because the position of the banner has changed by the time Appium could click!
There are many examples of the unpredictability of application behavior during testing, leading to test failures.
Imagine that your tests can tell the application: “I'm testing you now, so turn off the pop-up message asking you to rate the application.”
Problem number 2: Appium does not support everything
In our application there is a feature that allows, by shaking the device, to cancel its refusal from the assessment.
How to automate using Appium shaking on an Android device? There is a Shake endpoint ((POST / session /: session_id / appium / device / shake), but it is not implemented for the UIAutomator2 driver.
Imagine that your tests can tell the application: "Pretend that the device has been shaken."
Solution: let the application help your tests by implementing backdoors
Backdoors - a way to call the methods defined in the code of the Android application from the automation code base, that is, from your tests.
Let's take a look at this with a simple example. Let's say you have a
foo()
method
in Activity, let's say, in the
LoginActivity
class:

In the
foo()
method, we can write code that turns off random pop-up messages, OR add fake shaking functionality OR something that sets the state of your application. Imagine that we can call this method from the automation code base with something like this:

This will solve our problem “But imagine that your tests can tell the application ...”
The bad news: backdoors are not supported in Appium out of the box.
Good news: backdoors can still be implemented in Appium!
Before proceeding to the solution, I will give some real examples of the use of backdoors in automation:
- change backend URL;
- changing the geographic region of the application;
- selection of specific variants of client A / B tests;
- Simulation of the presence of a SIM card for the application;
- getting the ID of the current session;
- receiving from the application data analytics.
PS Doing it all yourself can be difficult. If you are not interested in how I did it, then you can skip to the chapter “Quick start working with backdoors in your project”.Implementation backdoors in Appium
Architecture out of the box server UIAutomator2- APK-1 => appium-uiautomator2-server-debug-androidTest.apk
- APK-2 => appium-uiautomator2-server.apk
APK-1 is an instrumentation package for APK-2. It has a test - instrumentation test. It is started by the command for the adb shell am instrument ... driver. The only purpose of such a test is to start the HTTP server defined in APK-2.
Both APK files are executed in the same process, highlighted in green in the illustration.
When the server is up and running, the client can send JSON-HTTP-requests, and the server will execute them.
But we can get the most out of the Android instrumentation if we change the architecture (as shown below).
Modified architecture of the UIAutomator2 server:
Equipment test application APK AppiumWe poured APK-2 code into APK-1. Therefore, the instrumental test and the HTTP server are now in the same APK file. Let's call it Merged Server APK.
Now you can modify the Manifest.xml of our new APK file so that its target instrumented package becomes the package with the name of our application under test:

This is easy to implement with the help of a Ruby Gem called
appium_instrumenter , which is based on the code from calabash-android gem.
Then we sign the Merged Server APK using the same keystore (keystore) as the application under test. Now we have a custom tool server running in the same process as the application under test (highlighted in green).
We have instrumented our application using Merged Server APK, which means that the latter can access the application context. This means that we can ask the Merged Server APK file to call the methods defined in our application.
Create an endpoint in the Merged Server APK to specify which application method to call:
/wd/hub/session/:sessionId/backdoor.
We can send the name of the method we want to call to this endpoint. Merged Server APK retrieves the application context and calls the method using the Java Reflection API.
You can download all the code for this Appium UIAutomator2 Server backdoor endpoint from the
repository from the 'single_apk' branch.
Next, we replace the APK file that comes with the appium_uiautomator2_driver package with our custom APK file. All the hardest thing behind, these manipulations need to be done only once.
Now in our test automation code there is an auxiliary method for calling backdoor methods. Here is an example in Ruby:

Voila! We got super power! Now it is very easy to call any public method defined in the Application class or the current Activity class:

At the same time, it is supported that the test code returns return values!
Quick start with backdoors in your project
1. Generate the Appium UIAutomator Server APK file for your
applications:
install
appium_instrumenter gem
. Even if your Appium tests are written in Java, you have to do it once, since I wrote the utility only in Ruby.
gem install appium_instrumenter appium_instrumenter instrument app-debug.apk
The folder
./test_servers
will be created in your current directory:
test_servers ├── appium-uiautomator2-server-debug-androidTest.apk └── appium-uiautomator2-server-v0.3.0.apk
Install both of the above mentioned apk files on your device:
adb install test_servers/appium-uiautomator2-server-debug-androidTest.apk adb install test_servers/appium-uiautomator2-server-v0.3.0.apk
2. Now install the NPM package appium_uiautomator2_driver
from my fork:
npm install “rajdeepv/appium-uiautomator2-driver
3. Define the backdoor()
method:
define a method that sends data to the endpoint for the backdoor:
http://localhost:#{APPIUM_FORWARDED_PORT}/wd/hub/session/:sessionId/backdoor
Consider the above example of implementing this method in Ruby. You can implement the same method if you write in Java or any other language.4. May the superpower come with you!
Stay away from problems
"With great strength comes great responsibility."
Backdoor is a very powerful tool. And if used improperly, it can lead to unpleasant consequences. So, if we use the backdoor to completely change the application logic, the risks of testing will be higher than its value.
Backdoors allow you to implement previously impossible scenarios, they expand the possibilities of testing. And in some cases, simply can not do without them!
Here is a story. By the way, on April 1, Rajdip
spoke at the CodeFest in Novosibirsk. A video of his report should appear in the summer on the CodeFest YouTube channel and on Badoo Tech.