Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue 249/compose support #307

Merged
merged 30 commits into from
Nov 15, 2021
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
438bd46
#249. Kakao Compose is included
matzuk Oct 25, 2021
0db17b6
#249. fixed some mistakes in Robolectric wiki
matzuk Nov 3, 2021
ed71945
#249. libraries update and including
matzuk Nov 3, 2021
56b727c
#249. main compose semantics interceptors are included to Kaspresso
matzuk Nov 4, 2021
c75259d
#249. Jetpack Compose Main page (template) in the sample is ready
matzuk Nov 4, 2021
6df379b
#249. The first test with Jetpack Compose in Kaspresso is ready
matzuk Nov 4, 2021
7211a8b
#249. Kautomator refactoring. loadView logic was removed because this…
matzuk Nov 5, 2021
2b90a5f
#249. Flaky defence is ready. Copy of ComposeOperations typealiases i…
matzuk Nov 6, 2021
61a7b45
#249. Improved stability of Kautomator due to changed logic of UiObje…
matzuk Nov 6, 2021
7a77c87
#249. Improved stability of Kautomator due to changed logic of UiObje…
matzuk Nov 6, 2021
49f1adb
#249. static onUiScreen method for Kamulator screens
matzuk Nov 6, 2021
3d7da93
#249. light changes in ComposeSimpleFlakyTest
matzuk Nov 6, 2021
a2094e1
#249. FailureLogging interceptors come back
matzuk Nov 6, 2021
d08bf45
#249. KakaoCompose 0.0.3 is included
matzuk Nov 7, 2021
e3a7a32
#249. Kaspresso compose is available for Jetpack Compose. ElementLoad…
matzuk Nov 7, 2021
0f028fa
#249. Robolectric support for Jetpack Compose
matzuk Nov 7, 2021
068f0b0
#249. small improvements in build.gradle
matzuk Nov 7, 2021
67860e2
#249. AutoScroll interceptor is ready
matzuk Nov 7, 2021
aeb5e5a
#249. detekt fixes
matzuk Nov 7, 2021
7b626a1
#249. Jetpack Compose readme
matzuk Nov 8, 2021
c8aa36b
#249 Jetpack Compose doc
matzuk Nov 8, 2021
44f3bbd
#249. Reverted the previous behavior of ObjectAutoScrollProviderImpl
matzuk Nov 14, 2021
1165160
#249. Changed the order of behavior interceptors to improve stability
matzuk Nov 14, 2021
dc905e6
#249. Up Kakao-Compose lib version
matzuk Nov 14, 2021
abf3983
#249. ElementLoaderParams
matzuk Nov 14, 2021
2edacd0
#249. reFindUiObject again
matzuk Nov 14, 2021
795dd5a
#249. provide libs.composeUiTest as api dependency
matzuk Nov 14, 2021
387549a
#249. renaming in VM
matzuk Nov 14, 2021
22aaa15
#249. up Kakao-Compose library version
matzuk Nov 15, 2021
60f586d
#249. readme changes
matzuk Nov 15, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,25 @@ configure<BaseExtension> {
named("androidTest").configure { java.srcDir("src/androidTest/kotlin") }
}

buildToolsVersion("29.0.3")
compileSdkVersion(29)
// temp solution of the following bug
// https://stackoverflow.com/questions/67358179/android-espresso-test-error-no-static-method-loadsingleserviceornull
// in the Kaspresso, it is caused by androidx.compose.ui:ui-test-junit4:1.0.3 in Kakao-compose that uses androidx.test.espresso:espresso-core:3.3.0 under the hood
// but we expect using of androidx.test.espresso:espresso-core:3.4.0
configurations.all {
resolutionStrategy {
force("androidx.test:monitor:1.4.0")
}
}

// temp solution
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@RuslanMingaliev need your help here

// appeared with kakao-compose library including
// similar to https://github.com/Kotlin/kotlinx.coroutines/issues/2023
packagingOptions {
exclude("META-INF/*")
}

buildToolsVersion("30.0.3")
compileSdkVersion(31)

compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,11 @@ android {
}
}

@Suppress("UnstableApiUsage")
onVariants {
androidTest {
enabled = false
}
}
// todo doesn't work on AGP 4.2.2
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@RuslanMingaliev such api is not available now

// @Suppress("UnstableApiUsage")
// onVariants {
// androidTest {
// enabled = false
// }
// }
}
46 changes: 34 additions & 12 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
[versions]
kotlin = "1.4.32"
kotlin = "1.5.21"
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1.5.31 causes weird problems, postponed

detekt = "1.18.1"
espresso = "3.3.0"
allure = "2.2.6"
espresso = "3.4.0"
allure = "2.2.7"
compose = "1.0.2"
activityCompose = "1.3.1"

[libraries]
# plugins
kotlinPlugin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" }
androidPlugin = "com.android.tools.build:gradle:4.1.0"
androidPlugin = "com.android.tools.build:gradle:4.2.2"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Мб нам его до 7 поднять?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I tried but I got a lot of errors with our plugins =(


# libraries
kotlinCli = "org.jetbrains.kotlinx:kotlinx-cli:0.3"
Expand All @@ -17,28 +19,47 @@ detektFormatting = { module = "io.gitlab.arturbosch.detekt:detekt-formatting", v
kotlinStdlib = { module = "org.jetbrains.kotlin:kotlin-stdlib", version.ref = "kotlin" }
espressoCore = { module = "androidx.test.espresso:espresso-core", version.ref = "espresso" }
espressoWeb = { module = "androidx.test.espresso:espresso-web", version.ref = "espresso" }
testCore = "androidx.test:core:1.3.0"
testCore = "androidx.test:core:1.4.0"
uiAutomator = "androidx.test.uiautomator:uiautomator:2.2.0"
fragmentTesting = "androidx.fragment:fragment-testing:1.3.2"
fragmentTesting = "androidx.fragment:fragment-testing:1.3.6"
# upgrading to 4.6.1 is blocked by https://github.com/robolectric/robolectric/issues/6593
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's not a bug, there is a solution in the comments

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am not sure that you are right. Ok, can you try to fix it?

robolectric = "org.robolectric:robolectric:4.5.1"

androidXCore = "androidx.core:core:1.3.0"
androidXRules = "androidx.test:rules:1.3.0"
androidXTest = "androidx.test.ext:junit:1.1.2"
androidXTestKtx = "androidx.test.ext:junit-ktx:1.1.2"
androidXCore = "androidx.core:core:1.7.0"

androidXRules = "androidx.test:rules:1.4.0"
androidXTest = "androidx.test.ext:junit:1.1.3"
androidXTestKtx = "androidx.test.ext:junit-ktx:1.1.3"

composeActivity = { module = "androidx.activity:activity-compose", version.ref = "activityCompose" }
composeUiTooling = { module = "androidx.compose.ui:ui-tooling", version.ref = "compose" }
composeMaterial = { module = "androidx.compose.material:material", version.ref = "compose" }
composeTestManifest = { module = "androidx.compose.ui:ui-test-manifest", version.ref = "compose" }
composeCompiler = { module = "androidx.compose.compiler:compiler", version.ref = "compose" }

composeUiTest = { module = "androidx.compose.ui:ui-test", version.ref = "compose" }

composeJunit = { module = "androidx.compose.ui:ui-test-junit4", version.ref = "compose" }

lifecycleViewModelKtx = "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0"
lifecycleLiveDataKtx = "androidx.lifecycle:lifecycle-livedata-ktx:2.4.0"
composeNavigation = "androidx.navigation:navigation-compose:2.4.0-beta02"
lifecycleViewModelComposeKtx = "androidx.lifecycle:lifecycle-viewmodel-compose:2.4.0"
composeRuntimeLiveData = "androidx.compose.runtime:runtime-livedata:1.0.5"

appcompat = "androidx.appcompat:appcompat:1.3.0"
material = "com.google.android.material:material:1.2.0"
constraint = "androidx.constraintlayout:constraintlayout:2.0.0"

kakao = "io.github.kakaocup:kakao:3.0.2"
kakao = "io.github.kakaocup:kakao:3.0.5"
kakaoCompose = "io.github.kakaocup:compose:0.0.2"
gson = "com.google.code.gson:gson:2.8.6"

junit = "junit:junit:4.13"
junitJupiter = "org.junit.jupiter:junit-jupiter:5.6.2"
truth = "com.google.truth:truth:1.0"
runner = "androidx.test:runner:1.3.0"
orchestrator = "androidx.test:orchestrator:1.3.0"
orchestrator = "androidx.test:orchestrator:1.4.0"

okhttp = "com.squareup.okhttp3:okhttp:4.9.1"
kotson = "com.github.salomonbrys.kotson:kotson:2.5.0"
Expand All @@ -51,3 +72,4 @@ allureKotlinAndroid = { module = "io.qameta.allure:allure-kotlin-android", versi
[bundles]
espresso = ["espressoCore", "espressoWeb"]
allure = ["allureKotlinModel", "allureKotlinCommons", "allureKotlinJunit4", "allureKotlinAndroid"]
compose = ["composeActivity", "composeUiTooling", "composeMaterial", "composeTestManifest", "composeCompiler"]
2 changes: 2 additions & 0 deletions kaspresso/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,12 @@ publish {
dependencies {
api(projects.kautomator)
api(libs.kakao)
// api(libs.kakaoCompose)
matzuk marked this conversation as resolved.
Show resolved Hide resolved
api(libs.bundles.espresso)
api(libs.uiAutomator)
api(libs.androidXCore)
api(libs.androidXRules)
api(files("libs/compose-debug.aar"))
matzuk marked this conversation as resolved.
Show resolved Hide resolved

implementation(libs.kotlinStdlib)
implementation(libs.gson)
Expand Down
Binary file added kaspresso/libs/compose-debug.aar
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -47,32 +47,16 @@ class ObjectAutoScrollProviderImpl(
* @return the result of [action] invocation.
*/
override fun <T> scroll(interaction: UiObjectInteraction, action: () -> T, cachedError: Throwable): T {
/**
* Looks for a scrollable content
*/
val scrollable = UiScrollable(UiSelector().scrollable(true))

/**
* Scrolls to the bottom and looks for the given view. Invokes the action if the view was found.
*/
do {
if (interaction.tryToFindUiObject()) {
logger.i("UiView autoscroll successfully performed.")
return action.invoke()
}
} while (scrollable.scrollForward())

/**
matzuk marked this conversation as resolved.
Show resolved Hide resolved
* Scrolls to the beginning and looks for the given view. Invokes the action if the view was found.
*/
do {
if (interaction.tryToFindUiObject()) {
logger.i("UiView autoscroll successfully performed.")
return action.invoke()
}
} while (scrollable.scrollBackward())

logger.i("UiObject autoscroll did not help. Throwing exception.")
throw cachedError
return try {
// Looks for a scrollable content
val scrollable = UiScrollable(UiSelector().scrollable(true))
// Scrolls to the bottom and looks for the given view. Invokes the action if the view was found.
scrollable.scrollForward()
logger.i("UiView autoscroll successfully performed.")
action.invoke()
} catch (error: Throwable) {
logger.i("UiObject autoscroll did not help. Throwing exception.")
throw cachedError
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.kaspersky.kaspresso.interceptors.behaviorcompose

interface ComposeBehaviorInterceptor<Interaction, Assertion, Action> {

/**
* Called to do some stuff and actually check an interaction with element.
*
* @param activity a function-wrapper of an action or an assertion to be invoked.
*/
fun <T> interceptCheck(interaction: Interaction, assertion: Assertion, activity: () -> T): T

/**
* Called to do some stuff and actually perform an interaction with element.
*
* @param activity a function-wrapper of an action or an assertion to be invoked.
*/
fun <T> interceptPerform(interaction: Interaction, action: Action, activity: () -> T): T
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.kaspersky.kaspresso.interceptors.behaviorcompose

import io.github.kakaocup.compose.intercept.interaction.ComposeInteraction
import io.github.kakaocup.compose.intercept.operation.ComposeAction
import io.github.kakaocup.compose.intercept.operation.ComposeAssertion

/**
* The derived from [ComposeBehaviorInterceptor] interface for intercepting [ComposeInteraction.perform] and
* [ComposeInteraction.check] behavior.
*/
interface SemanticsBehaviorInterceptor :
ComposeBehaviorInterceptor<ComposeInteraction, ComposeAssertion, ComposeAction>
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.kaspersky.kaspresso.interceptors.behaviorcompose.impl.failure

import com.kaspersky.kaspresso.failure.FailureLoggingProvider
import com.kaspersky.kaspresso.failure.FailureLoggingProviderImpl
import com.kaspersky.kaspresso.interceptors.behaviorcompose.SemanticsBehaviorInterceptor
import com.kaspersky.kaspresso.logger.UiTestLogger
import io.github.kakaocup.compose.intercept.interaction.ComposeInteraction
import io.github.kakaocup.compose.intercept.operation.ComposeAction
import io.github.kakaocup.compose.intercept.operation.ComposeAssertion

/**
* The implementation of [SemanticsBehaviorInterceptor] and [FailureLoggingProvider] interfaces.
* Provides failure logging functionality for [ComposeInteraction.perform] and [ComposeInteraction.check] calls.
*
* By default, this interceptor is not used in Kaspresso.
* If you desire to change result log (especially in case of an error) we recommend to use [FailureLoggingProvider] directly
*/
class FailureLoggingSemanticsBehaviorInterceptor(
logger: UiTestLogger
) : SemanticsBehaviorInterceptor,
FailureLoggingProvider by FailureLoggingProviderImpl(logger) {

/**
* Wraps the given [activity] invocation with the failure logging.
*
* @param interaction the intercepted [ComposeInteraction].
* @param assertion the intercepted [ComposeAssertion].
* @param activity the activity to invoke.
*/
override fun <T> interceptCheck(
interaction: ComposeInteraction,
assertion: ComposeAssertion,
activity: () -> T
): T = withLoggingOnFailure(action = activity)

/**
* Wraps the given [activity] invocation with the failure logging.
*
* @param interaction the intercepted [ComposeInteraction].
* @param action the intercepted [ComposeAction].
* @param activity the activity to invoke.
*/
override fun <T> interceptPerform(
interaction: ComposeInteraction,
action: ComposeAction,
activity: () -> T
): T = withLoggingOnFailure(action = activity)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.kaspersky.kaspresso.interceptors.behaviorcompose.impl.flakysafety

import com.kaspersky.kaspresso.flakysafety.FlakySafetyProvider
import com.kaspersky.kaspresso.flakysafety.FlakySafetyProviderSimpleImpl
import com.kaspersky.kaspresso.interceptors.behaviorcompose.SemanticsBehaviorInterceptor
import com.kaspersky.kaspresso.logger.UiTestLogger
import com.kaspersky.kaspresso.params.FlakySafetyParams
import io.github.kakaocup.compose.intercept.interaction.ComposeInteraction
import io.github.kakaocup.compose.intercept.operation.ComposeAction
import io.github.kakaocup.compose.intercept.operation.ComposeAssertion

/**
* The implementation of [SemanticsBehaviorInterceptor] and [FlakySafetyProvider] interfaces.
* Provides system flaky safety functionality for [ComposeInteraction.perform] and [ComposeInteraction.check] calls.
*/
class FlakySafeSemanticsBehaviorInterceptor(
params: FlakySafetyParams,
logger: UiTestLogger
) : SemanticsBehaviorInterceptor,
FlakySafetyProvider by FlakySafetyProviderSimpleImpl(params, logger) {

/**
* Wraps the given [activity] invocation with the flaky safety.
*
* @param interaction the intercepted [ComposeInteraction].
* @param assertion the intercepted [ComposeAssertion].
* @param activity the activity to invoke.
*/
override fun <T> interceptCheck(
interaction: ComposeInteraction,
assertion: ComposeAssertion,
activity: () -> T
): T = flakySafely(action = {
interaction.reFindNode()
activity.invoke()
})

/**
* Wraps the given [activity] invocation with the flaky safety.
*
* @param interaction the intercepted [ComposeInteraction].
* @param action the intercepted [ComposeAction].
* @param activity the activity to invoke.
*/
override fun <T> interceptPerform(
interaction: ComposeInteraction,
action: ComposeAction,
activity: () -> T
): T = flakySafely(action = {
interaction.reFindNode()
activity.invoke()
})
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package com.kaspersky.kaspresso.interceptors.behaviorcompose.impl.systemsafety

import com.kaspersky.kaspresso.device.server.AdbServer
import com.kaspersky.kaspresso.instrumental.InstrumentalDependencyProvider
import com.kaspersky.kaspresso.interceptors.behaviorcompose.SemanticsBehaviorInterceptor
import com.kaspersky.kaspresso.logger.UiTestLogger
import com.kaspersky.kaspresso.systemsafety.SystemDialogSafetyProvider
import com.kaspersky.kaspresso.systemsafety.SystemDialogSafetyProviderImpl
import io.github.kakaocup.compose.intercept.interaction.ComposeInteraction
import io.github.kakaocup.compose.intercept.operation.ComposeAction
import io.github.kakaocup.compose.intercept.operation.ComposeAssertion

/**
* The implementation of [SemanticsBehaviorInterceptor] and [SystemDialogSafetyProvider] interfaces.
* Provides system dialog safety functionality for [ComposeInteraction.perform] and [ComposeInteraction.check] calls.
*/
class SystemDialogSafetySemanticsBehaviorInterceptor(
logger: UiTestLogger,
instrumentalDependencyProvider: InstrumentalDependencyProvider,
adbServer: AdbServer
) : SemanticsBehaviorInterceptor,
SystemDialogSafetyProvider by SystemDialogSafetyProviderImpl(logger, instrumentalDependencyProvider, adbServer) {

/**
* Wraps the given [activity] invocation with the system dialog safety.
*
* @param interaction the intercepted [ComposeInteraction].
* @param assertion the intercepted [ComposeAssertion].
* @param activity the activity to invoke.
*/
override fun <T> interceptCheck(
interaction: ComposeInteraction,
assertion: ComposeAssertion,
activity: () -> T
): T = passSystemDialogs(activity)

/**
* Wraps the given [activity] invocation with the system dialog safety.
*
* @param interaction the intercepted [ComposeInteraction].
* @param action the intercepted [ComposeAction].
* @param activity the activity to invoke.
*/
override fun <T> interceptPerform(
interaction: ComposeInteraction,
action: ComposeAction,
activity: () -> T
): T = passSystemDialogs(activity)
}
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,10 @@ class FlakySafeObjectBehaviorInterceptor(
interaction: UiObjectInteraction,
assertion: UiObjectAssertion,
activity: () -> T
): T = flakySafely(action = activity)
): T = flakySafely(action = {
interaction.reFindUiObject()
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Improvements of Kautomator stability

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you describe the problem?

Copy link
Member Author

@matzuk matzuk Nov 12, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. This one must be relocated to a separate Interceptor. Thanks that you've highlighted.
  2. Good question about stability. So, the concept is similar to the one that we had in Loader...Interceptor. But, the architecture solution is better and the same with Jetpack Compose Intercepting (see old weird methods like loadView and etc.). Better stability is achieved due to changing the order of behavior interceptors (try to reload earlier). I think, there were cases when SystemDialog and Scrolling Interceptors consumed all allocated time, that's why there was no time to reload Element. But, I don't have tests where I can check it.
  3. Abstractions don't leak because BehaviorInterceptor offers access to all possible data (Interaction, Action, Assert). So, we give broad possibilities to do something with all of this. Also, I tried to reload an Element by using of custom getter where we recalculate the element. But, such a solution provides some edge cases like recalculation in absolutely all cases, possible caching of property that can lead to an inconsistent state, and others.

activity.invoke()
})

/**
* Wraps the given [activity] invocation with the flaky safety.
Expand All @@ -43,5 +46,8 @@ class FlakySafeObjectBehaviorInterceptor(
interaction: UiObjectInteraction,
action: UiObjectAction,
activity: () -> T
): T = flakySafely(action = activity)
): T = flakySafely(action = {
interaction.reFindUiObject()
activity.invoke()
})
}
Loading