Skip to content

Commit

Permalink
ISSUE-646: Clean semantics flaky safety interceptor (#670)
Browse files Browse the repository at this point in the history
* ISSUE-646: Clean semantics flaky safety interceptor

* ISSUE-646: Docs

* ISSUE-646: Add synchronized block

* ISSUE-646: Make ComposeFlakySafetyScalper internal
  • Loading branch information
Nikitae57 authored Dec 12, 2024
1 parent 03a2d0f commit 03c1ed6
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 16 deletions.
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) {
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

0 comments on commit 03c1ed6

Please sign in to comment.