All of this prevents you from writing clean, stable, maintainable and understandable UI-tests! And all teams who decide to write autotests are forced to struggle with these problems. It sucks.

That’s why we (developers at KasperskyLab, Avito, HeadHunter) have decided to join together to prepare a united library to address this lack.

Meet the new unique library — Kaspresso!

We’ve written a series of articles on how Kaspresso resolves your beloved tasks.

The first one is about problems with Readability, Flakiness, Logging and Architecture of UI-tests.

Let’s go!

Readability

To begin with, the Kakao library is going to improve readability. Kakao is a beautiful DSL wrapper over Espresso.

Just compare two pieces of code representing one test.

The first code is written with Espresso:

The second code is written with Kakao:

Pretty impressive.

You may notice the use of such things as MainScreen . MainScreen is an implementation of the Page Object pattern:

Shortly, Page Object describes views located on the screen. A more detailed explanation will come a little later. So, Kakao encourages us to write tests based on PageObjects.

Flaky tests and logging

Here, the real adventure begins. I want to draw your attention to the fact that there are no good and clean solutions to resolve flaky and logging problems. But we have tried to address this lack. What have we gotten?

As you remember Espresso doesn’t provide any mechanism to handle errors with a restarting of failed actions nor does it add automatic logs. Have a look at the entire scheme:

If we really wish to manage Espresso, then we have to put interceptors between the User and Espresso. They will be responsible for handling errors, restarting failed actions and logging, like on the following picture:

How to set interceptors here? What does it look like? To answer this question, let’s dive into Espresso!

Look at the Espresso test again:

I really like this API. So readable and maintainable. Pretty cool. (sarcasm =))

But let’s transform the code into a block-scheme to simplify the perception of Espresso:

Now, let’s consider the block-scheme step-by-step:

The onView method with ViewMatcher passed as the argument helps to get a special class describing the View with which we are working.

The name of this class is ViewInteraction. ViewIntercation is the most basic and important class in Espresso.

All actions and assertions over a view are available only through the ViewIntercation class. Actually, all actions and assertions over a view are available through two methods of the ViewIntercation . These are perform and check methods. These methods do a lot of different things with MainLooper, Views, Async operations and others under the hood. In arguments, we set implementations of simple interfaces: ViewAction and ViewAssertion by which we set rules for concrete action or assertion that we want to execute.

Now, let’s recall the initial scheme of the User-Espresso interaction:

When including the above interfaces and classes it transforms into:

I think you will predict the next step. Right, just wrap perform and check methods to manage Espresso:

This new intermediate layer is an ideal candidate where we can put our Interceptors. What are these interceptors and what do they look like?

We have implemented two kinds of Interceptors: BehaviorInterceptor and WatcherInterceptor.

BehaviorInterceptor

We shall now focus on the example with the viewIntercation.perform method. All we are going to discuss is absolutely identical for viewIntercation.check .

BehaviorInterceptor is the current Wrapper of viewIntercation.perform .

The Interceptor is responsible for:

Calling viewIntercation.perform as much as you need. Handling the result of each execution of viewIntercation.perform .

The code is like this:

The first implementation behaviorInterceptor is the FlakyBehaviorInterceptor to overcome the flakiness problem. See the draft of FlakyBehaviorInterceptor ’s implementation:

Pay attention to the action parameter. In our case, it’s viewInteraction.perform(ViewAction) . The calling of action is wrapped by try-catch and do-while constructions which we can handle and manage Espresso’s behavior.

In case of error, flakyBehaviorInterceptor catches the exception and repeats the calling after intervalMs period. The number of attempts to execute the action is restricted by the timeoutMs parameter.

But we have not stopped with only the FlakyBehaviorInterceptor .

For example, another common failure is the nonvisibility of the view on the screen. You just need to scroll the parent layout to make your view visible. Surprisingly enough, Espresso can’t do that and throws an exception. That’s why the AutoscrollBehaviorInterceptor was designed.

One more cause of flakiness is the random appearing of android system dialogs, especially in real devices. To tackle it we have written the SystemDialogBehaviorInterceptor .

Keep in mind that the principle of described interceptors works like a Russian matryoshka. Have a glance at the image below:

FlakyBehaviorInterceptor calls AutoscrollBehaviorInterceptor , AutoscrollBehaviorINterceptor calls SystemDialogBehaviorInterceptor , SystemDialogBehaviorInterceptor calls Espresso’s code. But the handling of a result is going in the opposite way.

WatcherInterceptor

The second kind of Interceptors is WatcherInterceptor .

WatcherInterceptor can’t impact the behavior of Espresso. But it can get a lot of useful information about a concrete action. Such information is located in ViewAction . Thanks to this data you can build, for example, richer, more understandable and readable logs. So, how to get such information?

We have introduced the ViewActionProxy class which looks like on the following image:

The implementation’s draft of ViewActionProxy is:

As you see we just call all watcherInterceptors and send them all the needed information about concrete viewAction and view . After this activity, we call the original viewAction.perform .

Our default WatcherInterceptor logs each action and assertion of each view. Have a glance at the example below:

Brilliant, isn’t? The concept is really interesting and flexible. Nobody prevents you from adding additional interceptors and modify the common Espresso behavior. But, where are all interceptors located? How is it organized?

First, the full wrapper over Espresso is Kakao that hides all interactions with ViewInteraction, onView, and other inner Espresso things. That’s why we put the support of interceptors into the Kakao library (version 2.1).

Second, we have written a library that provides a simple and convenient way to manage interceptors; it gives a rich set of default interceptors handling flaky tests, improving the logging and such like. And you know the name of this library — Kaspresso.

See an example of how it works with the simple test written with Kakao:

How do I enable all default interceptors i.e. behavior and watcher interceptors?

You just need to add the following thing:

TestCase is a special parent class making all the magic. Here, our test has the handling of flakiness and can output more detailed logs. It’s quite sexy =)

Sweety cookies

The earlier described interceptors allowed us to implement some very attractive functions. See below.

flakySafety

It’s a method that receives a lambda and invokes it in the same manner as FlakyBehaviorInterceptor does. If you disable this interceptor or if you want to set some special flaky safety params for any view, you can use this method.

continuously

This function is similar to what flakySafely does, but for negative scenarios, where you need all the time to check that something does not happen.

compose

This is a method to make a composed action from multiple actions or assertions, and this action succeeds if at least one of its components succeeds. It is available as an extension function for any BaseView (base class for all views in Kakao), and just as a regular method (in this case, it can take actions on different views as well).

Architecture of tests

Have you ever had any recommendations on how to write conventional UI-tests? I am not sure you have. But when a developers’ team starts to write autotests without any code style and rules, then, the outcome can be unpredictable.

So, what do we offer? We are going to divide all the recommendations into two large groups: Abstractions and Test structure.

Abstractions

This part is built using a question-answer format.

How many abstractions can you have in your tests?

Only one! It’s a page object (PO), the term explained well by Martin Fowler in this article. As we discussed Page Object describes views located on the screen. It makes your code cleaner.

In Kakao a Screen class is the implementation of the PO. Each screen visible by the user, even a simple dialog, should be a separate PO.

Of course, there are cases when you need a new abstraction and it's ok. But our advice is to think well before you introduce a new abstraction. The fewer number of abstractions makes the possibility of involving testers and auto-testers into the process of autotest writing simpler.

Is it ok that your PO contains helper methods?

If these methods help to understand what the test is doing, then it’s ok.

For example, compare two parts of code:

and

I am sure that the method navigateToTasksScreen() is more "explicit" than the simple click on some shieldView .

Can Page Object contain inner state or logic?

No! Page Object doesn’t have an inner state or logic. It’s only a description of the UI of a concrete View. Remember about the Single responsibility principle.

Assert help methods inside of Page Object. Is it appropriate?

Experienced auto-testers who engaged in the automatization of tests on the Desktop application may remember about potential huge sizes of Page Objects in the case when a developer puts a lot of helper (asserts) methods inside the PO, as suggested in the article of Martin Fowler.

But Mobile and Desktop applications are different things. And, sure, the size of a screen on a Mobile and the size of a screen on a Desktop are different, too. That’s why we don’t see obstacles to add assert methods into PO.

To clarify the advantage of such an approach just compare three pieces of code:

The third code was inspired by Martin Fowler’s advice in the mentioned article.

To summarize, we are in favor of the first variant.

Test structure

Check this simple example:

We have two screens and three actions here. Can you predict how this test correlates with the test-case on which the test is based? How many steps are there in the test-case? Doubt.

What can a developer do here? The first way is to put comments:

Okay. But, we also wish to see what step is running in the logs of the test.

Let’s perform the code:

We have added logs in the test. Not bad.

Another desirable feature is to catch potential exceptions in each step. In the case of an exception, we want to output additional info in the logs and make a screenshot.

The current code transforms into:

Ugh. You see how your simple test converts into a real mess. The maintenance of such code is too hard.

That’s why we have created a special kind of “Kotlin” DSL to carry out all our wishes leaving the code without significant changes.

This DSL looks like this:

The method step is doing all what we have discussed:

auto logging

exceptions’ catching

screenshot after an error

etc.

Now, your test consists of separate and logical steps with understandable names and the huge logic under the hood: