summaryrefslogtreecommitdiffhomepage
path: root/android/test
diff options
context:
space:
mode:
authorDavid Göransson <david.goransson@mullvad.net>2025-07-08 16:09:54 +0200
committerDavid Göransson <david.goransson@mullvad.net>2025-07-10 13:12:04 +0200
commitbcb4749950f75edd63b2200e4c15fc73479a7fb3 (patch)
treecfaac4be58e42b2fdaed9aad223dd006c851935a /android/test
parent396e0791d037fd939d9837ee1f2768ad5c73dc49 (diff)
downloadmullvadvpn-bcb4749950f75edd63b2200e4c15fc73479a7fb3.tar.xz
mullvadvpn-bcb4749950f75edd63b2200e4c15fc73479a7fb3.zip
Add e2e test for Google play purchases
Diffstat (limited to 'android/test')
-rw-r--r--android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/page/AddTimeBottomSheet.kt25
-rw-r--r--android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/page/OutOfTimePage.kt4
-rw-r--r--android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/PaymentTest.kt49
-rw-r--r--android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/annotations/RequiresGoogleBillingAccount.kt34
-rw-r--r--android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/annotations/RequiresPartnerAuth.kt33
-rw-r--r--android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/constant/Constants.kt5
-rw-r--r--android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/AccountProvider.kt6
-rw-r--r--android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/AccountTestRule.kt4
8 files changed, 156 insertions, 4 deletions
diff --git a/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/page/AddTimeBottomSheet.kt b/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/page/AddTimeBottomSheet.kt
new file mode 100644
index 0000000000..ce6a29676c
--- /dev/null
+++ b/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/page/AddTimeBottomSheet.kt
@@ -0,0 +1,25 @@
+package net.mullvad.mullvadvpn.test.common.page
+
+import androidx.test.uiautomator.By
+import androidx.test.uiautomator.UiDevice
+import net.mullvad.mullvadvpn.lib.ui.tag.ADD_TIME_BOTTOM_SHEET_TITLE_TEST_TAG
+import net.mullvad.mullvadvpn.test.common.constant.LONG_TIMEOUT
+import net.mullvad.mullvadvpn.test.common.extension.findObjectWithTimeout
+
+class AddTimeBottomSheet internal constructor() : Page() {
+ private val oneMonthSelector = By.textStartsWith("Add 30 days time")
+
+ override fun assertIsDisplayed() {
+ uiDevice.findObjectWithTimeout(By.res(ADD_TIME_BOTTOM_SHEET_TITLE_TEST_TAG))
+ }
+
+ fun click30days() {
+ uiDevice.findObjectWithTimeout(oneMonthSelector).click()
+ }
+}
+
+fun UiDevice.buyGooglePlayTime() {
+ findObjectWithTimeout(By.text("1-tap buy"), LONG_TIMEOUT)
+ findObjectWithTimeout(By.text("1-tap buy")).click()
+ waitForIdle()
+}
diff --git a/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/page/OutOfTimePage.kt b/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/page/OutOfTimePage.kt
index 33e11f4a2e..f3add0e487 100644
--- a/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/page/OutOfTimePage.kt
+++ b/android/test/common/src/main/kotlin/net/mullvad/mullvadvpn/test/common/page/OutOfTimePage.kt
@@ -10,4 +10,8 @@ class OutOfTimePage internal constructor() : Page() {
override fun assertIsDisplayed() {
uiDevice.findObjectWithTimeout(outOfTimeSelector)
}
+
+ fun clickAddTime() {
+ uiDevice.findObjectWithTimeout(By.text("Add time")).click()
+ }
}
diff --git a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/PaymentTest.kt b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/PaymentTest.kt
new file mode 100644
index 0000000000..fbf91058f5
--- /dev/null
+++ b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/PaymentTest.kt
@@ -0,0 +1,49 @@
+package net.mullvad.mullvadvpn.test.e2e
+
+import androidx.test.uiautomator.By
+import net.mullvad.mullvadvpn.lib.ui.tag.CONNECT_CARD_HEADER_TEST_TAG
+import net.mullvad.mullvadvpn.test.common.annotation.SkipForFlavors
+import net.mullvad.mullvadvpn.test.common.constant.VERY_LONG_TIMEOUT
+import net.mullvad.mullvadvpn.test.common.extension.findObjectWithTimeout
+import net.mullvad.mullvadvpn.test.common.page.AddTimeBottomSheet
+import net.mullvad.mullvadvpn.test.common.page.LoginPage
+import net.mullvad.mullvadvpn.test.common.page.OutOfTimePage
+import net.mullvad.mullvadvpn.test.common.page.buyGooglePlayTime
+import net.mullvad.mullvadvpn.test.common.page.on
+import net.mullvad.mullvadvpn.test.e2e.annotations.RequiresGoogleBillingAccount
+import net.mullvad.mullvadvpn.test.e2e.annotations.RequiresPartnerAuth
+import net.mullvad.mullvadvpn.test.e2e.misc.AccountTestRule
+import org.junit.jupiter.api.Test
+import org.junit.jupiter.api.extension.RegisterExtension
+
+class PaymentTest : EndToEndTest() {
+
+ @RegisterExtension @JvmField val accountTestRule = AccountTestRule(withTime = false)
+
+ @Test
+ @SkipForFlavors(currentFlavor = BuildConfig.FLAVOR_billing, "oss")
+ @RequiresGoogleBillingAccount
+ @RequiresPartnerAuth
+ fun testInAppPurchaseForOutOfTime() {
+ val validTestAccountNumber = accountTestRule.validAccountNumber
+
+ app.launchAndEnsureOnLoginPage()
+
+ on<LoginPage> {
+ enterAccountNumber(validTestAccountNumber)
+ clickLoginButton()
+ }
+
+ on<OutOfTimePage> { clickAddTime() }
+
+ on<AddTimeBottomSheet> { click30days() }
+
+ device.buyGooglePlayTime()
+
+ // Assert we reach the Connect page after purchase
+ device.findObjectWithTimeout(
+ By.res(CONNECT_CARD_HEADER_TEST_TAG),
+ timeout = VERY_LONG_TIMEOUT,
+ )
+ }
+}
diff --git a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/annotations/RequiresGoogleBillingAccount.kt b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/annotations/RequiresGoogleBillingAccount.kt
new file mode 100644
index 0000000000..7cedfd07d3
--- /dev/null
+++ b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/annotations/RequiresGoogleBillingAccount.kt
@@ -0,0 +1,34 @@
+package net.mullvad.mullvadvpn.test.e2e.annotations
+
+import androidx.test.platform.app.InstrumentationRegistry
+import net.mullvad.mullvadvpn.test.e2e.constant.isBillingEnabled
+import org.junit.jupiter.api.extension.ConditionEvaluationResult
+import org.junit.jupiter.api.extension.ExecutionCondition
+import org.junit.jupiter.api.extension.ExtendWith
+import org.junit.jupiter.api.extension.ExtensionContext
+
+/**
+ * Annotation for tests making use of a google billing test account in order to perform purchases
+ */
+@Retention(AnnotationRetention.RUNTIME)
+@ExtendWith(RequiresGoogleBillingAccount.AccessToBillingTestAccount::class)
+annotation class RequiresGoogleBillingAccount {
+ class AccessToBillingTestAccount : ExecutionCondition {
+ override fun evaluateExecutionCondition(
+ context: ExtensionContext?
+ ): ConditionEvaluationResult {
+
+ val enable = InstrumentationRegistry.getArguments().isBillingEnabled()
+
+ return if (enable) {
+ ConditionEvaluationResult.enabled(
+ "Running test which requires access to billing test account."
+ )
+ } else {
+ ConditionEvaluationResult.disabled(
+ "Skipping test which requires access to billing test account."
+ )
+ }
+ }
+ }
+}
diff --git a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/annotations/RequiresPartnerAuth.kt b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/annotations/RequiresPartnerAuth.kt
new file mode 100644
index 0000000000..d9afdc1d68
--- /dev/null
+++ b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/annotations/RequiresPartnerAuth.kt
@@ -0,0 +1,33 @@
+package net.mullvad.mullvadvpn.test.e2e.annotations
+
+import androidx.test.platform.app.InstrumentationRegistry
+import net.mullvad.mullvadvpn.test.e2e.constant.getPartnerAuth
+import org.junit.jupiter.api.extension.ConditionEvaluationResult
+import org.junit.jupiter.api.extension.ExecutionCondition
+import org.junit.jupiter.api.extension.ExtendWith
+import org.junit.jupiter.api.extension.ExtensionContext
+
+/** Annotation for tests requiring a partner api authentication. */
+@Retention(AnnotationRetention.RUNTIME)
+@ExtendWith(RequiresPartnerAuth.HasPartnerAuth::class)
+annotation class RequiresPartnerAuth {
+ class HasPartnerAuth : ExecutionCondition {
+ override fun evaluateExecutionCondition(
+ context: ExtensionContext?
+ ): ConditionEvaluationResult {
+
+ val provided =
+ InstrumentationRegistry.getArguments().getPartnerAuth()?.isNotEmpty() ?: false
+
+ return if (provided) {
+ ConditionEvaluationResult.enabled(
+ "Running test which requires partner authentication."
+ )
+ } else {
+ ConditionEvaluationResult.disabled(
+ "Skipping test which requires partner authentication."
+ )
+ }
+ }
+ }
+}
diff --git a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/constant/Constants.kt b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/constant/Constants.kt
index 8bcb5c2997..4326d156ea 100644
--- a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/constant/Constants.kt
+++ b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/constant/Constants.kt
@@ -23,6 +23,11 @@ fun Bundle.getInvalidAccountNumber() =
"mullvad.test.e2e.${BuildConfig.FLAVOR_infrastructure}.accountNumber.invalid"
)
+fun Bundle.isBillingEnabled(): Boolean =
+ InstrumentationRegistry.getArguments()
+ .getString("mullvad.test.e2e.config.billing.enable", "false")
+ .toBoolean()
+
fun Bundle.isRaasEnabled(): Boolean =
InstrumentationRegistry.getArguments()
.getRequiredArgument("mullvad.test.e2e.config.raas.enable")
diff --git a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/AccountProvider.kt b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/AccountProvider.kt
index 799f33be64..1d967c2fe6 100644
--- a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/AccountProvider.kt
+++ b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/AccountProvider.kt
@@ -13,12 +13,14 @@ object AccountProvider {
private val partnerAuth: String? = InstrumentationRegistry.getArguments().getPartnerAuth()
private val partnerClient: PartnerApi by lazy { PartnerApi(partnerAuth!!) }
- suspend fun getValidAccountNumber() =
+ suspend fun getValidAccountNumber(withTime: Boolean = true) =
// If partner auth is provided, create a new account using the partner API. Otherwise we
// expect and account with time to be provided.
if (partnerAuth != null) {
val accountNumber = partnerClient.createAccount()
- partnerClient.addTime(accountNumber = accountNumber, daysToAdd = 1)
+ if (withTime) {
+ partnerClient.addTime(accountNumber = accountNumber, daysToAdd = 1)
+ }
accountNumber
} else {
val validAccountNumber = InstrumentationRegistry.getArguments().getValidAccountNumber()
diff --git a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/AccountTestRule.kt b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/AccountTestRule.kt
index 1f455af5c1..d3c8f30c19 100644
--- a/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/AccountTestRule.kt
+++ b/android/test/e2e/src/main/kotlin/net/mullvad/mullvadvpn/test/e2e/misc/AccountTestRule.kt
@@ -4,12 +4,12 @@ import kotlinx.coroutines.runBlocking
import org.junit.jupiter.api.extension.BeforeEachCallback
import org.junit.jupiter.api.extension.ExtensionContext
-class AccountTestRule : BeforeEachCallback {
+class AccountTestRule(val withTime: Boolean = true) : BeforeEachCallback {
lateinit var validAccountNumber: String
lateinit var invalidAccountNumber: String
override fun beforeEach(context: ExtensionContext): Unit = runBlocking {
- validAccountNumber = AccountProvider.getValidAccountNumber()
+ validAccountNumber = AccountProvider.getValidAccountNumber(withTime)
invalidAccountNumber = AccountProvider.getInvalidAccountNumber()
}
}