Appium Backdoors Testing Technique with Espresso Driver

Lana Begunova
7 min readJan 19, 2024

--

Appium lets us use modern testing technologies, like Espresso, for White Box testing. With Appium’s Espresso Driver, we can interact with app’s internal methods from within the automation context.

Application Backdoors

Let’s discuss one of the strategies available for quickly setting up the app state. One such strategy is actually directly calling methods in our app source code from Appium. Since this technique is not available to users, we call it backdooring because we’re getting into the app from the backdoor per se. The idea is basically that our application can expose application-internal methods to the outside world. These would be methods that are useful for testing and setting up state in our app.

This backdoor technique is rather compelling, even though there’s not really an easy way to enable it within Appium today. Our test script would access these backdoors whenever it needs to set up state. At the moment this would have to be some kind of custom RPC channel between our test script and app. Let’s take a look at how it works.

The basic notion here is that we may have methods inside our app which are purely internal bits of code. Some of these bits of code might do useful things like adding a user or performing another action that could save us time as a tester. Typically we’d not be able to access these internal methods from Appium, because Appium can only automate the UI from the outside, just like a user.

Espresso Driver

However, if we use Appium’s Espresso driver, which is the newest of Appium’s Android drivers, we’ll be working with Google’s Espresso technology under the hood. Espresso is designed in such a way that it has access to application internals, which means that Appium via this driver can also have access to methods inside our app.

For a successful test run, make sure to have the Espresso driver installed with the appium driver install espresso command:

appium driver install espresso

Internal App Code

Of course, to call any methods inside our app we’ll need to know a lot of information about that method. For example, its name, where it’s defined — i.e. in the application class or the main activity class, and what kinds of arguments it takes. Once we do know all this information, we can call that internal method using a special Appium command called backdoor. Let's take a look at how this command works.

First of all, let’s assume that in our Android app we have a method on the main application class. It’s called raiseToast and it takes a string as a parameter. What the app does with this string is create something called a toast, which is like a little temporary notice that shows up on the Android screen. Remember, this is code that is in the application itself, not in our test suite.

We only know about this code because we can read the source of our application and see that it's there. It might not be in any way directly accessible to users.

The argument that should be passed into the invocation of driver.execute_script("mobile: backdoor") is a dictionary {} defining a complex object specifying the name and details of the in-app method to call. This dict is turned into a JSON object and interpreted by the Appium server.

Trigger internal app methods with Python client to raise a toast.

So if we call the raiseToast method, this is what would happen on the screen. Ultimately, what our Appium Python client needs to do in order to make all of this happen is to encode all the necessary information about the raiseToast method in JSON and send it to the Appium server. So let's have a look at what needs to be sent.

Backdoor Data and Call

And this is an example of the JSON object that needs to be sent to Appium in order to trigger the raiseToast method. It describes where the method is hosted, namely on the application. We then have an opportunity to call a whole set of methods but we'll call just one. We have to provide the name which is raiseToast then we have to tell Appium what arguments to call this method with. The args value is therefore an array of other objects, each one of which is described as a Java type and has a value. In this case, we're trying to call the raiseToast method with the message, "Toast raised by test script"

Thankfully, we don’t have to construct this JSON by hand but we do have to create the equivalent structure in Python. So what we really want to call in our test code is something like this:

{
"target": "application",
"methods": [{
"name": "raiseToast",
"args": [{
"value": "Toast raised by test script",
"type": "String"
}]
}]

}

Here we define a series of lists and maps that encode all of the same information but as a Python object with multiple levels. Then we pass this object to the mobile: backdoor command as the argument to the execute_script call.

script_args = {
"target": "application",
"methods": [{
"name": "raiseToast",
"args": [{
"value": "Toast raised by test script",
"type": "String"
}]
}]
}
driver.execute_script("mobile: backdoor", script_args)

Pros and Cons

Let’s talk about the pros and cons of this approach.

Pros

  1. One great thing about backdooring is that as long as we can read the source ourselves we might not need to built any extra functionality into the app to support it. If the methods we need already exist we can can just call them.
  2. Secondly, we are not limited to what can be done from the UI. We have access to the app’s internals, which gives us a lot of potential power.
  3. And this is also a very fast strategy because we’re not going through the UI at all and it’s just a single Appium command taking place.

Cons

  1. The main frustration here is that this is not a cross-platform solution. We don’t have any way to access app internals from an iOS test context and this only works on Android.
  2. Likewise this test method is only possible with the Espresso driver. It won’t work with the older drivers.

Python Coding Practice

With all that being said, let’s check out how this works in practice. Let’s take a look at a code sample illustrating the backdoor method in the backdoor_testing_espresso_android.py file. Most of the file is our usual setup for an Android test.

https://github.com/lana-20/appium-backdoor-testing/blob/main/backdoor_testing_espresso_android.py
CAPS = {
"platformName": "Android",
"appium:options": {
"platformVersion": "10",
"deviceName": "Android Emulator",
"automationName": "Espresso",
"app": APP,
}
}

The one important difference for setup is in the capabilities (CAPS) section. Look at line 12, where we have the automation name capability. Usually it’s UiAutomator2, but now we’ve declared that it should be Espresso. And this tells Appium that we want to use the Espresso driver for this particular test. Remember, we’re doing this because the backdoor command is available only within the Espresso driver!

Further down in the script, there’s the construction of our backdoor data using a bunch of maps and arrays declaring that we want to raise a toast with the message “Toast raised by test script”. We then pass this data object into the call to the mobile: backdoor command. And finally we have a time.sleep here, so that the app stays long enough for us to visually notice the toast message popping up.

Well, let’s give this test script a whirl. Load up an Android emulator and the Appium server logs to be able to watch the test proceeding.

Note: The first time we run a script, the session start will take a little bit longer because we’re running a new driver, in this case, the Espresso driver.

HD video: https://youtu.be/KaHSD_33b58

Once it launches, we’ll see the “Toast raised by test script” message and indeed we did. What this means, of course, is that we were able to successfully call a method written in the Java source code of our app all the way from our test script and that’s pretty fun!

Appium Backdoors Technique — Shortcut to App State

Anything we can do to save tests having to cover the same ground over and over again just to set up state is worth it, even if it involves a painful back-and-forth with our development team. Even if we don’t have control over the source code ourselves, it’s a relatively easy argument to make that a little up-front effort to create these state set-up mechanisms will pay off for everyone in the long run, in terms of quicker and more reliable builds.

Happy testing and debugging!

I welcome any comments and contributions to the subject. Connect with me on LinkedIn, X , GitHub, or Insta.

Resources:

--

--

Lana Begunova

I am a QA Automation Engineer passionate about discovering new technologies and learning from it. The processes that connect people and tech spark my curiosity.