This article is the fifth in a multi-part series on test speed and reliability, inspired by a webinar I gave on the same subject (you can watch the webinar here). You might also want to check out Part 1: Test Flakiness, Part 2: Finding Elements Reliably, Part 3: Waiting for App States, and Part 4: Dealing With Unfindable Elements.
One of the unfortunate realities of functional testing is the "speed limit": we can only go as fast as the underlying technologies let us go. Taps take time. Sending keystrokes takes time. Waiting for an application to load data from the network, or finish animations... your app takes time too. And that's kind of the point: we want functional testing to be "high-fidelity", meaning as close to real life as possible.
That's all well and good, except when we have multiple (or many) tests that rely on the same starting point. It's a real pain to wait for a test script to type into half a dozen boxes and tap half a dozen buttons before the meat of a test begins. Probably the most common scenario is logging in: a lot of what we test in our apps is likely behind a user login verification, which typically means finding and entering text into a few text boxes and tapping a button, not to mention waiting for the verification to take place over the network. It's not crazy to imagine that logging into an app could take as long as 20 or 25 seconds, depending on a variety of factors. Let's imagine for a moment that we have a suite of 50 tests which require logging in before they can start verifying their particular requirements. If math doesn't lie, then:
In this scenario, setting up the logged-in state in our application is costing us 20 minutes of build time (which might be real time if we're running in a single thread). Ouch. More than that, we're opening ourselves up to the potential of flakiness in that particular set-up flow, by running it many more times than we need to.
Sadly, there might not be a way to speed up the login steps using Appium itself (once it's established that the most efficient locator strategies are already in use, etc...). That doesn't mean we can't take a shortcut, though! What if we could start our test with the app already in its logged-in state? With a little help from the app developers, we can!
In this article, we're going to take a look at 5 ways to set up app state so that you don't have to use Appium to get to the point where you need to start your test. (Of course, don't forget to leave at least one test that does use Appium to set up the state, so you're actually covering it. You don't want your login functionality to break because none of your tests actually walk through the flow anymore!)
If you're developing an Android app, you might be aware of the concept of Activities. Android apps might have multiple activities, which correspond to different parts of the application which may need to be accessed directly (say from a link in an e-mail).
It's possible to design your app in such a way that starting a certain activity sets up the appropriate state in your app. Essentially, a given activity is simply a trigger for state set-up. Appium makes it possible to start arbitrary activities in your app using the startActivity
command, and passing in the package of your app along with the desired activity as parameters, for example:
driver.startActivity(new Activity("com.example.app", ".ActivityName"));
You can even use the appActivity
desired capability to dump your session immediately into the specified activity upon start, so that from the very beginning your code will be executing in the context of that activity. Just don't forget to strip out any activities that are used for testing only in the production build of your app.
A similar technique is possible with iOS, even though iOS apps do not have "activities". Instead, iOS apps can be launched with what are essentially command-line flags called "launch arguments". These flags can be read by the running application. You could easily design your application to parse the flags for a variety of data, including usernames and passwords to be used for an initial UI-less login.
The way to access this feature using Appium is via the processArguments
capability. For example, depending on how we'd set up our app, we could imagine a schema like this:
{
"processArguments": "-view login -viewparams 'my_username,my_password'"
}
Of course, the app developer would have to build the app in such a way that it knows what to do with these flags, and be sure to make the production version of the app ignore them.
A more straightforward approach that still involves UI automation using Appium is what I call the "Test Nexus" technique. I saw a great example of this from Wim Selles, who put the technique to good use at his company.
What's a Test Nexus? It's essentially a "kitchen sink" view in your app, only available in the test build, which contains links to a variety of other locations in your app, that come with attached state. So you could have a link that says "Logged-in Home", or one that reads "Cart With 1 Item". Tapping either of those links would trigger the app to take you to the appropriate view, with the appropriate state. This does mean the app developer would need to hook the links up to any necessary logic.
The advantage of this approach is that it is cross-platform, and easy enough for the Appium test author to simply find the right button and click on it, saving a lot of time in the process. As an example of what this Test Nexus could look like, here's a screenshot of Wim's app:
Another cross-platform option is so-called "Deep Linking". This is an OS-level feature whereby clicking on a URL with a custom scheme (i.e., a vendor-specific scheme which differs from "http" or "https") actually takes you to your application. The URL is passed to the app, and thus the app can parse it for all kinds of data which then direct the user experience.
Used for testing, it's a powerful way to set up a very flexible URL structure that can be used to direct your test to anywhere you want in your app. It does require the app developer to have registered a custom scheme with the OS, and to have created some kind of URL "controller" that parses the URL and actually navigates the app to the correct view with the appropriate state. For example, you could have a URL that looks like:
yourapp://test/login/:username/:password
Which, when clicked, would open up your application in a state where the login verification has already been performed with the supplied username and password. What's great is that, using Appium, you don't need to find some way to click a link to this URL: you can use Appium's driver.get()
command to trigger the URL to launch directly:
driver.get("yourapp://test/login/my_username/my_password");
I won't say more about Deep Linking here, because I wrote a whole Appium Pro article on that topic already; check it out for more info!
I mention this last technique because it is interesting, even though there's not really an easy way to enable it within Appium today. The idea is basically that your application can expose application-internal methods to the outside world. These would be methods that are useful for testing and setting up state in your app.
Your test script would then access these "backdoors" whenever it needed to set up state. At the moment this would have to be some kind of custom RPC channel between your test script and app, though there is an interesting proof-of-concept proposal to make something like this available within Appium itself (for Android). The idea was brought forward to the Appium community by Rajdeep Varma in his AppiumConf 2018 talk, and it's certainly a promising one!
In the meantime, I recommend checking out one of the first 4 techniques. Anything you 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 your development team. Even if you don't have control over the source code yourself, I think 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.