kasperskylab / kaspresso Goto Github PK
View Code? Open in Web Editor NEWAndroid UI test framework
Home Page: https://kasperskylab.github.io/Kaspresso/
License: Apache License 2.0
Android UI test framework
Home Page: https://kasperskylab.github.io/Kaspresso/
License: Apache License 2.0
Currently Kaspresso.default()
is feature-loaded (e.g. contains a screenshot interceptor for each step).
As discussed, default implementation should be more lightweight and we can provide few more reconfigured implementations for different hypothetical environments (e.g. local, CI etc).
All the sample tests should be passed on Pull Requests
When using view.compose { or { } then { } }
infix function "then" not applicable
Possible additional topics:
Very often, there is a need to extend BaseTextContext and put in the extended version some custom classes.
As it is performed in compose
methods there is a need to turn off flaky safety interceptors for flakySafely
method
Необходимо ещё раз проверить:
Very often it is not necessary to specify actions to perform before and after the test, so users would like not to create empty before
and after
sections before the run
block but to have an access straight to run
section when they start writing a test.
Force kaspresso check version of adb server for compatibility.
If not compatible then report current and compatible version and fail test on first request to adb server.
Stacktrace:
E/UiDevice: failed to save screen shot to file
java.io.FileNotFoundException: /storage/emulated/0/screenshots/com.kaspersky.kaspressample.simple_tests.SimpleTest/test/1578485958639_SimpleTest_step_1.png: open failed: ENOENT (No such file or directory)
at libcore.io.IoBridge.open(IoBridge.java:496)
Additional information:
screenshot with the error
Hello! I am trying to implement Kaspresso in my project, but i have conflicts with core annotations library:
* What went wrong:
Execution failed for task ':core:checkDebugAndroidTestDuplicateClasses'.
> 1 exception was raised by workers:
java.lang.RuntimeException: Duplicate class org.intellij.lang.annotations.Identifier found in modules adbserver-device-1.0.0.jar (com.kaspersky.android-components:adbserver-device:1.0.0) and annotations-12.0.jar (com.intellij:annotations:12.0)
Duplicate class org.intellij.lang.annotations.JdkConstants found in modules adbserver-device-1.0.0.jar (com.kaspersky.android-components:adbserver-device:1.0.0) and annotations-12.0.jar (com.intellij:annotations:12.0)
Duplicate class org.intellij.lang.annotations.JdkConstants$AdjustableOrientation found in modules adbserver-device-1.0.0.jar (com.kaspersky.android-components:adbserver-device:1.0.0) and annotations-12.0.jar (com.intellij:annotations:12.0)
Duplicate class org.intellij.lang.annotations.JdkConstants$BoxLayoutAxis found in modules adbserver-device-1.0.0.jar (com.kaspersky.android-components:adbserver-device:1.0.0) and annotations-12.0.jar (com.intellij:annotations:12.0)
Duplicate class org.intellij.lang.annotations.JdkConstants$CalendarMonth found in modules adbserver-device-1.0.0.jar (com.kaspersky.android-components:adbserver-device:1.0.0) and annotations-12.0.jar (com.intellij:annotations:12.0)
Duplicate class org.intellij.lang.annotations.JdkConstants$CursorType found in modules adbserver-device-1.0.0.jar (com.kaspersky.android-components:adbserver-device:1.0.0) and annotations-12.0.jar (com.intellij:annotations:12.0)
Duplicate class org.intellij.lang.annotations.JdkConstants$FlowLayoutAlignment found in modules adbserver-device-1.0.0.jar (com.kaspersky.android-components:adbserver-device:1.0.0) and annotations-12.0.jar (com.intellij:annotations:12.0)
Duplicate class org.intellij.lang.annotations.JdkConstants$FontStyle found in modules adbserver-device-1.0.0.jar (com.kaspersky.android-components:adbserver-device:1.0.0) and annotations-12.0.jar (com.intellij:annotations:12.0)
Duplicate class org.intellij.lang.annotations.JdkConstants$HorizontalAlignment found in modules adbserver-device-1.0.0.jar (com.kaspersky.android-components:adbserver-device:1.0.0) and annotations-12.0.jar (com.intellij:annotations:12.0)
Duplicate class org.intellij.lang.annotations.JdkConstants$InputEventMask found in modules adbserver-device-1.0.0.jar (com.kaspersky.android-components:adbserver-device:1.0.0) and annotations-12.0.jar (com.intellij:annotations:12.0)
Duplicate class org.intellij.lang.annotations.JdkConstants$ListSelectionMode found in modules adbserver-device-1.0.0.jar (com.kaspersky.android-components:adbserver-device:1.0.0) and annotations-12.0.jar (com.intellij:annotations:12.0)
Duplicate class org.intellij.lang.annotations.JdkConstants$PatternFlags found in modules adbserver-device-1.0.0.jar (com.kaspersky.android-components:adbserver-device:1.0.0) and annotations-12.0.jar (com.intellij:annotations:12.0)
Duplicate class org.intellij.lang.annotations.JdkConstants$TabLayoutPolicy found in modules adbserver-device-1.0.0.jar (com.kaspersky.android-components:adbserver-device:1.0.0) and annotations-12.0.jar (com.intellij:annotations:12.0)
Duplicate class org.intellij.lang.annotations.JdkConstants$TabPlacement found in modules adbserver-device-1.0.0.jar (com.kaspersky.android-components:adbserver-device:1.0.0) and annotations-12.0.jar (com.intellij:annotations:12.0)
Duplicate class org.intellij.lang.annotations.JdkConstants$TitledBorderJustification found in modules adbserver-device-1.0.0.jar (com.kaspersky.android-components:adbserver-device:1.0.0) and annotations-12.0.jar (com.intellij:annotations:12.0)
Duplicate class org.intellij.lang.annotations.JdkConstants$TitledBorderTitlePosition found in modules adbserver-device-1.0.0.jar (com.kaspersky.android-components:adbserver-device:1.0.0) and annotations-12.0.jar (com.intellij:annotations:12.0)
Duplicate class org.intellij.lang.annotations.JdkConstants$TreeSelectionMode found in modules adbserver-device-1.0.0.jar (com.kaspersky.android-components:adbserver-device:1.0.0) and annotations-12.0.jar (com.intellij:annotations:12.0)
Duplicate class org.intellij.lang.annotations.Language found in modules adbserver-device-1.0.0.jar (com.kaspersky.android-components:adbserver-device:1.0.0) and annotations-12.0.jar (com.intellij:annotations:12.0)
Duplicate class org.intellij.lang.annotations.MagicConstant found in modules adbserver-device-1.0.0.jar (com.kaspersky.android-components:adbserver-device:1.0.0) and annotations-12.0.jar (com.intellij:annotations:12.0)
Duplicate class org.intellij.lang.annotations.Pattern found in modules adbserver-device-1.0.0.jar (com.kaspersky.android-components:adbserver-device:1.0.0) and annotations-12.0.jar (com.intellij:annotations:12.0)
Duplicate class org.intellij.lang.annotations.PrintFormat found in modules adbserver-device-1.0.0.jar (com.kaspersky.android-components:adbserver-device:1.0.0) and annotations-12.0.jar (com.intellij:annotations:12.0)
Duplicate class org.intellij.lang.annotations.PrintFormatPattern found in modules adbserver-device-1.0.0.jar (com.kaspersky.android-components:adbserver-device:1.0.0) and annotations-12.0.jar (com.intellij:annotations:12.0)
Duplicate class org.intellij.lang.annotations.RegExp found in modules adbserver-device-1.0.0.jar (com.kaspersky.android-components:adbserver-device:1.0.0) and annotations-12.0.jar (com.intellij:annotations:12.0)
Duplicate class org.intellij.lang.annotations.Subst found in modules adbserver-device-1.0.0.jar (com.kaspersky.android-components:adbserver-device:1.0.0) and annotations-12.0.jar (com.intellij:annotations:12.0)
Duplicate class org.jetbrains.annotations.Nls found in modules adbserver-device-1.0.0.jar (com.kaspersky.android-components:adbserver-device:1.0.0) and annotations-12.0.jar (com.intellij:annotations:12.0)
Duplicate class org.jetbrains.annotations.NonNls found in modules adbserver-device-1.0.0.jar (com.kaspersky.android-components:adbserver-device:1.0.0) and annotations-12.0.jar (com.intellij:annotations:12.0)
Duplicate class org.jetbrains.annotations.NotNull found in modules adbserver-device-1.0.0.jar (com.kaspersky.android-components:adbserver-device:1.0.0) and annotations-12.0.jar (com.intellij:annotations:12.0)
Duplicate class org.jetbrains.annotations.Nullable found in modules adbserver-device-1.0.0.jar (com.kaspersky.android-components:adbserver-device:1.0.0) and annotations-12.0.jar (com.intellij:annotations:12.0)
Duplicate class org.jetbrains.annotations.PropertyKey found in modules adbserver-device-1.0.0.jar (com.kaspersky.android-components:adbserver-device:1.0.0) and annotations-12.0.jar (com.intellij:annotations:12.0)
Duplicate class org.jetbrains.annotations.TestOnly found in modules adbserver-device-1.0.0.jar (com.kaspersky.android-components:adbserver-device:1.0.0) and annotations-12.0.jar (com.intellij:annotations:12.0)
I have tried to exclude org.jetbrains.annotations module from kaspresso dependency in implementing dependency process, but it couldn't help. Please, give me the way, how i can resolve that:)
My gradle code:
dependencies {
androidTestImplementation libraries.junitAndroid
androidTestImplementation( libraries.kaspresso ){
exclude(group: 'com.intellij', module: 'annotations')
}
androidTestImplementation libraries.espressoCore
androidTestImplementation libraries.uiAutomator
androidTestImplementation libraries.kakao
androidTestImplementation libraries.espressoWeb
androidTestImplementation libraries.espressoContrib
androidTestImplementation libraries.espressoIntents
}
dependencies.gradle:
junitAndroid : "androidx.test.ext:junit:1.1.1",
kaspresso : "com.kaspersky.android-components:kaspresso:1.0.1",
espressoCore : "androidx.test.espresso:espresso-core:3.2.0",
uiAutomator : "androidx.test.uiautomator:uiautomator:2.2.0",
kakao : "com.agoda.kakao:kakao:2.2.0,
espressoWeb : "androidx.test.espresso:espresso-web:3.2.0",
espressoContrib : "androidx.test.espresso:espresso-contrib:3.2.0",
espressoIntents : "androidx.test.espresso:espresso-intents:3.2.0",
Если попытаться сделать typetext в такой элемент, то ничего не впечатается
Set dokka to work with at least two modules: kaspresso and kautomator
When a developer writes a test, extends from BaseTestCase
/TestCase
with default Kaspresso settings and doesn't write inside the test permission granting rule then he can get an exception that permissions denied.
The possible solution is to catch such exceptions and output a detailed message where there will be an instruction to write permission granting rule.
Keyword:
java.io.FileNotFoundException: /storage/emulated/0/screenshots/run_1/com.kaspersky.kaspressample.simple_tests.SimpleTest/test/Additional_screenshot.png: open failed: EACCES (Permission denied)
The rule:
@get:Rule
val runtimePermissionRule: GrantPermissionRule = GrantPermissionRule.grant(
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE
)
Кейс:
Меня полностью устраивают настройки по умолчанию, но я хочу поменять лишь flakySafetyParams.timeoutMs
Как я сделал (первое, что пришло в голову):
kaspressoBuilder = Kaspresso.Builder.default().apply {
flakySafetyParams = FlakySafetyParams().apply {
timeoutMs = 5 * 1000L
}
}
Но это не отработало, т.к. viewBehaviorInterceptors создаётся в default() с FlakySafetyParams(), и моё создание нового объекта никак не влияет на него.
Чтобы viewBehaviorInterceptors использовал мои настройки, нужно делать:
kaspressoBuilder = Kaspresso.Builder.default().apply {
flakySafetyParams = flakySafetyParams.apply {
timeoutMs = 5 * 1000L
}
}
То есть менять существующий, а не создавать новый flakySafetyParams.
Мои предложения:
Мой кейс связан только с viewBehaviorInterceptors и flakySafetyParams, но это касается и других Interceptors и полей
as used in avito: https://github.com/avito-tech/android-ui-testing/blob/master/ui-testing-core/src/main/java/com/avito/android/test/element/field/actions/TypeText.kt
looks like better solution in every scenario.
Problem is InputConnection API is being closed as internal
cc @dimorinny as an author of this method
device.keyboard.typeText("йцуке")
throws
com.kaspersky.kaspresso.internal.exceptions.AdbServerException: command=input text й was performed with failed result=CommandResult(status=FAILED, description=exitCode=137, message=)
tested with:
Sometimes there is the need to execute some actions with BaseTestContext in every test in before/after sections. Annotations like @beforeEachTest or something else don't work because BaseTestContext is not available outside before-after-run sections.
Also, it will be more flexible when a developer has the ability to set/add/modify sets of start/end actions for all tests (or some tests) from a single place.
There is an idea to automate upgrade-tests.
Additional idea: to introduce a beauty dsl to work with ui-automator like with Espresso by Kakao. Separate issue - #21.
The simplest thing can be done to avoid the appearance of autofill dialog is to call context.getSystemService(AutofillManager::class.java).disableAutofillServices()
before the test starts. This can be done via implementing the DefaultTestRunWatcherInterceptor which will make such a call before every test.
Какие вещи подметил, и которые желательно отметить в доке:
@get:Rule
val runtimePermissionRule: GrantPermissionRule = GrantPermissionRule.grant(
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE
)
В Telegram-чате Kaspresso&AdbServer support [RU] обсуждали идею удобного и интуитивно понятного API для ситуаций, когда необходимо ожидать появления View в иерархии (аналог Espresso Idling Resource).
Проблематика: сейчас это можно реализовать с помощью flakySafely + метода isVisible(), но это неудобно (нужно оборачивать в before{ }.after{ }.run{ }), неочевидно и решает не совсем ту задачу, о которой я пишу.
Хотелось бы иметь возможность явно указать, что View может изначально не быть в иерархии (ViewNotFoundException). Какие варианты мне видятся наиболее лаконичными (в порядке убывания):
class MainActivityScreen : Screen<MainActivityScreen>() {
@Idling(maxWaitMillis = 60_000)
val moreBtn = KButton {withId(R.id.btn_more)}
}
@Test
fun mainActivityTest() {
mainActivityScreen{
moreBtn {
wait(maxWaitMillis = 60_000)
click()
}
}
}
Второй вариант делает чтение теста более понятным (т.к. аннотация находится в другом классе, ее не будет видно из класса с тестами, в отличие от метода wait()). Можно конечно заморочиться и для аннотированных объектов генерировать объект с именем moreBtnIdle и в тесте использовать уже его, но это полет фантазии уже)
Также часто возникают ситуации, когда нужно дождаться состояния уже существующего в иерархии View (например, дождаться состояния кнопки isEnabled, а затем кликнуть по ней).
Пусть это будет метод waitFor{} (waitForState{}, waitState{}) в который можно поместить assert-ы которые доступны только для объявленного KView (в данном случае KButton). Также должна быть возможность указать кастомные matcher-ы/assert-ы
Применив идею на примере выше, было бы отлично писать код в следующем стиле:
@Test
fun mainActivityTest() {
mainActivityScreen{
moreBtn {
wait(maxWaitMillis = 60_000) //или аннотация @Idling
waitFor(maxWaitMillis = 60_000) {
isEnabled()
}
click()
waitFor(maxWaitMillis = 60_000) {
withText(R.string.new_btn_text)
}
}
}
}
В результате тест можно было трактовать так: ждать появления в иерархии moreBtn максимум 60 секунд. После того, как кнопка появится, ждать состояния isEnabled и после этого выполнить по ней клик, после чего дождаться текста R.string.new_btn_text
Был бы очень рад такому API. Возможно, вы разовьете идею и придумаете еще более элегантное решение. Заранее спасибо)
Dsl will look outside and inside like Kakao plus all similar Interceptors.
There will be base classes like UaBaseView, UaBaseAssertions, UaBaseActions.
The first draft looks:
step("click") {
mainScreenFb(this) {
simpleButton {
click()
}
}
}
class MainScreenFb : ExtraScreen<MainScreenFb>() {
override val layoutId: Int? = R.layout.activity_main
override val viewClass: Class<*>? = MainActivity::class.java
val simpleButton = UiObjectSafe(
uiDevice,
composeProvider,
"com.kaspersky.kaspressample",
R.id::activity_main_button_simple.name
)
}
abstract class ExtraScreen<out T : ExtraScreen<T>> {
abstract val layoutId: Int?
abstract val viewClass: Class<*>?
protected lateinit var uiDevice: UiDevice
protected lateinit var composeProvider: ComposeProvider
@Suppress("UNCHECKED_CAST")
operator fun invoke(ref: BaseTestContext, function: T.() -> Unit) {
uiDevice = ref.device.uiDevice
composeProvider = ref
function.invoke(this as T)
}
}
class UiObjectSafe(
private val uiDevice: UiDevice,
private val composeProvider: ComposeProvider,
private val packageName: String,
private val resId: String
) {
operator fun invoke(function: UiObject2.() -> Unit) {
uiDevice.wait(
Until.findObject(
By.res(packageName, resId)
),
2000 // put timeOutMs from FlakySafetyParams
)
.apply(function) // put compose method wrapping over function, but it's a topic to think out
// we need to warn developers that they must put in lambda only one action
// otherwise the retry may be incorrect (one action is correct, second is not => compose retries two actions)
}
}
In case if we use in our tests checks like:
. . .
control {
hasText("bla bla")
}
. . .
And this check will be failed. We'll get output like:
E/KASPRESSO: All attempts to interact for 2000 ms totally failed because of AssertionFailedError
E/KASPRESSO: junit.framework.AssertionFailedError: 'view has effective visibility=VISIBLE' doesn't match the selected view.
Expected: bla bla
Got: CustomFontTextView{id=2131362469, res-name=text_sign_in_button, visibility=VISIBLE, width=0, height=0, has-focus=false, has-focusable=false, has-window-focus=true, is-clickable=false, is-enabled=true, is-focused=false, is-focusable=false, is-layout-requested=true, is-selected=false, layout-params=android.widget.LinearLayout$LayoutParams@cec54a4, tag=null, root-is-layout-requested=false, has-input-connection=false, x=0.0, y=0.0, text=Sign in, input-type=0, ime-target=false, has-links=false}
at dalvik.system.VMStack.getThreadStackTrace(Native Method)
Will be great to have standard hamcrest output like:
Expected: "bla bla"
Got: "ops it's devops =) "
Задача на @ele638
Ориентировочное время исполнения - 29.03.2020
For now each step is logged with generated step number in header.
As discussed in Telegram chat, it's nice to have ability to turn off auto-numeration.
Names of DocLoc screenshots are the following:
1570158949597_1__Simple_screen.png
1570158950868_2__Simple_fragment_-two_buttons.png
1570158954644_3__Simple_fragment-input.png
1570158956282_4__Simple_fragment-_typed_text.png
It's not readable and it prevents to automate some processes for DocLoc.
But "timems" is useful for common screenshotting during the test because it allows saving all screenshots of all attempts to pass the test.
Include a stage of source code compiling.
The reason. Sometimes, I've observed green build when the project didn't build.
Please add capturing of external screens for external screens.
On any test finished or on test failure.
It is possible with these commands:
adb shell uiautomator dump
adb pull /sdcard/window_dump.xml dump.xml
This will create dump.xml file with all elements on screen with their properties.
Steps to reproduce:
kaspressample.docloc_tests.ScreenshotSampleTest
to make it failing.Expected behavior: "fails" directory contains a failure screenshot.
Actual behavior: "fails" directory is empty. Logcat has java.lang.IllegalArgumentException: Could not find test class!
Tested on Emulator API 26, x86_64.
I tryed to write something like this:
AutologinScreen {
compose {
or(quickSignIn) { isNotDisplayed() }
or(quickSignIn) { click() }
}
}
But it returns Element not found for isNotDisplayed()
Kaspresso has flakySafely function for positive scenarios like check the dialog appears.
It does during some period cycle: look if dialog is visible, if not - wait some period and repeat. If visible - test goes out of flakySafely and continues.
But this fun will not work for negative scenarios, like check if dialog doesn't appear .
It could look like this: during some period cycle: look if dialog is not visible, if not - wait some period and repeat. If visible - test fails.
If we will add our check of the dialog not visible in flakySafely - it will check only first time and the test will continue.
Hey guys, new kakao version is released
https://github.com/agoda-com/Kakao/releases/tag/2.2.0
Do you wanna to update version in kaspresso?
Нужно настроить сборку и публикацию артефактов в автоматическом режиме.
Kaspresso.Builder
you have to use such code:open class KisaTestCase : TestCase(
kaspressoBuilder = Kaspresso.Builder.default().apply {
flakySafetyParams.apply {
timeoutMs = Time.Five.seconds
}
}
) {
It's a consequence of some incorrect order of variables in Kaspresso.Builder
.
We are going to fix it.
2. I have concluded to increase default flakySafetyParams.timeoutMs
param to 5 sec after some experiments.
Also, I'll change it.
Add the possibility to make FullView screenshots.
What are the FullView screenshots? When your feature's screen doesn't fit a device's screen then you have to make some screenshots scrolling your screen and, further, glue photos into a single screenshot.
Уже есть большинство оберток и примеров использования. Нужно добавить оставшиеся и написать на них тесты.
Сейчас невозможно использовать ресурсы R.id и R.string в Кавтоматоре. Неплохо было бы добавить поддержку этого, как в какао. Для тестов своего же приложения (апрейд-сценариев) можно копировать ресурсы с помощью Gradle-плагина (пример реализации есть в ButterKnife).
Hi ,
I my sample android app in java and wrote Kaspresso sample test in koltin .
I am facing unknown compilation issue .
But same test app works fine when sample android app is written in koltin .
So My query is direct .
Do Kaspresso support Java ?
It is hard to change something in the default Kaspresso.Builder without breaking of consistency. For example, to change a screenshots directory this code is required:
fun Kaspresso.Builder.changeScreenshotsDir(screenshotDir: File) {
screenshots = ScreenshotsImpl(libLogger, activities, screenshotDir)
stepWatcherInterceptors.replaceAll {
if (it is ScreenshotStepWatcherInterceptor) {
ScreenshotStepWatcherInterceptor(screenshots)
} else {
it
}
}
testRunWatcherInterceptors.replaceAll {
if (it is TestRunnerScreenshotWatcherInterceptor) {
TestRunnerScreenshotWatcherInterceptor(screenshots)
} else {
it
}
}
}
Without calls of replaceAll
interceptors will use old screenshots implementation.
Can this api be more convenient?
я скачал sample app. Пытаюсь запустить DeviceLocationSampleTest но он не проходит. Можете пожалуйста посмотреть что для неё нужно?
There is an idea to add callback to flakySafely function of actual timeout needed to pass the action.
Now timeouts in tests are set according to internal feeling of developer.
After such callback will be add there will be an opportunity to have a statistics on it and to configure actual timeout according to this statistics.
Therefore in case of fail tests will not wait for extra timeout and will fail earlier.
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.