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-646: Clean semantics flaky safety interceptor #670

Merged
merged 5 commits into from
Dec 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
@@ -1,5 +1,6 @@
package com.kaspersky.components.composesupport.config

import com.kaspersky.components.composesupport.flakysafety.ComposeFlakySafetyScalper
import com.kaspersky.components.composesupport.interceptors.behavior.SemanticsBehaviorInterceptor
import com.kaspersky.components.composesupport.interceptors.behavior.impl.autoscroll.AutoScrollSemanticsBehaviorInterceptor
import com.kaspersky.components.composesupport.interceptors.behavior.impl.elementloader.ElementLoaderSemanticsBehaviorInterceptor
Expand Down Expand Up @@ -51,6 +52,9 @@ class ComposeConfig {
FlakySafeSemanticsBehaviorInterceptor(flakySafetyParams, libLogger)
)
}

val composeFlakySafetyScalper = ComposeFlakySafetyScalper(semanticsBehaviorInterceptors, semanticsWatcherInterceptors)
externalFlakySafetyScalperNotifier.addScalper(composeFlakySafetyScalper)
}

fun build() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package com.kaspersky.components.composesupport.flakysafety

import com.kaspersky.components.composesupport.config.ComposeInterceptorsInjector
import com.kaspersky.components.composesupport.interceptors.behavior.SemanticsBehaviorInterceptor
import com.kaspersky.components.composesupport.interceptors.behavior.impl.flakysafety.FlakySafeSemanticsBehaviorInterceptor
import com.kaspersky.components.composesupport.interceptors.watcher.SemanticsWatcherInterceptor
import com.kaspersky.kaspresso.flakysafety.scalpel.external.ExternalFlakySafetyScalper

/**
* Removes and restores compose flaky safety interceptor so the `flakySafely` expression works correctly
* @see com.kaspersky.kaspresso.flakysafety.scalpel.FlakySafeInterceptorScalpel
*/
internal class ComposeFlakySafetyScalper(
private val semanticsBehaviorInterceptors: MutableList<SemanticsBehaviorInterceptor>,
private val semanticsWatcherInterceptors: MutableList<SemanticsWatcherInterceptor>,
) : ExternalFlakySafetyScalper {
override fun isFlakySafetyInterceptorPresent(): Boolean {
return semanticsBehaviorInterceptors.any { it is FlakySafeSemanticsBehaviorInterceptor }
}

override fun scalpFlakySafety() {
val interceptors = semanticsBehaviorInterceptors.filter { it !is FlakySafeSemanticsBehaviorInterceptor }
ComposeInterceptorsInjector.injectKaspressoInKakaoCompose(interceptors, semanticsWatcherInterceptors)
}

override fun restoreFlakySafety() {
ComposeInterceptorsInjector.injectKaspressoInKakaoCompose(semanticsBehaviorInterceptors, semanticsWatcherInterceptors)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,12 @@ import com.kaspersky.kaspresso.interceptors.behaviorkautomator.DeviceBehaviorInt
import com.kaspersky.kaspresso.interceptors.behaviorkautomator.ObjectBehaviorInterceptor
import com.kaspersky.kaspresso.interceptors.behaviorkautomator.impl.flakysafety.FlakySafeDeviceBehaviorInterceptor
import com.kaspersky.kaspresso.interceptors.behaviorkautomator.impl.flakysafety.FlakySafeObjectBehaviorInterceptor
import com.kaspersky.kaspresso.interceptors.tolibrary.KakaoLibraryInjector.injectKaspressoInKakao
import com.kaspersky.kaspresso.interceptors.tolibrary.KakaoLibraryInjector.injectKaspressoInKautomator
import com.kaspersky.kaspresso.interceptors.tolibrary.KakaoLibraryInjector
import com.kaspersky.kaspresso.kaspresso.Kaspresso
import java.util.concurrent.atomic.AtomicInteger

/**
* The special class that removes all interceptors related to FlakySafety from Kautomator settings
* The special class that removes all interceptors related to FlakySafety from kakao settings
* and restore them by demand
*/
internal class FlakySafeInterceptorScalpel(
Expand All @@ -26,23 +25,23 @@ internal class FlakySafeInterceptorScalpel(
private val entriesCount = AtomicInteger()

fun scalpFromLibs() {
if (entriesCount.getAndIncrement() == 0) {
scalpelSwitcher.attemptTakeScalp(
actionToDetermineScalp = { determineScalpExistingInKaspresso() },
actionToTakeScalp = {
scalpKakaoInterceptors()
scalpKautomatorInterceptors()
}
)
}
if (entriesCount.getAndIncrement() == 0) {scalpelSwitcher.attemptTakeScalp(
actionToDetermineScalp = { determineScalpExistingInKaspresso() },
actionToTakeScalp = {
scalpKakaoInterceptors()
scalpKautomatorInterceptors()
kaspresso.externalFlakySafetyScalperNotifier.scalpFlakySafety()
}
)}
}

private fun determineScalpExistingInKaspresso() =
kaspresso.viewBehaviorInterceptors.filterIsInstance<FlakySafeViewBehaviorInterceptor>().isNotEmpty() ||
kaspresso.dataBehaviorInterceptors.filterIsInstance<FlakySafeDataBehaviorInterceptor>().isNotEmpty() ||
kaspresso.webBehaviorInterceptors.filterIsInstance<FlakySafeWebBehaviorInterceptor>().isNotEmpty() ||
kaspresso.objectBehaviorInterceptors.filterIsInstance<FlakySafeObjectBehaviorInterceptor>().isNotEmpty() ||
kaspresso.deviceBehaviorInterceptors.filterIsInstance<FlakySafeDeviceBehaviorInterceptor>().isNotEmpty()
kaspresso.deviceBehaviorInterceptors.filterIsInstance<FlakySafeDeviceBehaviorInterceptor>().isNotEmpty() ||
kaspresso.externalFlakySafetyScalperNotifier.isAnyExternalFlakySafetyInterceptorPresent()

private fun scalpKakaoInterceptors() {
val scalpedViewBehaviorInterceptors: List<ViewBehaviorInterceptor> =
Expand All @@ -58,7 +57,7 @@ internal class FlakySafeInterceptorScalpel(
it !is FlakySafeWebBehaviorInterceptor
}

injectKaspressoInKakao(
KakaoLibraryInjector.injectKaspressoInKakao(
scalpedViewBehaviorInterceptors,
scalpedDataBehaviorInterceptors,
scalpedWebBehaviorInterceptors,
Expand All @@ -80,7 +79,7 @@ internal class FlakySafeInterceptorScalpel(
it !is FlakySafeDeviceBehaviorInterceptor
}

injectKaspressoInKautomator(
KakaoLibraryInjector.injectKaspressoInKautomator(
scalpedObjectBehaviorInterceptors,
scalpedDeviceBehaviorInterceptors,
kaspresso.objectWatcherInterceptors,
Expand All @@ -92,7 +91,7 @@ internal class FlakySafeInterceptorScalpel(
val nestingDepth = entriesCount.decrementAndGet()
if (nestingDepth <= 0) { // prevent restoring the interceptors in case if a "flakySafely" block is nested in an another "flakySafely"
scalpelSwitcher.attemptRestoreScalp {
injectKaspressoInKakao(
KakaoLibraryInjector.injectKaspressoInKakao(
kaspresso.viewBehaviorInterceptors,
kaspresso.dataBehaviorInterceptors,
kaspresso.webBehaviorInterceptors,
Expand All @@ -109,6 +108,8 @@ internal class FlakySafeInterceptorScalpel(
kaspresso.objectWatcherInterceptors,
kaspresso.deviceWatcherInterceptors
)

kaspresso.externalFlakySafetyScalperNotifier.restoreFlakySafety()
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.kaspersky.kaspresso.flakysafety.scalpel.external

interface ExternalFlakySafetyScalper {
fun isFlakySafetyInterceptorPresent(): Boolean
fun scalpFlakySafety()
fun restoreFlakySafety()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.kaspersky.kaspresso.flakysafety.scalpel.external

interface ExternalFlakySafetyScalperNotifier {
fun addScalper(scalper: ExternalFlakySafetyScalper)

fun isAnyExternalFlakySafetyInterceptorPresent(): Boolean
fun scalpFlakySafety()
fun restoreFlakySafety()
}

internal class ExternalFlakySafetyScalperNotifierImpl : ExternalFlakySafetyScalperNotifier {
private val scalpers = mutableListOf<ExternalFlakySafetyScalper>()

override fun addScalper(scalper: ExternalFlakySafetyScalper) {
synchronized(this) {
Copy link
Collaborator

Choose a reason for hiding this comment

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

maybe we should synchronized not only write methods?

scalpers.add(scalper)
}
}

override fun isAnyExternalFlakySafetyInterceptorPresent(): Boolean {
synchronized(this) {
return scalpers.any { it.isFlakySafetyInterceptorPresent() }
}
}

override fun scalpFlakySafety() = scalpers.forEach {
it.scalpFlakySafety()
}

override fun restoreFlakySafety() = scalpers.forEach {
it.restoreFlakySafety()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@ import com.kaspersky.kaspresso.files.resources.impl.DefaultResourceFilesProvider
import com.kaspersky.kaspresso.files.resources.impl.DefaultResourcesDirNameProvider
import com.kaspersky.kaspresso.files.resources.impl.DefaultResourcesDirsProvider
import com.kaspersky.kaspresso.files.resources.impl.DefaultResourcesRootDirsProvider
import com.kaspersky.kaspresso.flakysafety.scalpel.external.ExternalFlakySafetyScalperNotifier
import com.kaspersky.kaspresso.flakysafety.scalpel.external.ExternalFlakySafetyScalperNotifierImpl
import com.kaspersky.kaspresso.idlewaiting.KautomatorWaitForIdleSettings
import com.kaspersky.kaspresso.instrumental.InstrumentalDependencyProvider
import com.kaspersky.kaspresso.instrumental.InstrumentalDependencyProviderFactory
Expand Down Expand Up @@ -162,6 +164,7 @@ data class Kaspresso(
internal val testRunWatcherInterceptors: List<TestRunWatcherInterceptor>,
internal val resourceFilesProvider: ResourceFilesProvider,
internal val visualTestWatcher: VisualTestWatcher,
internal val externalFlakySafetyScalperNotifier: ExternalFlakySafetyScalperNotifier,
) {

companion object {
Expand Down Expand Up @@ -674,6 +677,13 @@ data class Kaspresso(
*/
lateinit var testRunWatcherInterceptors: MutableList<TestRunWatcherInterceptor>

/**
* Holds a reference to the custom "external" flaky safety scalpers that are not set in the kaspresso by default
* @see com.kaspersky.kaspresso.flakysafety.scalpel.FlakySafeInterceptorScalpel
* @see com.kaspersky.kaspresso.flakysafety.scalpel.external.ExternalFlakySafetyScalper
*/
lateinit var externalFlakySafetyScalperNotifier: ExternalFlakySafetyScalperNotifier

/**
* Holds the implementation of the [androidx.test.espresso.FailureHandler] interface, that is called on every
* failure.
Expand Down Expand Up @@ -964,6 +974,8 @@ data class Kaspresso(
defaultsTestRunWatcherInterceptor
)

if (!::externalFlakySafetyScalperNotifier.isInitialized) externalFlakySafetyScalperNotifier = ExternalFlakySafetyScalperNotifierImpl()

if (artifactsPullParams.enabled) {
instrumentalDependencyProviderFactory.getComponentProvider<Kaspresso>(instrumentation).runNotifier.addUniqueListener {
ArtifactsPullRunListener(params = artifactsPullParams, files = files, logger = libLogger)
Expand Down Expand Up @@ -1040,6 +1052,7 @@ data class Kaspresso(

stepWatcherInterceptors = stepWatcherInterceptors,
testRunWatcherInterceptors = testRunWatcherInterceptors,
externalFlakySafetyScalperNotifier = externalFlakySafetyScalperNotifier,
visualTestWatcher = visualTestWatcher,
)

Expand Down
Loading