Skip to content

Kaspresso configurator

Kaspresso class - is a single point to set Kaspresso parameters.
A developer can customize Kaspresso by setting Kaspresso.Builder at constructors of TestCase, BaseTestCase, TestCaseRule, BaseTestCaseRule.
The example:

class SomeTest : TestCase(
    kaspressoBuilder = Kaspresso.Builder.simple {
        beforeEachTest {
            testLogger.i("The beginning")
        }
        afterEachTest {
            testLogger.i("The end")
        }
    }
) {
    // your test
}

Structure

Kaspresso configuration contains:

Kakao clicks

Kaspresso provides the possibility to override Espresso custom clicks. Kakao library provides a set of prepared custom clicks which improves the stability of the tests especially on the devices under high load.

All details about the problem and solutions are described in Kakao documentation.

The example of how to apply the custom clicks in your test is presented in CustomClickTest.

class ClickTest : TestCase(
    kaspressoBuilder = Kaspresso.Builder.simple(
        customize = {
            clickParams = ClickParams.kakaoVisual()
        }
    )
) {
    // your test
}

Kaspresso provides the next prepared options to customise clicks: 1. ClickParams.kakaoVisual()' - Kakao clicks with visualisation. 2.ClickParams.kakao()' - Kakao clicks. 3. `ClickParams.default()' - Espresso clicks. Using by default.

Loggers

Kaspresso provides two loggers: libLogger and testLogger. libLogger - inner Kaspresso logger
testLogger - logger that is available for developers in tests.
The last one is accessible by testLogger property in test sections (before, after, init, transform, run) in the test DSL (by TestContext class).
Also, it is available while setting Kaspresso.Builder if you want to add it to your custom interceptors, for example.

Kaspresso interceptors based on Kakao/Kautomator Interceptors.

These interceptors were introduced to simplify and uniform using of Kakao interceptors and Kautomator interceptors.

Important moment about a mixing of Kaspresso interceptors and Kakao/Kautomator interceptors.
Kaspresso interceptors will not work if you set your custom Kakao interceptors by calling of Kakao.intercept method in the test or set your custom Kautomator interceptors by calling of Kautomator.intercept in the test.
If you set your custom Kakao interceptors for concrete Screen or KView and set argument isOverride in true then Kaspresso interceptors will not work for concrete Screen or KView fully. The same statement is right for Kautomator where a developer interacts with UiScreen and UiBaseView.

Kaspresso interceptors can be divided into two types:

  1. Behavior Interceptors - are intercepting calls to ViewInteraction, DataInteraction, WebInteraction, UiObjectInteraction, UiDeviceInteraction and do some stuff.
    Attention, we are going to consider some important notes about Behavior Interceptors at the end of this document.
  2. Watcher Interceptors - are intercepting calls to ViewAction, ViewAssertion, Atom, WebAssertion, UiObjectAssertion, UiObjectAction, UiDeviceAssertion, UiDeviceAction and do some stuff.

Let's expand mentioned Kaspresso interceptors types:

  1. Behavior Interceptors
    1. viewBehaviorInterceptors - intercept calls to ViewInteraction#perform and ViewInteraction#check
    2. dataBehaviorInterceptors - intercept calls to DataInteraction#check
    3. webBehaviorInterceptors - intercept calls to Web.WebInteraction<R>#perform and Web.WebInteraction<R>#check
    4. objectBehaviorInterceptors - intercept calls to UiObjectInteraction#perform and UiObjectInteraction#check
    5. deviceBehaviorInterceptors - intercept calls to UiDeviceInteraction#perform and UiDeviceInteraction#check
  2. Watcher Interceptors
    1. viewActionWatcherInterceptors - do some stuff before android.support.test.espresso.ViewAction.perform is actually called
    2. viewAssertionWatcherInterceptors - do some stuff before android.support.test.espresso.ViewAssertion.check is actually called
    3. atomWatcherInterceptors - do some stuff before android.support.test.espresso.web.model.Atom.transform is actually called
    4. webAssertionWatcherInterceptors - do some stuff before android.support.test.espresso.web.assertion.WebAssertion.checkResult is actually called
    5. objectWatcherInterceptors - do some stuff before UiObjectInteraction.perform or UiObjectInteraction.check is actually called
    6. deviceWatcherInterceptors - do some stuff before UiDeviceInteraction.perform or UiDeviceInteraction.check is actually called

Please, remember! Behavior and watcher interceptors work under the hood in every action and assertion of every View of Kakao and Kautomator by default in Kaspresso.

Special Kaspresso interceptors

These interceptors are not based on some lib. Short description:

  1. stepWatcherInterceptors - an interceptor of Step lifecycle actions
  2. testRunWatcherInterceptors - an interceptor of entire Test lifecycle actions

As you noticed these interceptors are a part of Watcher Interceptors, also.

BuildStepReportWatcherInterceptor

This watcher interceptor by default is included into Kaspresso configurator to collect your tests steps information for further processing in tests orchestrator.
By default this interceptor is based on AllureReportWriter (if you don't know what Allure is you should really check on it).
This report writer works with each TestInfo after test finishing, converts its steps information into Allure's steps info JSON, and then prints JSON into LogCat in the following format:

I/KASPRESSO: ---------------------------------------------------------------------------
I/KASPRESSO: TEST PASSED
I/KASPRESSO: ---------------------------------------------------------------------------
I/KASPRESSO: #AllureStepsInfoJson#: [{"attachments":[],"name":"My step 1","parameters":[],"stage":"finished","start":1568790287246,"status":"passed", "steps":[],"stop":1568790288184}]

This logs should be processed by your test orchestrator (e.g. Marathon). If you use Marathon you should know that the it requires some additional modifications to support processing this logs and doesn't work as expected at the current moment. But we are working hard on it.

Default actions in before/after sections

Sometimes, a developer wishes to put some actions repeating in all tests before/after into a single place to simplify the maintenance of tests.
You can make a remark that there are @beforeTest/@afterTest annotations to resolve mentioned tasks. But the developer doesn't have an access to BaseTestContext in those methods. That's why we have introduced special default actions that you can set in constructor by Kaspresso.Builder.
The example how to implement default actions in Kaspresso.Builder is:

open class YourTestCase : TestCase(
    kaspressoBuilder = Kaspresso.Builder.simple {
        beforeEachTest {
            testLogger.i("beforeTestFirstAction")
        }
        afterEachTest {
            testLogger.i("afterTestFirstAction")
        }
    }
)
The full signature of beforeEachTest is:
beforeEachTest(override = true, action = {
    testLogger.i("beforeTestFirstAction")
})
afterEachTest is similar to beforeEachTest.
If you set override in false then the final beforeAction will be beforeAction of the parent TestCase plus current action. Otherwise, final beforeAction will be only current action. How it's work and how to override (or just extend) default action, please, observe the example.

Device

Device instance. Detailed info is at Device wiki.

AdbServer

AdbServer instance. Detailed info is at AdbServer wiki.

Kaspresso configuring and Kaspresso interceptors example

The example of how to configure Kaspresso and how to use Kaspresso interceptors is in here.

Default Kaspresso settings

BaseTestCase, TestCase, BaseTestCaseRule, TestCaseRule are using default customized Kaspresso (Kaspresso.Builder.simple builder).
Most valuable features of default customized Kaspresso are below.

Logging

Just start SimpleTest. Next, you will see those logs:

I/KASPRESSO: ---------------------------------------------------------------------------
I/KASPRESSO: BEFORE TEST SECTION
I/KASPRESSO: ---------------------------------------------------------------------------
I/KASPRESSO: ---------------------------------------------------------------------------
I/KASPRESSO: TEST SECTION
I/KASPRESSO: ---------------------------------------------------------------------------
I/KASPRESSO: ___________________________________________________________________________
I/KASPRESSO: TEST STEP: "1. Open Simple Screen" in SimpleTest
I/KASPRESSO_SPECIAL: I am kLogger
I/ViewInteraction: Checking 'com.kaspersky.kaspresso.proxy.ViewAssertionProxy@95afab5' assertion on view (with id: com.kaspersky.kaspressample:id/activity_main_button_next)
I/KASPRESSO: Check view has effective visibility=VISIBLE on AppCompatButton(id=activity_main_button_next;text=Next;)
I/KASPRESSO: single click on AppCompatButton(id=activity_main_button_next;text=Next;)
I/KASPRESSO: TEST STEP: "1. Open Simple Screen" in SimpleTest SUCCEED. It took 0 minutes, 0 seconds and 618 millis. 
I/KASPRESSO: ___________________________________________________________________________
I/KASPRESSO: ___________________________________________________________________________
I/KASPRESSO: TEST STEP: "2. Click button_1 and check button_2" in SimpleTest
I/KASPRESSO: single click on AppCompatButton(id=button_1;text=Button 1;)
I/ViewInteraction: Checking 'com.kaspersky.kaspresso.proxy.ViewAssertionProxy@9f38781' assertion on view (with id: com.kaspersky.kaspressample:id/button_2)
I/KASPRESSO: Check view has effective visibility=VISIBLE on AppCompatButton(id=button_2;text=Button 2;)
I/KASPRESSO: TEST STEP: "2. Click button_1 and check button_2" in SimpleTest SUCCEED. It took 0 minutes, 0 seconds and 301 millis. 
I/KASPRESSO: ___________________________________________________________________________
I/KASPRESSO: ___________________________________________________________________________
I/KASPRESSO: TEST STEP: "3. Click button_2 and check edit" in SimpleTest
I/KASPRESSO: single click on AppCompatButton(id=button_2;text=Button 2;)
I/ViewInteraction: Checking 'com.kaspersky.kaspresso.proxy.ViewAssertionProxy@ad01abd' assertion on view (with id: com.kaspersky.kaspressample:id/edit)
I/KASPRESSO: Check view has effective visibility=VISIBLE on AppCompatEditText(id=edit;text=Some text;)
E/KASPRESSO: Failed to interact with view matching: (with id: com.kaspersky.kaspressample:id/edit) because of AssertionFailedError
I/ViewInteraction: Checking 'com.kaspersky.kaspresso.proxy.ViewAssertionProxy@d0f1c0a' assertion on view (with id: com.kaspersky.kaspressample:id/edit)
I/KASPRESSO: Check view has effective visibility=VISIBLE on AppCompatEditText(id=edit;text=Some text;)
I/ViewInteraction: Checking 'com.kaspersky.kaspresso.proxy.ViewAssertionProxy@3b62c7b' assertion on view (with id: com.kaspersky.kaspressample:id/edit)
I/KASPRESSO: Check with string from resource id: <2131558461> on AppCompatEditText(id=edit;text=Some text;)
I/KASPRESSO: TEST STEP: "3. Click button_2 and check edit" in SimpleTest SUCCEED. It took 0 minutes, 2 seconds and 138 millis. 
I/KASPRESSO: ___________________________________________________________________________
I/KASPRESSO: ___________________________________________________________________________
I/KASPRESSO: TEST STEP: "4. Check all possibilities of edit" in SimpleTest
I/KASPRESSO: ___________________________________________________________________________
I/KASPRESSO: TEST STEP: "4.1. Change the text in edit and check it" in SimpleTest
I/KASPRESSO: replace text on AppCompatEditText(id=edit;text=Some text;)
I/KASPRESSO: type text(111) on AppCompatEditText(id=edit;)
I/ViewInteraction: Checking 'com.kaspersky.kaspresso.proxy.ViewAssertionProxy@dbd9c8' assertion on view (with id: com.kaspersky.kaspressample:id/edit)
I/KASPRESSO: Check with text: is "111" on AppCompatEditText(id=edit;text=111;)
I/KASPRESSO: TEST STEP: "4.1. Change the text in edit and check it" in SimpleTest SUCCEED. It took 0 minutes, 0 seconds and 621 millis. 
I/KASPRESSO: ___________________________________________________________________________
I/KASPRESSO: ___________________________________________________________________________
I/KASPRESSO: TEST STEP: "4.2. Change the text in edit and check it. Second check" in SimpleTest
I/KASPRESSO: replace text on AppCompatEditText(id=edit;text=111;)
I/KASPRESSO: type text(222) on AppCompatEditText(id=edit;)
I/ViewInteraction: Checking 'com.kaspersky.kaspresso.proxy.ViewAssertionProxy@b8ca74' assertion on view (with id: com.kaspersky.kaspressample:id/edit)
I/KASPRESSO: Check with text: is "222" on AppCompatEditText(id=edit;text=222;)
I/KASPRESSO: TEST STEP: "4.2. Change the text in edit and check it. Second check" in SimpleTest SUCCEED. It took 0 minutes, 0 seconds and 403 millis. 
I/KASPRESSO: ___________________________________________________________________________
I/KASPRESSO: TEST STEP: "4. Check all possibilities of edit" in SimpleTest SUCCEED. It took 0 minutes, 1 seconds and 488 millis. 
I/KASPRESSO: ___________________________________________________________________________
I/KASPRESSO: ---------------------------------------------------------------------------
I/KASPRESSO: AFTER TEST SECTION
I/KASPRESSO: ---------------------------------------------------------------------------
I/KASPRESSO: ---------------------------------------------------------------------------
I/KASPRESSO: TEST PASSED
I/KASPRESSO: ---------------------------------------------------------------------------
I/KASPRESSO: #AllureStepsInfoJson#: [{"attachments":[],"name":"My step 1","parameters":[],"stage":"finished","start":1568790287246,"status":"passed", "steps":[],"stop":1568790288184}]
I/KASPRESSO: ---------------------------------------------------------------------------
Pretty good.

Defense from flaky tests

If a failure occurs then Kaspresso tries to fix it using a big set of diverse ways.
This defense works for every action and assertion of each View of Kakao and Kautomator! You just need to extend your test class from TestCase (BaseTestCase) or to set TestCaseRule(BaseTestCaseRule) in your test.
More detailed info about some ways of defense is below

Interceptors

Interceptors turned by default:

  1. Watcher interceptors
  2. Behavior interceptors
  3. Kaspresso interceptors
  4. BuildStepReportWatcherInterceptor

So, all features described above are available thanks to these interceptors.

Some words about Behavior Interceptors

Any lib for ui-tests is flaky. It's a hard truth of life. Any action/assert in your test may fail for some undefined reason.

What general kinds of flaky errors exist:

  1. Common flaky errors that happened because Espresso/UI Automator was in a bad mood =)
    That's why Kaspresso wraps all actions/assertions of Kakao/Kautomator and handles set of potential flaky exceptions. If an exception happened then Kaspresso attempts to repeat failed actions/assert for 10 seconds. Such handling rescues developers of any flaky action/assert.
    The details are available at flakysafety and examples are here.
  2. The reason of a failure is non visibility of a View. In most cases you just need to scroll a parent layout to make the View visible. So, Kaspresso tries to perform it in auto mode.
    The details are available at autoscroll.
  3. Also, Kaspresso attempts to remove all system dialogs if it prevents the test execution.
    The details are available at systemsafety.

These handlings are possible thanks to BehaviorInterceptors. Also, you can set your custom processing by Kaspresso.Builder. But remember, the order of BehaviorInterceptors is significant: the first item will be at the lowest level of intercepting chain, and the last item will be at the highest level.

Let's consider the work principle of BehaviorInterceptors over Kakao interceptors. The first item actually wraps the androidx.test.espresso.ViewInteraction.perform call, the second item wraps the first item, and so on.
Have a glance at the order of BehaviorInterceptors enabled by default in Kaspresso over Kakao. It's:

  1. AutoScrollViewBehaviorInterceptor
  2. SystemDialogSafetyViewBehaviorInterceptor
  3. FlakySafeViewBehaviorInterceptor

Under the hood, all Kakao actions and assertions first of all call FlakySafeViewBehaviorInterceptor that calls SystemDialogSafetyViewBehaviorInterceptor and that calls AutoScrollViewBehaviorInterceptor.
If a result of AutoScrollViewBehaviorInterceptor handling is an error then SystemDialogSafetyViewBehaviorInterceptor attempts to handle received error. If a result of SystemDialogSafetyViewBehaviorInterceptor handling is an error too then FlakySafeViewBehaviorInterceptor attempts to handle received the error.
To simplify the discussed topic we have drawn a picture:

Main section enrichers

Developer also can extends parametrized tests functionality by providing MainSectionEnricher in BaseTestCase or BaseTestCaseRule. The main idea of enrichers - allow adding additional test case's steps before and after the main section's run block.

All you need to do is:

  1. Define your enricher implementation for MainSectionEnricher interface;
class LoggingMainSectionEnricher : MainSectionEnricher<TestCaseData> {
    ...

}

Here, TestCaseData is the same data type as in your BaseTestCase implementation.

  1. Override beforeMainSectionRun or/and afterMainSectionRun methods to add your before/after actions;
class LoggingMainSectionEnricher : MainSectionEnricher<TestCaseData> {

    override fun TestContext<TestCaseData>.beforeMainSectionRun(testInfo: TestInfo) {
        testLogger.d("Before main section run... | ${testInfo.testName}")
        step("Check users count...") {
            testLogger.d("Check users count: ${data.users.size}")
        }
    }

    override fun TestContext<TestCaseData>.afterMainSectionRun(testInfo: TestInfo) {
        testLogger.d("After main section run... | ${testInfo.testName}")
        step("Check posts count...") {
            testLogger.d("Check posts count: ${data.posts.size}")
        }
    }

}

In beforeMainSectionRun and afterMainSectionRun methods you have full access to TestContext<TestCaseData properties and methods, so you can use logger, add test case's steps and so on. Also, this methods received TestInfo parameter.

  1. Add your enrichers into your BaseTestCase implementation.
class EnricherBaseTestCase : BaseTestCase<TestCaseDsl, TestCaseData>(
    kaspresso = Kaspresso.Builder.default(),
    dataProducer = { action -> TestCaseDataCreator.initData(action) },
    mainSectionEnrichers = listOf(
        LoggingMainSectionEnricher(),
        AnalyticsMainSectionEnricher()
    )
)

After this manipulations your described actions will be executed before or after main section's run block.

Pulling the artifacts from the device to the host

Depending on your test configuration, useful artifacts may remain on the device after test finish: screenshots, reports, videos, etc. In order to pull them off the device you could program a script, which would be executed after the completion of the test run on CI. With Kaspresso, you can simplify this process. To do this, you need to configure the artifactsPullParams variable in the Kaspresso Builder. Example:

class SomeTest : TestCase(
    kaspressoBuilder = Kaspresso.Builder.simple {
        artifactsPullParams = ArtifactsPullParams(enabled = true, destinationPath = "artifacts/", artifactsRegex = Regex("(screenshots)|(videos)"))
    }
) {
    ...
}

To make this work, you need to start the ADB server before running the test. After the test is completed, the artifacts will be located by the path specified in the destinationPath argument relative to the working directory from which the ADB server was launched.