Updated on 2026-08-14
This commit is contained in:
parent
aed32a47f1
commit
58e26ca5e6
8 changed files with 139 additions and 14 deletions
41
Marathonfile
Normal file
41
Marathonfile
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
name: "Tangem Android UI Tests"
|
||||
outputDir: "build/reports/marathon"
|
||||
|
||||
testOutputTimeoutMillis: 180000 # 3 minutes
|
||||
testBatchTimeoutMillis: 600000 # 10 minutes
|
||||
|
||||
vendorConfiguration:
|
||||
type: "Android"
|
||||
applicationApk: "app/build/outputs/apk/google/mocked/app-google-mocked.apk"
|
||||
testApplicationApk: "app/build/outputs/apk/androidTest/google/mocked/app-google-mocked-androidTest.apk"
|
||||
autoGrantPermission: true
|
||||
applicationPmClear: true
|
||||
testApplicationPmClear: true
|
||||
# wiremockBaseUrl redirects API calls from wiremock.tests-d.com to local WireMock
|
||||
# This is used on CI where each emulator has its own WireMock instance via adb reverse
|
||||
instrumentationArgs:
|
||||
wiremockBaseUrl: "http://localhost:8080"
|
||||
allureConfiguration:
|
||||
enabled: false # we collect allure via adb pull, not through marathon
|
||||
|
||||
poolingStrategy:
|
||||
type: "omni"
|
||||
|
||||
batchingStrategy:
|
||||
type: "isolate"
|
||||
|
||||
sortingStrategy:
|
||||
type: "no-sorting"
|
||||
|
||||
retryStrategy:
|
||||
type: "fixed-quota"
|
||||
totalAllowedRetryQuota: 30
|
||||
retryPerTestQuota: 1
|
||||
|
||||
flakinessStrategy:
|
||||
type: "ignore"
|
||||
|
||||
analyticsConfiguration:
|
||||
type: "disabled"
|
||||
|
||||
debug: false
|
||||
|
|
@ -6,6 +6,7 @@ import androidx.compose.ui.test.junit4.createEmptyComposeRule
|
|||
import androidx.compose.ui.test.printToLog
|
||||
import androidx.test.core.app.ActivityScenario
|
||||
import androidx.test.espresso.intent.Intents
|
||||
import androidx.test.platform.app.InstrumentationRegistry
|
||||
import androidx.test.rule.GrantPermissionRule
|
||||
import com.kaspersky.components.alluresupport.interceptors.step.ScreenshotStepInterceptor
|
||||
import com.kaspersky.components.alluresupport.withForcedAllureSupport
|
||||
|
|
@ -19,6 +20,7 @@ import com.tangem.common.rules.ApiEnvironmentRule
|
|||
import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.utils.WireMockRedirectInterceptor
|
||||
import com.tangem.domain.promo.PromoRepository
|
||||
import com.tangem.domain.promo.models.PromoId
|
||||
import com.tangem.tap.MainActivity
|
||||
|
|
@ -86,6 +88,9 @@ abstract class BaseTestCase : TestCase(
|
|||
additionalAfterSection: () -> Unit = {},
|
||||
) = before {
|
||||
Allure.label(ALLURE_LABEL_NAME, ALLURE_LABEL_VALUE)
|
||||
// Setup WireMock redirect for CI with local WireMock instances
|
||||
val wiremockUrl = InstrumentationRegistry.getArguments().getString(WIREMOCK_BASE_URL_ARG)
|
||||
WireMockRedirectInterceptor.overriddenBaseUrl = wiremockUrl
|
||||
hiltRule.inject()
|
||||
runBlocking {
|
||||
appPreferencesStore.editData { mutablePreferences ->
|
||||
|
|
@ -147,4 +152,8 @@ abstract class BaseTestCase : TestCase(
|
|||
)
|
||||
)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val WIREMOCK_BASE_URL_ARG = "wiremockBaseUrl"
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +1,31 @@
|
|||
package com.tangem.common.utils
|
||||
|
||||
import com.tangem.datasource.utils.WireMockRedirectInterceptor
|
||||
import okhttp3.*
|
||||
import okhttp3.MediaType.Companion.toMediaType
|
||||
import okhttp3.RequestBody.Companion.toRequestBody
|
||||
import timber.log.Timber
|
||||
import java.io.IOException
|
||||
|
||||
private const val DEFAULT_WIREMOCK_URL = "[REDACTED_ENV_URL]"
|
||||
|
||||
/**
|
||||
* Returns the WireMock base URL to use.
|
||||
*/
|
||||
private fun getWireMockBaseUrl(): String =
|
||||
WireMockRedirectInterceptor.overriddenBaseUrl ?: DEFAULT_WIREMOCK_URL
|
||||
|
||||
/**
|
||||
* Method uses to set WireMock scenario state
|
||||
* @param scenarioName Name of the scenario to modify
|
||||
* @param state The target state to set (must be one of the scenario's possibleStates)
|
||||
* @param baseUrl WireMock base URL
|
||||
* @param baseUrl WireMock base URL (defaults to local override if set, otherwise remote)
|
||||
* @return true if state was set successfully, false otherwise
|
||||
*/
|
||||
fun setWireMockScenarioState(
|
||||
scenarioName: String,
|
||||
state: String,
|
||||
baseUrl: String = "[REDACTED_ENV_URL]"
|
||||
baseUrl: String = getWireMockBaseUrl()
|
||||
): Boolean {
|
||||
Timber.i("=== WireMock Scenario Set ===")
|
||||
Timber.i("Setting scenario '$scenarioName' to state: $state")
|
||||
|
|
@ -46,8 +55,9 @@ fun setWireMockScenarioState(
|
|||
|
||||
/**
|
||||
* Method checks accessibility of WireMock
|
||||
* @param baseUrl WireMock base URL (defaults to local override if set, otherwise remote)
|
||||
*/
|
||||
fun checkWireMockStatus(baseUrl: String = "[REDACTED_ENV_URL]"): Boolean {
|
||||
fun checkWireMockStatus(baseUrl: String = getWireMockBaseUrl()): Boolean {
|
||||
val client = OkHttpClient()
|
||||
val request = Request.Builder()
|
||||
.url("$baseUrl/__admin/scenarios")
|
||||
|
|
@ -69,8 +79,9 @@ fun checkWireMockStatus(baseUrl: String = "[REDACTED_ENV_URL]"): Boolean {
|
|||
|
||||
/**
|
||||
* Method to reset all WireMock scenarios
|
||||
* @param baseUrl WireMock base URL (defaults to local override if set, otherwise remote)
|
||||
*/
|
||||
fun resetWireMockScenarios(baseUrl: String = "[REDACTED_ENV_URL]"): Boolean {
|
||||
fun resetWireMockScenarios(baseUrl: String = getWireMockBaseUrl()): Boolean {
|
||||
Timber.i("=== WireMock Scenarios Reset ===")
|
||||
Timber.i("Base URL: $baseUrl")
|
||||
|
||||
|
|
@ -105,13 +116,13 @@ fun resetWireMockScenarios(baseUrl: String = "[REDACTED_ENV_URL]"): Boolean {
|
|||
* Method to reset a specific WireMock scenario to its initial state
|
||||
* @param scenarioName Name of the scenario to reset
|
||||
* @param initialState The target state to reset the scenario to (must be one of the scenario's possibleStates)
|
||||
* @param baseUrl WireMock base URL
|
||||
* @param baseUrl WireMock base URL (defaults to local override if set, otherwise remote)
|
||||
* @return true if reset was successful, false otherwise
|
||||
*/
|
||||
fun resetWireMockScenarioState(
|
||||
scenarioName: String,
|
||||
initialState: String = "Started",
|
||||
baseUrl: String = "[REDACTED_ENV_URL]"
|
||||
baseUrl: String = getWireMockBaseUrl()
|
||||
): Boolean {
|
||||
Timber.i("=== WireMock Scenario Reset ===")
|
||||
Timber.i("Resetting scenario '$scenarioName' to initial state: $initialState")
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ import com.tangem.datasource.local.logs.AppLogsStore
|
|||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.token.UserTokensResponseStore
|
||||
import com.tangem.datasource.utils.NetworkLogsSaveInterceptor
|
||||
import com.tangem.datasource.utils.WireMockRedirectInterceptor
|
||||
import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
|
||||
import com.tangem.domain.apptheme.GetAppThemeModeUseCase
|
||||
import com.tangem.domain.apptheme.repository.AppThemeModeRepository
|
||||
|
|
@ -334,15 +335,23 @@ open class TangemApplication : Application(), ImageLoaderFactory, Configuration.
|
|||
ExceptionHandler.append(blockchainExceptionHandler)
|
||||
|
||||
if (LogConfig.network.isBlockchainSdkNetworkLogEnabled) {
|
||||
BlockchainSdkRetrofitBuilder.interceptors = listOf(
|
||||
createNetworkLoggingInterceptor(),
|
||||
ChuckerInterceptor(this),
|
||||
)
|
||||
BlockchainSdkRetrofitBuilder.interceptors = buildList {
|
||||
if (BuildConfig.MOCK_DATA_SOURCE) {
|
||||
add(WireMockRedirectInterceptor())
|
||||
}
|
||||
add(createNetworkLoggingInterceptor())
|
||||
add(ChuckerInterceptor(this@TangemApplication))
|
||||
}
|
||||
|
||||
TangemApiServiceSettings.addInterceptors(
|
||||
createNetworkLoggingInterceptor(),
|
||||
ChuckerInterceptor(this),
|
||||
NetworkLogsSaveInterceptor(appLogsStore),
|
||||
*buildList {
|
||||
if (BuildConfig.MOCK_DATA_SOURCE) {
|
||||
add(WireMockRedirectInterceptor())
|
||||
}
|
||||
add(createNetworkLoggingInterceptor())
|
||||
add(ChuckerInterceptor(this@TangemApplication))
|
||||
add(NetworkLogsSaveInterceptor(appLogsStore))
|
||||
}.toTypedArray(),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,4 +6,9 @@
|
|||
<certificates src="user" />
|
||||
</trust-anchors>
|
||||
</base-config>
|
||||
|
||||
<domain-config cleartextTrafficPermitted="true">
|
||||
<domain includeSubdomains="false">10.0.2.2</domain>
|
||||
<domain includeSubdomains="false">localhost</domain>
|
||||
</domain-config>
|
||||
</network-security-config>
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import com.tangem.datasource.api.utils.WriteTimeout
|
|||
import com.tangem.datasource.di.NetworkMoshi
|
||||
import com.tangem.datasource.local.logs.AppLogsStore
|
||||
import com.tangem.datasource.utils.NetworkLogsSaveInterceptor
|
||||
import com.tangem.datasource.utils.WireMockRedirectInterceptor
|
||||
import com.tangem.datasource.utils.addHeaders
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import okhttp3.Interceptor
|
||||
|
|
@ -77,6 +78,7 @@ internal class RetrofitApiBuilder @Inject constructor(
|
|||
.client(
|
||||
OkHttpClient.Builder()
|
||||
.applyApiConfig(apiConfigId = apiConfigId, environmentConfig = environmentConfig)
|
||||
.applyWireMockRedirect()
|
||||
.let {
|
||||
if (applyTimeoutAnnotations) it.applyTimeoutAnnotations() else it
|
||||
}
|
||||
|
|
@ -181,6 +183,14 @@ internal class RetrofitApiBuilder @Inject constructor(
|
|||
)
|
||||
}
|
||||
|
||||
private fun OkHttpClient.Builder.applyWireMockRedirect(): OkHttpClient.Builder {
|
||||
return if (BuildConfig.MOCK_DATA_SOURCE) {
|
||||
addInterceptor(interceptor = WireMockRedirectInterceptor())
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
private fun OkHttpClient.Builder.addLoggers(apiConfigId: ApiConfig.ID, context: Context): OkHttpClient.Builder {
|
||||
if (apiConfigId in excludedApiForLogging) return this
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
package com.tangem.datasource.utils
|
||||
|
||||
import okhttp3.Interceptor
|
||||
import okhttp3.Response
|
||||
import timber.log.Timber
|
||||
|
||||
/**
|
||||
* OkHttp interceptor that redirects requests from wiremock.tests-d.com to a local WireMock instance.
|
||||
*/
|
||||
class WireMockRedirectInterceptor : Interceptor {
|
||||
|
||||
override fun intercept(chain: Interceptor.Chain): Response {
|
||||
val override = overriddenBaseUrl ?: return chain.proceed(chain.request())
|
||||
|
||||
val request = chain.request()
|
||||
val url = request.url.toString()
|
||||
|
||||
if (url.contains(WIREMOCK_REMOTE_URL)) {
|
||||
val newUrl = url.replace(WIREMOCK_REMOTE_URL, override.trimEnd('/'))
|
||||
Timber.d("WireMockRedirect: $url -> $newUrl")
|
||||
val newRequest = request.newBuilder()
|
||||
.url(newUrl)
|
||||
.build()
|
||||
return chain.proceed(newRequest)
|
||||
}
|
||||
|
||||
return chain.proceed(request)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val WIREMOCK_REMOTE_URL = "[REDACTED_ENV_URL]"
|
||||
|
||||
/**
|
||||
* Override base URL for WireMock requests.
|
||||
* When null (default), requests go to wiremock.tests-d.com.
|
||||
* When set (e.g., "http://localhost:8080"), requests are redirected to local WireMock instance.
|
||||
*/
|
||||
var overriddenBaseUrl: String? = null
|
||||
}
|
||||
}
|
||||
|
|
@ -1 +1 @@
|
|||
Subproject commit ab6aee1d49ac0cfca644330a525dced725d5a257
|
||||
Subproject commit dc9df17919126bce93482559fe26602f9801eb42
|
||||
Loading…
Add table
Add a link
Reference in a new issue