diff --git a/compose-support/src/main/java/com/kaspersky/components/composesupport/config/ComposeConfig.kt b/compose-support/src/main/java/com/kaspersky/components/composesupport/config/ComposeConfig.kt index 5134f7fd3..48fbdcf9e 100644 --- a/compose-support/src/main/java/com/kaspersky/components/composesupport/config/ComposeConfig.kt +++ b/compose-support/src/main/java/com/kaspersky/components/composesupport/config/ComposeConfig.kt @@ -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 @@ -51,6 +52,9 @@ class ComposeConfig { FlakySafeSemanticsBehaviorInterceptor(flakySafetyParams, libLogger) ) } + + val composeFlakySafetyScalper = ComposeFlakySafetyScalper(semanticsBehaviorInterceptors, semanticsWatcherInterceptors) + externalFlakySafetyScalperNotifier.addScalper(composeFlakySafetyScalper) } fun build() { diff --git a/compose-support/src/main/java/com/kaspersky/components/composesupport/flakysafety/ComposeFlakySafetyScalper.kt b/compose-support/src/main/java/com/kaspersky/components/composesupport/flakysafety/ComposeFlakySafetyScalper.kt new file mode 100644 index 000000000..4ab7f4c49 --- /dev/null +++ b/compose-support/src/main/java/com/kaspersky/components/composesupport/flakysafety/ComposeFlakySafetyScalper.kt @@ -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, + private val semanticsWatcherInterceptors: MutableList, +) : 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) + } +} diff --git a/kaspresso/src/main/kotlin/com/kaspersky/kaspresso/flakysafety/scalpel/FlakySafeInterceptorScalpel.kt b/kaspresso/src/main/kotlin/com/kaspersky/kaspresso/flakysafety/scalpel/FlakySafeInterceptorScalpel.kt index ecf94d6a9..e30034a68 100644 --- a/kaspresso/src/main/kotlin/com/kaspersky/kaspresso/flakysafety/scalpel/FlakySafeInterceptorScalpel.kt +++ b/kaspresso/src/main/kotlin/com/kaspersky/kaspresso/flakysafety/scalpel/FlakySafeInterceptorScalpel.kt @@ -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( @@ -26,15 +25,14 @@ 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() = @@ -42,7 +40,8 @@ internal class FlakySafeInterceptorScalpel( kaspresso.dataBehaviorInterceptors.filterIsInstance().isNotEmpty() || kaspresso.webBehaviorInterceptors.filterIsInstance().isNotEmpty() || kaspresso.objectBehaviorInterceptors.filterIsInstance().isNotEmpty() || - kaspresso.deviceBehaviorInterceptors.filterIsInstance().isNotEmpty() + kaspresso.deviceBehaviorInterceptors.filterIsInstance().isNotEmpty() || + kaspresso.externalFlakySafetyScalperNotifier.isAnyExternalFlakySafetyInterceptorPresent() private fun scalpKakaoInterceptors() { val scalpedViewBehaviorInterceptors: List = @@ -58,7 +57,7 @@ internal class FlakySafeInterceptorScalpel( it !is FlakySafeWebBehaviorInterceptor } - injectKaspressoInKakao( + KakaoLibraryInjector.injectKaspressoInKakao( scalpedViewBehaviorInterceptors, scalpedDataBehaviorInterceptors, scalpedWebBehaviorInterceptors, @@ -80,7 +79,7 @@ internal class FlakySafeInterceptorScalpel( it !is FlakySafeDeviceBehaviorInterceptor } - injectKaspressoInKautomator( + KakaoLibraryInjector.injectKaspressoInKautomator( scalpedObjectBehaviorInterceptors, scalpedDeviceBehaviorInterceptors, kaspresso.objectWatcherInterceptors, @@ -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, @@ -109,6 +108,8 @@ internal class FlakySafeInterceptorScalpel( kaspresso.objectWatcherInterceptors, kaspresso.deviceWatcherInterceptors ) + + kaspresso.externalFlakySafetyScalperNotifier.restoreFlakySafety() } } } diff --git a/kaspresso/src/main/kotlin/com/kaspersky/kaspresso/flakysafety/scalpel/external/ExternalFlakySafetyScalper.kt b/kaspresso/src/main/kotlin/com/kaspersky/kaspresso/flakysafety/scalpel/external/ExternalFlakySafetyScalper.kt new file mode 100644 index 000000000..b612c26c5 --- /dev/null +++ b/kaspresso/src/main/kotlin/com/kaspersky/kaspresso/flakysafety/scalpel/external/ExternalFlakySafetyScalper.kt @@ -0,0 +1,7 @@ +package com.kaspersky.kaspresso.flakysafety.scalpel.external + +interface ExternalFlakySafetyScalper { + fun isFlakySafetyInterceptorPresent(): Boolean + fun scalpFlakySafety() + fun restoreFlakySafety() +} diff --git a/kaspresso/src/main/kotlin/com/kaspersky/kaspresso/flakysafety/scalpel/external/ExternalFlakySafetyScalperNotifier.kt b/kaspresso/src/main/kotlin/com/kaspersky/kaspresso/flakysafety/scalpel/external/ExternalFlakySafetyScalperNotifier.kt new file mode 100644 index 000000000..967990a37 --- /dev/null +++ b/kaspresso/src/main/kotlin/com/kaspersky/kaspresso/flakysafety/scalpel/external/ExternalFlakySafetyScalperNotifier.kt @@ -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() + + 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() + } +} diff --git a/kaspresso/src/main/kotlin/com/kaspersky/kaspresso/kaspresso/Kaspresso.kt b/kaspresso/src/main/kotlin/com/kaspersky/kaspresso/kaspresso/Kaspresso.kt index 0da5892b4..d571ec532 100644 --- a/kaspresso/src/main/kotlin/com/kaspersky/kaspresso/kaspresso/Kaspresso.kt +++ b/kaspresso/src/main/kotlin/com/kaspersky/kaspresso/kaspresso/Kaspresso.kt @@ -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 @@ -162,6 +164,7 @@ data class Kaspresso( internal val testRunWatcherInterceptors: List, internal val resourceFilesProvider: ResourceFilesProvider, internal val visualTestWatcher: VisualTestWatcher, + internal val externalFlakySafetyScalperNotifier: ExternalFlakySafetyScalperNotifier, ) { companion object { @@ -674,6 +677,13 @@ data class Kaspresso( */ lateinit var testRunWatcherInterceptors: MutableList + /** + * 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. @@ -964,6 +974,8 @@ data class Kaspresso( defaultsTestRunWatcherInterceptor ) + if (!::externalFlakySafetyScalperNotifier.isInitialized) externalFlakySafetyScalperNotifier = ExternalFlakySafetyScalperNotifierImpl() + if (artifactsPullParams.enabled) { instrumentalDependencyProviderFactory.getComponentProvider(instrumentation).runNotifier.addUniqueListener { ArtifactsPullRunListener(params = artifactsPullParams, files = files, logger = libLogger) @@ -1040,6 +1052,7 @@ data class Kaspresso( stepWatcherInterceptors = stepWatcherInterceptors, testRunWatcherInterceptors = testRunWatcherInterceptors, + externalFlakySafetyScalperNotifier = externalFlakySafetyScalperNotifier, visualTestWatcher = visualTestWatcher, )