Android UI test framework
We've decided to merge AdbServer into main Kaspresso repository. AdbServer is very tied with Kaspresso and there is no mind to maintain two separate repositories. Further support and development should be easier.
AdbServer logging is more enjoyable, suitable, and understandable now. Take a look at the example of logs from the Deskop:
INFO 10/09/2020 11:37:20.185 desktop=Desktop-25920 device=emulator-5554 message: The attempt to connect to Device was a success
You can see the type of a message, date and time, the desktop executing the message and the emulator giving the task, and the message. Another example of logs from the Device:
2020-09-10 12:24:27.427 10349-10378/com.kaspersky.kaspressample I/KASPRESSO_ADBSERVER: The result of command=AdbCommand(body=shell su 0 svc data disable) => CommandResult(status=SUCCESS, description=exitCode=0, message=, serviceInfo=The command was executed on desktop=Desktop-30548)
Here is very useful information about where the command was executed. In this example, the command was executed on Desktop-30548.
You can choose an appropriate type of logging: VERBOSE
, DEBUG
, INFO
, WARN
, ERROR
. INFO
is a default type providing all basic logs. In some cases VERBOSE
and DEBUG
modes could be useful too.
Also, a lot of potential bugs and issues were fixed. All information is available in AdbServer wiki.
Network
implementation is able to turn on/off network even without running AdbServer.compose
with extended API.Logcat
implementation that required AdbServer to be run before every test.StepInfo
is available in every step
now.artifacts/adbserver-desktop.jar
The old version artifacts/desktop_1_1_0.jar
is also available for use with older versions of Kaspresso.device.logcat
in your tests, you should call device.logcat.disableChatty
in the before
section of your test.
In previous version of Kaspresso, device.logcat.disableChatty
was called automatically during initialization. This resulted in the need to always run AdbServer before tests.We are happy to announce a new unique Kotlin DSL wrapper over UI Automator: Kautomator! Let's consider the main features.
The most amazing feature of Kautomator is an incredible boost of UI Automator. Just have a look at the video below:
The left video is a boosted UI Automator, the right video is a default UI Automator.
All details are available here.
Have a look at a typical piece of code written with UI Automator:
val instrumentation: Instrumentation = InstrumentationRegistry.getInstrumentation()
val uiDevice = UiDevice.getInstance(instrumentation)
val uiObject = uiDevice.wait(
Until.findObject(
By.res(
"com.kaspersky.kaspresso.sample_kautomator",
"editText"
)
),
2_000
)
uiObject.text = "Kaspresso"
assertEquals(uiObject.text, "Kaspresso")
And just evaluate a Kautomator API that is doing the same thing:
MainScreen {
simpleEditText {
replaceText("Kaspresso")
hasText("Kaspresso")
}
}
You can find a comprehensive Kautomator tutorial here.
By default, Kaspresso provides a rich set of interceptors for Kautomator that prevent flakiness of UI Automator. How does it work? You are welcome to read here and here.
Let's evaluate multi-stage protection from flakiness in UI Automator:
UiObject2
) visible.UiObject2
by BySelector
.Forget your pain, forget your suffering. Just enjoy writing stable and fast tests even though these tests are using UI Automator under the hood.
Diff in 380 commits contains not only Kautomator =) Have a glance at some sweet features and changes.
Yep, a developer/tester can automate upgrade scenarios using Kaspresso now. Check the example to learn how to do it.
If you have not heard about GDPR and high-profile lawsuits then you are lucky. But, if your application works in Europe then it's so important to enable/disable all analytics/statistics according to acceptance of the agreements.
One of the most reliable ways to check analytics/statistics sending is to verify logcat where all analytics/statistics write their logs (in debug mode, sure).
That's why we have created a special Logcat
class providing a wide variety of ways to check logcat. Please, observe the example and save money of your company =)
Kaspresso provides a new continuously
method that can be useful for your tests. As opposed to flakySafely
, it does not skip the last attempt after the first success and throws inner exception outside as soon as it was thrown. It can be especially helpful for checking negative scenarios.
continuously {
dialogTitle {
doesNotExist()
}
}
Check the source code and example.
We've updated and extended compose functionality.
The main changes are the UI Automator support and the keyword then
adding:
compose {
// the first potential branch when ComplexComposeScreen.stage1Button is visible
// here, Espresso is used under the hood
or(ComplexComposeScreen.stage1Button) {
isVisible()
} then {
// if the first branch was succeed then we execute some special flow
step("Flow is over the product") {
ComplexComposeScreen {
stage1Button {
click()
}
stage2Button {
isVisible()
click()
}
}
}
}
// the second potential branch when UiComposeDialog1.title is visible
// just imagine that is some unexpected system or product behavior and we cannot fix it now
// here, UI Automator is used under the hood
or(UiComposeDialog1.title) {
isDisplayed()
} then {
// if the second branch was succeed then we execute some special flow
step("Flow is over dialogs") {
UiComposeDialog1 {
okButton {
isDisplayed()
click()
}
}
UiComposeDialog2 {
title {
isDisplayed()
}
okButton {
isDisplayed()
click()
}
}
}
}
}
But remember, compose
is useful in cases when we don't know an accurate sequence of events and can't influence it. Such cases are possible when a test is performed outside the application. When a test is performed inside the application we strongly recommend to make your test linear and not to put any conditions in tests that are possible thanks to compose
.
For more information, please check the documentation, the example and some additional possibilities.
We have decided to increase all default timeouts from 2 seconds to 10 seconds. Usage of UI Automator has been the main reason for this decision.
We have introduced a special provider that is available via BaseTestCase
and BaseTestContext
:
interface TestAssistantsProvider {
val device: Device
val adbServer: AdbServer
val testLogger: UiTestLogger
val params: Params
}
If you have forgotten what BaseTestCase
and BaseTestContext
mean then let me remind you:
@RunWith(AndroidJUnit4::class)
class SimpleTest : TestCase() { <===== TestCase is an inheritor of BaseTestContext
<===== TestAssistantsProvider is available
// ....
@Test
fun test() =
before { <===== a lambda with receiver where receiver is BaseTestContext
// .... <===== TestAssistantsProvider is available
}.after { <===== a lambda with receiver where receiver is BaseTestContext
// .... <===== TestAssistantsProvider is available
}.run { <===== a lambda with receiver where receiver is inheritor of BaseTestContext
// .... <===== TestAssistantsProvider is available
}
}
It might be useful when a developer wants to put some common code into separate classes. But this common code interacts with mentioned properties like device
, adbServer
, testLogger,
params`. So, your code refactoring will be easier.
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 the mentioned tasks. But the developer doesn't have access to BaseTestContext
in those methods. That's why we have introduced special default actions that you can set in the constructor by Kaspresso.Builder
.
The example of how to implement default actions in Kaspresso.Builder
is:
open class YourTestCase : TestCase(
kaspressoBuilder = Kaspresso.Builder.advanced {
beforeEachTest {
testLogger.i("beforeTestFirstAction")
flakySafety { device.network.enable() }
flakySafety { device.language.switchInApp(FRENCH) }
}
afterEachTest {
testLogger.i("afterTestFirstAction")
}
}
)
The more detailed info is available here.
A developer can also extend 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.
It looks like:
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}")
}
}
}
class EnricherBaseTestCase : BaseTestCase<TestCaseDsl, TestCaseData>(
kaspresso = Kaspresso.Builder.default(),
dataProducer = { action -> TestCaseDataCreator.initData(action) },
mainSectionEnrichers = listOf(
LoggingMainSectionEnricher(),
AnalyticsMainSectionEnricher()
)
)
The documentation is here. The example is here.
Minor fixes for correct artifactory delivery
Initial Release with the core functionality of Kaspresso. Please observe README file for additional info.