Skip to content

Commit

Permalink
Merge branch 'master' into ISSUE-646-Clean-flaky-safety-interceptor
Browse files Browse the repository at this point in the history
# Conflicts:
#	kaspresso/src/main/kotlin/com/kaspersky/kaspresso/flakysafety/scalpel/FlakySafeInterceptorScalpel.kt
#	kaspresso/src/main/kotlin/com/kaspersky/kaspresso/kaspresso/Kaspresso.kt
  • Loading branch information
Nikitae57 committed Dec 12, 2024
2 parents 360920b + a55eaa2 commit 68b0116
Show file tree
Hide file tree
Showing 59 changed files with 836 additions and 109 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ package com.kaspersky.adbserver.commandtypes

import com.kaspersky.adbserver.common.api.Command

/**
* Command for backward compatibility with old version of adb-server
*/
data class AdbCommand(
override val command: String,
override val arguments: List<String> = emptyList()
) : Command(command, arguments)
override val body: String,
) : Command(body)
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package com.kaspersky.adbserver.commandtypes

import com.kaspersky.adbserver.common.api.Command
import com.kaspersky.adbserver.common.api.ComplexCommand

data class CmdCommand(
override val command: String,
override val body: String,
override val arguments: List<String> = emptyList()
) : Command(command, arguments)
) : ComplexCommand(body, arguments)
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.kaspersky.adbserver.commandtypes

import com.kaspersky.adbserver.common.api.ComplexCommand

data class ComplexAdbCommand(
override val body: String,
override val arguments: List<String> = emptyList()
) : ComplexCommand(body, arguments)
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ import java.io.Serializable
/**
* Command to execute by AdbServer
*/
abstract class Command(open val command: String, open val arguments: List<String> = emptyList()) : Serializable
abstract class Command(open val body: String) : Serializable
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package com.kaspersky.adbserver.common.api

abstract class ComplexCommand(override val body: String, open val arguments: List<String> = emptyList()) : Command(body)
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package com.kaspersky.adbserver.device

import com.kaspersky.adbserver.commandtypes.AdbCommand
import com.kaspersky.adbserver.commandtypes.CmdCommand
import com.kaspersky.adbserver.commandtypes.ComplexAdbCommand
import com.kaspersky.adbserver.common.api.CommandResult
import com.kaspersky.adbserver.common.log.LoggerFactory
import com.kaspersky.adbserver.common.log.logger.LogLevel
Expand Down Expand Up @@ -35,7 +36,7 @@ object AdbTerminal {
* Please first of all call [connect] method to establish a connection
*/
fun executeAdb(command: String, arguments: List<String>): CommandResult = device?.fulfill(
AdbCommand(command, arguments)
ComplexAdbCommand(command, arguments)
) ?: throw IllegalStateException("Please first of all call [connect] method to establish a connection")

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import com.kaspersky.adbserver.common.api.CommandExecutor
import com.kaspersky.adbserver.common.api.CommandResult
import com.kaspersky.adbserver.commandtypes.AdbCommand
import com.kaspersky.adbserver.commandtypes.CmdCommand
import com.kaspersky.adbserver.commandtypes.ComplexAdbCommand
import com.kaspersky.adbserver.common.log.logger.Logger
import java.lang.UnsupportedOperationException

Expand All @@ -16,16 +17,24 @@ internal class CommandExecutorImpl(
private val adbPath: String
) : CommandExecutor {

private fun getSimpleAdbCommand(command: Command): String = "$adbPath ${adbServerPort?.let { "-P $adbServerPort " } ?: ""}-s $deviceName ${command.body}"

override fun execute(command: Command): CommandResult {
return when (command) {
is CmdCommand -> cmdCommandPerformer.perform(command.command, command.arguments)
is CmdCommand -> cmdCommandPerformer.perform(command.body, command.arguments)

is AdbCommand -> {
val adbCommand = getSimpleAdbCommand(command)
logger.d("The created adbCommand=$adbCommand")
cmdCommandPerformer.perform(adbCommand, emptyList())
}

is ComplexAdbCommand -> {
val adbCommand: String
val adbArguments: List<String>

if (command.arguments.isEmpty()) {
adbCommand = "$adbPath ${adbServerPort?.let { "-P $adbServerPort " } ?: ""}-s $deviceName ${command.command}"
adbCommand = getSimpleAdbCommand(command)
adbArguments = emptyList()
} else {
adbCommand = adbPath
Expand All @@ -36,7 +45,7 @@ internal class CommandExecutorImpl(
}
add("-s")
add(deviceName)
add(command.command)
add(command.body)
addAll(command.arguments)
}
}
Expand Down
1 change: 1 addition & 0 deletions allure-support/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ dependencies {
api(libs.bundles.allure)

implementation(projects.kaspresso)
implementation(projects.adbServer.adbServerCommon)

implementation(libs.kotlinStdlib)
implementation(libs.truth)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package com.kaspersky.components.alluresupport

import com.kaspersky.adbserver.common.log.logger.LogLevel
import com.kaspersky.components.alluresupport.files.dirs.AllureDirsProvider
import com.kaspersky.components.alluresupport.files.resources.impl.AllureResourceFilesProvider
import com.kaspersky.components.alluresupport.files.resources.impl.DefaultAllureResourcesRootDirsProvider
Expand All @@ -10,17 +11,26 @@ import com.kaspersky.components.alluresupport.interceptors.testrun.DumpViewsTest
import com.kaspersky.components.alluresupport.interceptors.testrun.HackyVideoRecordingTestInterceptor
import com.kaspersky.components.alluresupport.interceptors.testrun.ScreenshotTestInterceptor
import com.kaspersky.components.alluresupport.interceptors.testrun.VideoRecordingTestInterceptor
import com.kaspersky.components.alluresupport.interceptors.testrun.VisualTestLateFailInterceptor
import com.kaspersky.components.alluresupport.results.AllureResultsHack
import com.kaspersky.components.alluresupport.runlisteners.AllureRunListener
import com.kaspersky.components.alluresupport.visual.AllureScreenshotsComparator
import com.kaspersky.components.alluresupport.visual.AllureVisualTestWatcher
import com.kaspersky.kaspresso.BuildConfig
import com.kaspersky.kaspresso.device.files.FilesImpl
import com.kaspersky.kaspresso.device.server.AdbServerImpl
import com.kaspersky.kaspresso.files.dirs.DefaultDirsProvider
import com.kaspersky.kaspresso.files.resources.impl.DefaultResourceFileNamesProvider
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.instrumental.InstrumentalDependencyProvider
import com.kaspersky.kaspresso.kaspresso.Kaspresso
import com.kaspersky.kaspresso.logger.UiTestLoggerImpl
import com.kaspersky.kaspresso.runner.listener.addUniqueListener
import com.kaspersky.kaspresso.runner.listener.getUniqueListener
import com.kaspersky.kaspresso.visual.VisualTestParams
import com.kaspersky.kaspresso.visual.VisualTestType

/**
* Kaspresso Builder that includes all appropriate interceptors to support rich Allure reports.
Expand Down Expand Up @@ -64,6 +74,7 @@ fun Kaspresso.Builder.addAllureSupport(): Kaspresso.Builder = apply {
*/
fun Kaspresso.Builder.Companion.withForcedAllureSupport(
shouldRecordVideo: Boolean = true,
visualTestParams: VisualTestParams = VisualTestParams(testType = VisualTestType.valueOf(BuildConfig.VISUAL_TEST_TYPE)),
customize: Kaspresso.Builder.() -> Unit = {}
): Kaspresso.Builder = simple {
if (!isAndroidRuntime) {
Expand All @@ -72,6 +83,7 @@ fun Kaspresso.Builder.Companion.withForcedAllureSupport(
customize.invoke(this)
val instrumentalDependencyProvider = instrumentalDependencyProviderFactory.getComponentProvider<Kaspresso>(instrumentation)
forceAllureSupportFileProviders(instrumentalDependencyProvider)
initVisualTestParams(visualTestParams)
addRunListenersIfNeeded(instrumentalDependencyProvider)
}.apply {
postInitAllure(shouldRecordVideo, builder = this)
Expand All @@ -98,14 +110,32 @@ private fun Kaspresso.Builder.forceAllureSupportFileProviders(provider: Instrume
resourceFilesProvider = allureResourcesFilesProvider
}

private fun Kaspresso.Builder.initVisualTestParams(visualParams: VisualTestParams) {
visualTestParams = visualParams
testLogger = UiTestLoggerImpl(Kaspresso.DEFAULT_TEST_LOGGER_TAG)
libLogger = UiTestLoggerImpl(Kaspresso.DEFAULT_LIB_LOGGER_TAG)

screenshotsComparator = AllureScreenshotsComparator(
visualTestParams,
testLogger,
resourcesRootDirsProvider,
resourcesDirsProvider,
resourceFileNamesProvider,
)
adbServer = AdbServerImpl(LogLevel.WARN, libLogger)
files = FilesImpl(libLogger, adbServer)
visualTestWatcher = AllureVisualTestWatcher(visualTestParams, testLogger, (dirsProvider as AllureDirsProvider), resourcesRootDirsProvider, files)
}

private fun Kaspresso.Builder.addRunListenersIfNeeded(provider: InstrumentalDependencyProvider) {
provider.runNotifier.apply {
addUniqueListener(::AllureRunListener)
addUniqueListener {
AllureResultsHack(
uiDevice = provider.uiDevice,
resourcesRootDirsProvider = resourcesRootDirsProvider as DefaultAllureResourcesRootDirsProvider,
dirsProvider = dirsProvider as AllureDirsProvider
dirsProvider = dirsProvider as AllureDirsProvider,
visualTestParams = visualTestParams,
)
}
}
Expand All @@ -126,6 +156,7 @@ private fun postInitAllure(shouldRecordVideo: Boolean, builder: Kaspresso.Builde
DumpLogcatTestInterceptor(logcatDumper),
ScreenshotTestInterceptor(screenshots),
DumpViewsTestInterceptor(viewHierarchyDumper),
VisualTestLateFailInterceptor(),
)
)
if (shouldRecordVideo) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.kaspersky.components.alluresupport.interceptors.testrun

import com.kaspersky.components.alluresupport.results.AllureVisualTestFlag
import com.kaspersky.kaspresso.device.screenshots.ScreenshotsImpl
import com.kaspersky.kaspresso.interceptors.watcher.testcase.TestRunWatcherInterceptor
import com.kaspersky.kaspresso.testcases.models.info.TestInfo

class VisualTestLateFailInterceptor : TestRunWatcherInterceptor {
override fun onAfterSectionStarted(testInfo: TestInfo) {
if (AllureVisualTestFlag.shouldFailLate.get()) {
// Wrap with assertion error so test would be marked as FAILED instead of BROKEN
// See https://github.com/allure-framework/allure-kotlin allure-kotlin-commons/src/main/kotlin/io/qameta/allure/kotlin/util/ResultsUtils.kt
throw AssertionError(ScreenshotsImpl.ScreenshotDoesntMatchException("There were failed screenshot comparisons. Check the allure report"))
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,17 @@ import com.kaspersky.components.alluresupport.files.dirs.AllureDirsProvider
import com.kaspersky.components.alluresupport.files.resources.AllureResourcesRootDirsProvider
import com.kaspersky.components.alluresupport.files.resources.impl.AllureResourceFilesProvider
import com.kaspersky.kaspresso.runner.listener.KaspressoRunListener
import com.kaspersky.kaspresso.visual.VisualTestParams
import com.kaspersky.kaspresso.visual.VisualTestType
import io.qameta.allure.kotlin.Allure
import org.junit.runner.Result
import java.io.File

class AllureResultsHack(
private val uiDevice: UiDevice,
private val visualTestParams: VisualTestParams,
resourcesRootDirsProvider: AllureResourcesRootDirsProvider,
dirsProvider: AllureDirsProvider,
private val dirsProvider: AllureDirsProvider,
) : KaspressoRunListener {

private val allureResultsSourceDir: File =
Expand Down Expand Up @@ -48,6 +51,13 @@ class AllureResultsHack(

allureResultsSourceDir.deleteRecursively()
stubVideosDir.deleteRecursively()

if (visualTestParams.testType == VisualTestType.Record) {
val rootDir = dirsProvider.provideNew(File("")).absolutePath
val newScreenshotsDir = File(rootDir, File(visualTestParams.hostScreenshotsDir).name)
val targetScreenshotsDir = dirsProvider.provideNewOnSdCard(File(visualTestParams.hostScreenshotsDir))
newScreenshotsDir.copyRecursively(targetScreenshotsDir)
}
}

data class VideoBinding(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.kaspersky.components.alluresupport.results

import java.util.concurrent.atomic.AtomicBoolean

// TODO(Nikita Evdokimov) - Certainly there should be a better way
/**
* @see com.kaspersky.components.alluresupport.interceptors.testrun.VisualTestLateFailInterceptor
*/
object AllureVisualTestFlag {
val shouldFailLate = AtomicBoolean(false)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.kaspersky.components.alluresupport.visual

import android.graphics.Bitmap
import com.kaspersky.components.alluresupport.files.attachScreenshotToAllureReport
import com.kaspersky.kaspresso.files.resources.ResourceFileNamesProvider
import com.kaspersky.kaspresso.files.resources.ResourcesDirsProvider
import com.kaspersky.kaspresso.files.resources.ResourcesRootDirsProvider
import com.kaspersky.kaspresso.internal.visual.DefaultScreenshotsComparator
import com.kaspersky.kaspresso.logger.Logger
import com.kaspersky.kaspresso.visual.VisualTestParams
import java.io.File

class AllureScreenshotsComparator(
visualTestParams: VisualTestParams,
logger: Logger,
resourcesRootDirsProvider: ResourcesRootDirsProvider,
resourcesDirsProvider: ResourcesDirsProvider,
resourceFileNamesProvider: ResourceFileNamesProvider,
) : DefaultScreenshotsComparator(visualTestParams, logger, resourcesRootDirsProvider, resourcesDirsProvider, resourceFileNamesProvider) {
override fun compare(originalScreenshot: File, newScreenshot: File): Boolean {
val doScreenshotsMatch = super.compare(originalScreenshot, newScreenshot)
if (!doScreenshotsMatch) {
originalScreenshot.attachScreenshotToAllureReport()
newScreenshot.attachScreenshotToAllureReport()
}

return doScreenshotsMatch
}

override fun processScreenshotDiff(original: Bitmap, diffPixels: IntArray, diffName: String): File {
return super.processScreenshotDiff(original, diffPixels, diffName).also {
it.attachScreenshotToAllureReport()
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package com.kaspersky.components.alluresupport.visual

import com.kaspersky.components.alluresupport.results.AllureVisualTestFlag
import com.kaspersky.components.alluresupport.withForcedAllureSupport
import com.kaspersky.kaspresso.device.screenshots.ScreenshotsImpl
import com.kaspersky.kaspresso.kaspresso.Kaspresso
import com.kaspersky.kaspresso.testcases.api.testcase.VisualTestCase
import io.qameta.allure.kotlin.Allure
import io.qameta.allure.kotlin.model.Status
import io.qameta.allure.kotlin.model.StatusDetails

abstract class AllureVisualTestCase(
private val failEarly: Boolean = false,
kaspressoBuilder: Kaspresso.Builder = Kaspresso.Builder.withForcedAllureSupport()
) : VisualTestCase(kaspressoBuilder = kaspressoBuilder) {

override fun assertScreenshot(tag: String, isFullWindow: Boolean) {
try {
device.screenshots.assert(tag, isFullWindow)
} catch (ex: ScreenshotsImpl.ScreenshotDoesntMatchException) {
if (failEarly) {
// Wrap with assertion error so test would be marked as FAILED instead of BROKEN
// See https://github.com/allure-framework/allure-kotlin allure-kotlin-commons/src/main/kotlin/io/qameta/allure/kotlin/util/ResultsUtils.kt
throw AssertionError(ex)
}

Allure.lifecycle.updateStep {
it.status = Status.FAILED
it.statusDetails = StatusDetails(known = true, muted = true, message = ex.message, trace = ex.stackTraceToString())
}
Allure.lifecycle.stopStep()
AllureVisualTestFlag.shouldFailLate.set(true)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.kaspersky.components.alluresupport.visual

import com.kaspersky.components.alluresupport.files.dirs.AllureDirsProvider
import com.kaspersky.kaspresso.device.files.Files
import com.kaspersky.kaspresso.files.resources.ResourcesRootDirsProvider
import com.kaspersky.kaspresso.logger.Logger
import com.kaspersky.kaspresso.visual.VisualTestParams
import com.kaspersky.kaspresso.visual.VisualTestType
import com.kaspersky.kaspresso.visual.VisualTestWatcher
import java.io.File

class AllureVisualTestWatcher(
private val params: VisualTestParams,
private val logger: Logger,
private val dirsProvider: AllureDirsProvider,
resourcesRootDirsProvider: ResourcesRootDirsProvider,
private val files: Files,
) : VisualTestWatcher {

private val diffDir = dirsProvider.provideNew(resourcesRootDirsProvider.screenshotsDiffRootDir)
private val originalScreenshotsTargetDir: File
get() {
val rootDir = dirsProvider.provideNewOnSdCard(File("")).absolutePath
return File(rootDir, File(params.hostScreenshotsDir).name)
}

override fun prepare() {
logger.i("Visual test run started. Parameters: $params")

if (params.testType == VisualTestType.Compare) {
logger.i("Pushing the screenshots unto the device...")
dirsProvider.provideCleared(diffDir)

// Allure stores all files in the app's private directory. We can't "adb push" directly there,
// so we have to do this in 2 steps
dirsProvider.provideCleared(originalScreenshotsTargetDir)
val tmp = dirsProvider.provideNewOnSdCard(File(""))
files.push(params.hostScreenshotsDir, tmp.absolutePath)
val target = dirsProvider.provideNew(File("")).resolve(params.hostScreenshotsDir)
File(tmp, params.hostScreenshotsDir).copyRecursively(target, overwrite = true)
logger.i("Done pushing the screenshots unto the device")
}
}

override fun cleanUp() {
// Do nothing
}
}
Binary file modified artifacts/adbserver-desktop.jar
Binary file not shown.
Original file line number Diff line number Diff line change
Expand Up @@ -32,5 +32,6 @@ configure<BaseExtension> {
resValues = false
shaders = false
viewBinding = false
buildConfig = true
}
}
Loading

0 comments on commit 68b0116

Please sign in to comment.