2
0

Compare commits

...

4 Commits

Author SHA1 Message Date
b6291e4770 Add more logs when tests failure 2025-10-29 09:20:55 +03:00
63010918ee Results transmit via interceptor. 2024-11-11 13:56:24 +03:00
36d60dbf39 add manifest 2024-09-09 04:03:36 +07:00
67babd8e6e fix packages and build errors 2024-09-09 02:49:46 +07:00
9 changed files with 130 additions and 97 deletions

View File

@@ -1,9 +1,24 @@
plugins { plugins {
androidApplication kotlinAndroid
androidLibrary
} }
android { android {
namespace = "ru.samsung.test.core" namespace = "ru.samsung.test.core"
compileSdk = Version.Android.Sdk.compile
defaultConfig {
minSdk = Version.Android.Sdk.min
}
compileOptions {
sourceCompatibility = Version.Kotlin.javaSource
targetCompatibility = Version.Kotlin.javaSource
}
kotlinOptions {
jvmTarget = Version.Kotlin.jvmTarget
}
} }
dependencies { dependencies {

View File

@@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest/>

122
src/main/java/ru/samsung/test/core/core/BaseTest.kt Normal file → Executable file
View File

@@ -1,83 +1,85 @@
package ru.innovationcampus.test.core.core package ru.samsung.test.core.core
import android.app.Activity import android.app.Activity
import android.app.Instrumentation
import android.content.Context
import android.content.Intent
import androidx.compose.ui.test.junit4.createComposeRule
import androidx.test.core.app.ActivityScenario import androidx.test.core.app.ActivityScenario
import androidx.test.espresso.Espresso
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice
import com.kaspersky.components.composesupport.config.addComposeSupport import com.kaspersky.components.composesupport.config.addComposeSupport
import com.kaspersky.kaspresso.idlewaiting.KautomatorWaitForIdleSettings import com.kaspersky.kaspresso.idlewaiting.KautomatorWaitForIdleSettings
import com.kaspersky.kaspresso.kaspresso.Kaspresso import com.kaspersky.kaspresso.kaspresso.Kaspresso
import com.kaspersky.kaspresso.testcases.api.testcase.TestCase import com.kaspersky.kaspresso.testcases.api.testcase.TestCase
import com.kaspersky.kaspresso.testcases.core.testcontext.BaseTestContext
import com.kaspersky.kaspresso.testcases.core.testcontext.TestContext import com.kaspersky.kaspresso.testcases.core.testcontext.TestContext
import java.util.Locale import java.util.Locale
import org.junit.After
import org.junit.Before
import org.junit.FixMethodOrder import org.junit.FixMethodOrder
import org.junit.Rule
import org.junit.runners.MethodSorters import org.junit.runners.MethodSorters
import ru.innovationcampus.test.core.utils.ResultTestsData import ru.samsung.test.core.utils.ResultTestsData
/**
* Базовый класс тестов, который подключается для запуска основных
*
* @param clazz класс активити, который будет запущен при старте.
* Эта активити должна иметь соответствующий флаг в манифесте и
*/
@FixMethodOrder(MethodSorters.NAME_ASCENDING) @FixMethodOrder(MethodSorters.NAME_ASCENDING)
open class BaseTest<LaunchActivity : Activity>( open class BaseTest<LaunchActivity : Activity>(
private val clazz: Class<LaunchActivity> clazz: Class<LaunchActivity>,
) : TestCase(kaspressoBuilder = Kaspresso.Builder.simple { isEnabledCompose: Boolean = false,
kautomatorWaitForIdleSettings = KautomatorWaitForIdleSettings.boost() private val skipAfterFirstFail: Boolean = true,
}.apply { addComposeSupport() }) { ) : TestCase(
@get:Rule kaspressoBuilder = getBuilder(
val composeTestRule = createComposeRule() clazz = clazz,
isEnabledCompose = isEnabledCompose,
protected lateinit var activityScenario: ActivityScenario<LaunchActivity> )
private set ) {
protected lateinit var uiDevice: UiDevice
private set
protected lateinit var appContext: Context
private set
protected lateinit var instrumentation: Instrumentation
private set
open fun beforeTest() {}
@Before
fun setUp() {
instrumentation = InstrumentationRegistry.getInstrumentation()
val handler = DescriptionFailureHandler(instrumentation)
Espresso.setFailureHandler(handler)
uiDevice = UiDevice.getInstance(instrumentation)
uiDevice.pressHome()
Thread.sleep(1_000)
val nonLocalizedContext = instrumentation.targetContext
val configuration = nonLocalizedContext.resources.configuration
configuration.setLocale(Locale.UK)
appContext = nonLocalizedContext.createConfigurationContext(configuration)
val intent = Intent(appContext, clazz)
activityScenario = ActivityScenario.launch(intent)
beforeTest()
}
@After
fun reset() {
uiDevice.pressHome()
}
fun runWithInit(grade: Int, test: TestContext<Unit>.() -> Unit) = run { fun runWithInit(grade: Int, test: TestContext<Unit>.() -> Unit) = run {
val hasFailed = ResultTestsData.hasFailed()
ResultTestsData.setupTest(grade) ResultTestsData.setupTest(grade)
if (hasFailed && skipAfterFirstFail) throw Throwable("SKIPPED. Prev test failed")
try { try {
test.invoke(this) test.invoke(this)
ResultTestsData.successTest(grade) ResultTestsData.successTest(grade)
} catch (e: Exception) { } catch (e: Throwable) {
ResultTestsData.putResult(instrumentation, uiDevice) DescriptionFailureHandler.throwError(e)
throw AssertionError(e.message) }
}
private companion object {
fun <LaunchActivity : Activity> getBuilder(
clazz: Class<LaunchActivity>,
isEnabledCompose: Boolean,
): Kaspresso.Builder {
val builder = Kaspresso.Builder.simple {
kautomatorWaitForIdleSettings = KautomatorWaitForIdleSettings.boost()
beforeEachTest(override = true) { beforeEachAction(this, clazz) }
afterEachTest(override = true) { afterEachAction(this) }
}
builder.setupHandler()
if (isEnabledCompose) builder.addComposeSupport()
return builder
}
fun <LaunchActivity : Activity> Kaspresso.Builder.beforeEachAction(
context: BaseTestContext,
clazz: Class<LaunchActivity>,
) {
with(context.device) {
language.switchInApp(Locale.UK)
uiDevice.pressHome()
}
Thread.sleep(1_000)
ActivityScenario.launch(clazz)
}
fun Kaspresso.Builder.afterEachAction(
context: BaseTestContext,
) {
ResultTestsData.putResult(instrumentation)
context.device.uiDevice.pressHome()
}
fun Kaspresso.Builder.setupHandler() {
stepWatcherInterceptors.add(DescriptionFailureHandler)
testRunWatcherInterceptors.add(DescriptionFailureHandler)
} }
ResultTestsData.putResult(instrumentation, uiDevice)
} }
} }

View File

@@ -1,31 +1,42 @@
package ru.innovationcampus.test.core.core package ru.samsung.test.core.core
import android.app.Instrumentation import com.kaspersky.kaspresso.interceptors.watcher.testcase.StepWatcherInterceptor
import android.view.View import com.kaspersky.kaspresso.interceptors.watcher.testcase.TestRunWatcherInterceptor
import androidx.test.espresso.FailureHandler import com.kaspersky.kaspresso.testcases.models.info.StepInfo
import androidx.test.espresso.base.DefaultFailureHandler import com.kaspersky.kaspresso.testcases.models.info.TestInfo
import org.hamcrest.Matcher
import kotlin.math.min
class DescriptionFailureHandler(instrumentation: Instrumentation) : FailureHandler { object DescriptionFailureHandler : StepWatcherInterceptor, TestRunWatcherInterceptor {
var extraMessage = "" private val outputLogs = mutableListOf<String>()
private var delegate: DefaultFailureHandler =
DefaultFailureHandler(instrumentation.targetContext)
override fun handle(error: Throwable?, viewMatcher: Matcher<View>?) { override fun onTestStarted(testInfo: TestInfo) {
// Log anything you want here outputLogs.clear()
if (error != null) { }
val newError = Throwable(
"$extraMessage " + error.message?.substring( override fun interceptAfterWithSuccess(stepInfo: StepInfo) {
0, min( super.interceptAfterWithSuccess(stepInfo)
1_700, error.message?.length ?: 0 outputLogs.add(stepInfo.description + ": OK")
) }
) + "...", error.cause
)
// Then delegate the error handling to the default handler which will throw an exception override fun interceptAfterWithError(stepInfo: StepInfo, error: Throwable) {
delegate.handle(newError, viewMatcher) super.interceptAfterWithError(stepInfo, error)
outputLogs.add(stepInfo.description + ": FAIL")
}
fun throwError(error: Throwable): Nothing {
var message = buildString {
outputLogs.forEach { str ->
append(str)
append("\n")
} }
} }
if (message.length >= MAX_MESSAGE_LENGTH - MIN_ERROR_LENGTH) {
message = message.takeLast(MAX_MESSAGE_LENGTH - MIN_ERROR_LENGTH)
}
message += error.message?.take(MAX_MESSAGE_LENGTH - message.length) ?: ""
throw Throwable(message, error)
}
private const val MAX_MESSAGE_LENGTH = 1_700
private const val MIN_ERROR_LENGTH = 100
} }

View File

@@ -0,0 +1,6 @@
package ru.samsung.test.core.utils
import org.junit.rules.TestRule
import org.junit.rules.TestWatcher
class EmptyRule : TestWatcher()

View File

@@ -1,12 +1,10 @@
package ru.innovationcampus.test.core.utils package ru.samsung.test.core.utils
import android.app.Activity import android.app.Activity
import android.app.Instrumentation import android.app.Instrumentation
import android.app.Instrumentation.ActivityResult
import android.os.Build import android.os.Build
import android.os.Bundle import android.os.Bundle
import android.util.Log import android.util.Log
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.uiautomator.UiDevice import androidx.test.uiautomator.UiDevice
object ResultTestsData { object ResultTestsData {
@@ -25,13 +23,12 @@ object ResultTestsData {
ResultTestsData.grade += grade ResultTestsData.grade += grade
} }
fun hasFailed() = totalTests != passTests
fun putResult( fun putResult(
instrumentation: Instrumentation, instrumentation: Instrumentation
uiDevice: UiDevice
) { ) {
uiDevice.pressHome()
val results = Bundle() val results = Bundle()
results.putInt("passTests", passTests) results.putInt("passTests", passTests)
results.putInt("totalTests", totalTests) results.putInt("totalTests", totalTests)

View File

@@ -1,4 +1,4 @@
package ru.innovationcampus.test.core.utils package ru.samsung.test.core.utils
import com.google.common.truth.Truth.assertThat import com.google.common.truth.Truth.assertThat
import com.kaspersky.components.kautomator.component.text.UiTextViewAssertions import com.kaspersky.components.kautomator.component.text.UiTextViewAssertions

View File

@@ -1,4 +1,4 @@
package ru.innovationcampus.test.core.utils package ru.samsung.test.core.utils
import com.kaspersky.components.kautomator.component.common.builders.UiViewBuilder import com.kaspersky.components.kautomator.component.common.builders.UiViewBuilder

2
src/main/java/ru/samsung/test/core/utils/Utils.kt Normal file → Executable file
View File

@@ -1,4 +1,4 @@
package ru.innovationcampus.test.core.utils package ru.samsung.test.core.utils
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
import io.github.kakaocup.compose.node.element.ComposeScreen import io.github.kakaocup.compose.node.element.ComposeScreen