test(payment, plan): Fix plan UI tests for dynamic plans.

This commit is contained in:
Mateusz Armatys
2023-10-20 13:46:22 +00:00
committed by MargeBot
parent 3b3282495d
commit fdb6aa32f4
25 changed files with 884 additions and 30 deletions
@@ -1310,6 +1310,7 @@ public final class me/proton/core/auth/presentation/entity/signup/SubscriptionDe
}
public abstract interface class me/proton/core/auth/presentation/testing/ProtonTestEntryPoint {
public abstract fun getFeatureFlagRepository ()Lme/proton/core/featureflag/domain/repository/FeatureFlagRepository;
public abstract fun getGetAvailablePaymentProviders ()Lme/proton/core/payment/domain/usecase/GetAvailablePaymentProviders;
public abstract fun getLoginTestHelper ()Lme/proton/core/auth/domain/testing/LoginTestHelper;
}
@@ -1,6 +1,6 @@
/*
* Copyright (c) 2021 Proton Technologies AG
* This file is part of Proton Technologies AG and ProtonCore.
* Copyright (c) 2023 Proton AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -23,12 +23,14 @@ import dagger.hilt.EntryPoint
import dagger.hilt.InstallIn
import dagger.hilt.components.SingletonComponent
import me.proton.core.auth.domain.testing.LoginTestHelper
import me.proton.core.featureflag.domain.repository.FeatureFlagRepository
import me.proton.core.payment.domain.usecase.GetAvailablePaymentProviders
@EntryPoint
@InstallIn(SingletonComponent::class)
@RestrictTo(RestrictTo.Scope.TESTS)
interface ProtonTestEntryPoint {
val featureFlagRepository: FeatureFlagRepository
val getAvailablePaymentProviders: GetAvailablePaymentProviders
val loginTestHelper: LoginTestHelper
}
+1
View File
@@ -307,6 +307,7 @@ dependencies {
androidTestImplementation(
project(Module.androidInstrumentedTest),
project(Module.paymentIapTest),
project(Module.planTest),
project(Module.quark),
`android-test-runner`,
`hilt-android-testing`,
@@ -1,6 +1,6 @@
/*
* Copyright (c) 2021 Proton Technologies AG
* This file is part of Proton Technologies AG and ProtonCore.
* Copyright (c) 2023 Proton AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -26,6 +26,7 @@ import kotlinx.coroutines.runBlocking
import me.proton.android.core.coreexample.Constants
import me.proton.android.core.coreexample.MainActivity
import me.proton.core.auth.presentation.testing.ProtonTestEntryPoint
import me.proton.core.domain.entity.UserId
import me.proton.core.payment.domain.usecase.PaymentProvider
import me.proton.core.test.android.instrumented.ProtonTest
import me.proton.core.test.android.instrumented.utils.Shell.setupDeviceForAutomation
@@ -54,7 +55,8 @@ open class BaseTest(
fun login(user: User) {
Log.d(testTag, "Login user: ${user.name}")
AddAccountRobot().back<CoreexampleRobot>().verify { accountSwitcherDisplayed() }
authHelper.login(user.name, user.password)
val sessionInfo = authHelper.login(user.name, user.password)
fetchUnleashFeatureFlags(sessionInfo.userId)
Log.d(testTag, "Login done.")
}
@@ -79,6 +81,7 @@ open class BaseTest(
fun prepare() {
setupDeviceForAutomation(true)
authHelper.logoutAll()
fetchUnleashFeatureFlags(userId = null)
Plan.Dev.text = Plan.MailPlus.text
Plan.Dev.planName = Plan.MailPlus.planName
}
@@ -95,5 +98,9 @@ open class BaseTest(
availablePaymentProviders().filter {
it != PaymentProvider.PayPal
}.toSet()
private fun fetchUnleashFeatureFlags(userId: UserId? = null) = runBlocking {
protonTestEntryPoint.featureFlagRepository.getAll(userId)
}
}
}
@@ -0,0 +1,143 @@
/*
* Copyright (c) 2023 Proton AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ProtonCore is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
*/
package me.proton.core.test.android.uitests.tests.medium.payments
import android.content.Context
import androidx.test.core.app.ApplicationProvider
import me.proton.core.domain.entity.AppStore
import me.proton.core.test.quark.data.Card
import me.proton.core.test.quark.data.Plan
import me.proton.core.test.android.robots.payments.ExistingPaymentMethodsRobot
import me.proton.core.test.android.robots.payments.ExistingPaymentMethodsRobot.PaymentMethodElement.paymentMethod
import me.proton.core.test.android.uitests.robot.CoreexampleRobot
import me.proton.core.test.android.uitests.tests.BaseTest
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.Parameterized
import me.proton.core.payment.presentation.R
import me.proton.core.plan.test.robot.SubscriptionRobot
import me.proton.core.test.android.instrumented.utils.StringUtils.stringFromResource
@RunWith(Parameterized::class)
class DynamicExistingPaymentMethodTests(
private val appStore: AppStore,
private val card: Boolean,
private val paypal: Boolean,
private val inApp: Boolean
) : BaseTest() {
@Before
fun setPaymentMethods() {
quark.setPaymentMethods(appStore, card, paypal, inApp)
}
@After
fun setDefaults() {
quark.setDefaultPaymentMethods()
}
@Test
@Ignore("Requires user with paypal account linked")
fun existingPaypalMethodDisplayed() {
login(userWithPaypal)
CoreexampleRobot().plansUpgrade()
SubscriptionRobot.selectPlan(Plan.Dev)
ExistingPaymentMethodsRobot().verify {
paymentMethodDisplayed("PayPal", userWithPaypal.paypal)
}
}
@Test
fun existingCreditCardMethodDisplayed() {
login(userWithCard)
CoreexampleRobot().plansUpgrade()
SubscriptionRobot.selectPlan(Plan.Dev)
ExistingPaymentMethodsRobot().verify {
paymentMethodDisplayed(Card.default.details(), Card.default.name)
if (inApp) {
val context = ApplicationProvider.getApplicationContext<Context>()
googlePaymentMethodDisplayed("${context.getString(R.string.payments_method_google)}*")
}
}
}
@Test
@Ignore("Requires user with paypal account linked")
fun existingCreditCardAndPayPalDisplayed() {
val user = users.getUser { it.paypal.isNotEmpty() && it.cards.isNotEmpty() && !it.isPaid }
val card = user.cards[0]
login(user)
CoreexampleRobot().plansUpgrade()
SubscriptionRobot.selectPlan(Plan.Dev)
ExistingPaymentMethodsRobot().verify {
paymentMethodDisplayed(card.details(), card.name)
paymentMethodDisplayed("PayPal", user.paypal)
}
}
@Test
@Ignore("Requires user with paypal account linked")
fun switchPaymentMethod() {
val user = users.getUser { it.paypal.isNotEmpty() && it.cards.isNotEmpty() && !it.isPaid }
CoreexampleRobot().plansUpgrade()
SubscriptionRobot.selectPlan(Plan.Dev)
ExistingPaymentMethodsRobot().verify {
paymentMethod(user.paypal).checkIsNotChecked()
paymentMethod(user.cards[0].details()).checkIsChecked()
}
ExistingPaymentMethodsRobot()
.selectPaymentMethod(user.paypal)
.verify {
paymentMethod(user.paypal).checkIsChecked()
paymentMethod(user.cards[0].details()).checkIsNotChecked()
}
}
private fun Card.details(): String = stringFromResource(
R.string.payment_cc_list_item,
brand.value,
number.takeLast(4),
expMonth,
expYear
)
companion object {
val userWithCard by lazy { quark.seedUserWithCreditCard() }
val userWithPaypal by lazy { users.getUser { it.paypal.isNotEmpty() } }
@get:Parameterized.Parameters(name = "appStore_{0}_card_{1}_paypal_{2}_inApp_{3}")
@get:JvmStatic
val data = listOf(
// appStore, card, paypal, inApp
arrayOf(AppStore.GooglePlay, true, true, true),
arrayOf(AppStore.GooglePlay, true, true, false),
arrayOf(AppStore.GooglePlay, true, false, true),
arrayOf(AppStore.GooglePlay, true, false, false)
).toList()
}
}
@@ -0,0 +1,87 @@
/*
* Copyright (c) 2023 Proton AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ProtonCore is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
*/
package me.proton.core.test.android.uitests.tests.medium.payments
import me.proton.core.domain.entity.AppStore
import me.proton.core.payment.presentation.R
import me.proton.core.plan.test.robot.SubscriptionRobot
import me.proton.core.test.android.robots.payments.AddCreditCardRobot
import me.proton.core.test.android.uitests.robot.CoreexampleRobot
import me.proton.core.test.android.uitests.tests.BaseTest
import me.proton.core.test.quark.data.Plan
import me.proton.core.test.quark.data.User
import org.junit.After
import org.junit.Test
class DynamicNewCreditCardTests : BaseTest() {
private val newCreditCardRobot = AddCreditCardRobot()
@After
fun setDefaults() {
quark.setDefaultPaymentMethods()
}
private fun goToPlanUpgrade() {
quark.setPaymentMethods(AppStore.GooglePlay, card = true, paypal = false, inApp = false)
login(userWithoutCard)
CoreexampleRobot().plansCurrent()
SubscriptionRobot.selectPlan(Plan.Dev)
}
@Test
fun backToPlanSelection() {
goToPlanUpgrade()
newCreditCardRobot.close<CoreexampleRobot>()
SubscriptionRobot.verifyAtLeastOnePlanIsShown()
}
@Test
fun missingPaymentDetails() {
goToPlanUpgrade()
newCreditCardRobot
.pay<AddCreditCardRobot>()
.verify {
inputErrorDisplayed(R.string.payments_error_card_name, scroll = true)
inputErrorDisplayed(R.string.payments_error_card_number, scroll = true)
inputErrorDisplayed(R.string.payments_error_cvc, scroll = true)
}
}
@Test
fun invalidCreditCardNumber() {
goToPlanUpgrade()
newCreditCardRobot
.ccname("Test name")
.country()
.selectCountry<AddCreditCardRobot>("Angola")
.expirationDate("1234")
.cvc("123")
.ccnumber("424242424242424")
.postalCode("1234")
.pay<AddCreditCardRobot>()
.verify { errorSnackbarDisplayed("Your card has been declined") }
}
companion object {
val userWithoutCard: User = quark.userCreate().first
}
}
@@ -1,6 +1,6 @@
/*
* Copyright (c) 2021 Proton Technologies AG
* This file is part of Proton Technologies AG and ProtonCore.
* Copyright (c) 2023 Proton AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -36,6 +36,7 @@ import org.junit.runners.Parameterized
import me.proton.core.payment.presentation.R
import me.proton.core.test.android.instrumented.utils.StringUtils.stringFromResource
@Ignore("Replaced with DynamicExistingPaymentMethodTests")
@RunWith(Parameterized::class)
class ExistingPaymentMethodTests(
private val appStore: AppStore,
@@ -1,6 +1,6 @@
/*
* Copyright (c) 2021 Proton Technologies AG
* This file is part of Proton Technologies AG and ProtonCore.
* Copyright (c) 2023 Proton AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -27,8 +27,10 @@ import me.proton.core.test.android.robots.plans.SelectPlanRobot
import me.proton.core.test.android.uitests.robot.CoreexampleRobot
import me.proton.core.test.android.uitests.tests.BaseTest
import org.junit.After
import org.junit.Ignore
import org.junit.Test
@Ignore("Replaced by DynamicNewCreditCardTests")
class NewCreditCardTests : BaseTest() {
private val newCreditCardRobot = AddCreditCardRobot()
@@ -31,11 +31,13 @@ import me.proton.core.test.android.uitests.robot.CoreexampleRobot
import me.proton.core.test.android.uitests.tests.BaseTest
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
import java.text.DateFormat
import java.util.Calendar
import java.util.Date
@Ignore("Replaced by CurrentDynamicPlanTests")
class CurrentPlanTests : BaseTest() {
private fun navigateUserToCurrentPlans(user: User): SelectPlanRobot {
quark.setPaymentMethods(AppStore.GooglePlay, card = true, paypal = false, inApp = false)
@@ -0,0 +1,191 @@
/*
* Copyright (c) 2023 Proton AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ProtonCore is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
*/
package me.proton.core.test.android.uitests.tests.medium.plans
import android.content.Context
import androidx.core.text.HtmlCompat
import androidx.test.core.app.ApplicationProvider
import me.proton.core.domain.entity.AppStore
import me.proton.core.plan.presentation.R
import me.proton.core.plan.presentation.entity.PlanCycle
import me.proton.core.plan.test.robot.SubscriptionRobot
import me.proton.core.test.android.uitests.robot.CoreexampleRobot
import me.proton.core.test.android.uitests.tests.BaseTest
import me.proton.core.test.quark.data.Plan
import me.proton.core.test.quark.data.User
import org.junit.After
import org.junit.Before
import org.junit.Test
import java.text.DateFormat
import java.util.Calendar
import java.util.Date
class DynamicCurrentPlanTests : BaseTest() {
private fun navigateUserToCurrentPlans(user: User) {
quark.setPaymentMethods(AppStore.GooglePlay, card = true, paypal = false, inApp = false)
login(user)
CoreexampleRobot().plansCurrent()
}
@Before
override fun setUp() {
super.setUp()
quark.jailUnban()
}
@After
fun setDefaults() {
quark.setDefaultPaymentMethods()
}
@Test
fun userWithFreePlan() {
val user = quark.userCreate().first
navigateUserToCurrentPlans(user)
SubscriptionRobot.apply {
verifySubscriptionIsShown()
verifyUpgradeYourPlanTextIsDisplayed()
verifyAtLeastOnePlanIsShown()
}
}
@Test
fun userWithPaidPlan() {
val paidUser = User(plan = Plan.Unlimited)
quark.seedNewSubscriberWithCycle(paidUser, PlanCycle.YEARLY.cycleDurationMonths)
navigateUserToCurrentPlans(paidUser)
SubscriptionRobot.apply {
verifySubscriptionIsShown()
verifyCannotManagePlansFromMobile()
verifyNoPaidPlansAreShown()
}
}
@Test
fun userWithPaidPlanCardAndIAPPayment1month() {
quark.setPaymentMethods(AppStore.GooglePlay, card = true, paypal = false, inApp = true)
val paidUserCycle1 = User(plan = Plan.MailPlus)
val cycle1 = PlanCycle.OTHER.apply {
cycleDurationMonths = 1
}
val user = quark.seedNewSubscriberWithCycle(paidUserCycle1, cycle1.cycleDurationMonths)
login(user)
CoreexampleRobot().plansCurrent()
SubscriptionRobot.apply {
val context = ApplicationProvider.getApplicationContext<Context>()
val cycleText = context.resources.getQuantityString(
R.plurals.plans_billing_other_period,
cycle1.cycleDurationMonths,
cycle1.cycleDurationMonths
)
val calendar = Calendar.getInstance()
calendar.timeInMillis = System.currentTimeMillis()
calendar.add(Calendar.MONTH, 1)
verifyPlanRenewalDisplayed(context.calendarToDateFormat(calendar.time).toString())
verifyPlanCycleDisplayed(cycleText)
}
}
@Test
fun userWithPaidPlanCardAndIAPPayment12months() {
quark.setPaymentMethods(AppStore.GooglePlay, card = true, paypal = false, inApp = true)
val paidUserCycle12 = User(plan = Plan.MailPlus)
val cycle12 = PlanCycle.OTHER.apply {
cycleDurationMonths = 12
}
val user = quark.seedNewSubscriberWithCycle(paidUserCycle12, cycle12.cycleDurationMonths)
login(user)
CoreexampleRobot().plansCurrent()
SubscriptionRobot.apply {
val context = ApplicationProvider.getApplicationContext<Context>()
val calendar = Calendar.getInstance()
calendar.timeInMillis = System.currentTimeMillis()
calendar.add(Calendar.YEAR, 1)
verifyPlanRenewalDisplayed(context.calendarToDateFormat(calendar.time).toString())
verifyPlanCycleDisplayed(context.getString(R.string.plans_billing_yearly))
}
}
@Test
fun userWithPaidPlanCardAndIAPPayment15months() {
quark.setPaymentMethods(AppStore.GooglePlay, card = true, paypal = false, inApp = true)
val paidUserCycle15 = User(plan = Plan.VpnPlus)
val cycle15 = PlanCycle.OTHER.apply {
cycleDurationMonths = 15
}
val user = quark.seedNewSubscriberWithCycle(paidUserCycle15, cycle15.cycleDurationMonths)
login(user)
CoreexampleRobot().plansCurrent()
SubscriptionRobot.apply {
val context = ApplicationProvider.getApplicationContext<Context>()
val cycleText = context.resources.getQuantityString(
R.plurals.plans_billing_other_period,
cycle15.cycleDurationMonths,
cycle15.cycleDurationMonths
)
val calendar = Calendar.getInstance()
calendar.timeInMillis = System.currentTimeMillis()
calendar.add(Calendar.MONTH, 15)
verifyPlanRenewalDisplayed(context.calendarToDateFormat(calendar.time).toString())
verifyPlanCycleDisplayed(cycleText)
}
}
@Test
fun userWithPaidPlanCardAndIAPPayment30months() {
quark.setPaymentMethods(AppStore.GooglePlay, card = true, paypal = false, inApp = true)
val paidUserCycle30 = User(plan = Plan.VpnPlus)
val cycle30 = PlanCycle.OTHER.apply {
cycleDurationMonths = 30
}
val user = quark.seedNewSubscriberWithCycle(paidUserCycle30, cycle30.cycleDurationMonths)
login(user)
CoreexampleRobot().plansCurrent()
SubscriptionRobot.apply {
val context = ApplicationProvider.getApplicationContext<Context>()
val cycleText = context.resources.getQuantityString(
R.plurals.plans_billing_other_period,
cycle30.cycleDurationMonths,
cycle30.cycleDurationMonths
)
val calendar = Calendar.getInstance()
calendar.timeInMillis = System.currentTimeMillis()
calendar.add(Calendar.MONTH, 30)
verifyPlanRenewalDisplayed(context.calendarToDateFormat(calendar.time).toString())
verifyPlanCycleDisplayed(cycleText)
}
}
private fun Context.calendarToDateFormat(date: Date) = HtmlCompat.fromHtml(
String.format(
getString(R.string.plans_renewal_date),
DateFormat.getDateInstance().format(date)
),
HtmlCompat.FROM_HTML_MODE_LEGACY
)
}
@@ -0,0 +1,141 @@
/*
* Copyright (c) 2023 Proton AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ProtonCore is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
*/
package me.proton.core.test.android.uitests.tests.medium.plans
import me.proton.core.domain.entity.AppStore
import me.proton.core.paymentiap.test.robot.GoogleIAPRobot
import me.proton.core.plan.test.robot.SubscriptionRobot
import me.proton.core.test.android.robots.CoreRobot
import me.proton.core.test.android.robots.auth.AddAccountRobot
import me.proton.core.test.android.robots.auth.signup.ChooseUsernameRobot
import me.proton.core.test.android.robots.auth.signup.RecoveryMethodsRobot
import me.proton.core.test.android.robots.humanverification.HVRobot
import me.proton.core.test.android.robots.payments.AddCreditCardRobot
import me.proton.core.test.android.uitests.tests.BaseTest
import me.proton.core.test.android.uitests.tests.SmokeTest
import me.proton.core.test.quark.data.Plan.Dev
import me.proton.core.test.quark.data.Plan.Free
import me.proton.core.test.quark.data.User
import org.junit.After
import org.junit.Before
import org.junit.Test
class DynamicSelectPlanTests : BaseTest() {
@After
fun setDefaults() {
quark.setDefaultPaymentMethods()
}
@Before
fun goToPlanSelection() {
quark.jailUnban()
AddAccountRobot()
.createAccount()
.chooseInternalEmail()
.verify { domainInputDisplayed() }
val user = User()
ChooseUsernameRobot()
.username(user.name)
.next()
.setAndConfirmPassword<RecoveryMethodsRobot>(user.password)
.skip()
.skipConfirm<CoreRobot>()
}
@Test
fun verifyInitialState() {
quark.setPaymentMethods(AppStore.GooglePlay, card = true, paypal = false, inApp = false)
SubscriptionRobot.verifyAtLeastOnePlanIsShown()
}
@Test
fun selectFreeAndCancelHumanVerification() {
quark.setPaymentMethods(AppStore.GooglePlay, card = true, paypal = false, inApp = false)
SubscriptionRobot.selectPlan(Free)
HVRobot().apply {
verify { hvElementsDisplayed() }
}.close<CoreRobot>()
SubscriptionRobot.verifyAtLeastOnePlanIsShown()
}
@Test
@SmokeTest
fun selectPlusAndCancelPayment() {
quark.setPaymentMethods(AppStore.GooglePlay, card = true, paypal = false, inApp = false)
SubscriptionRobot.selectPlan(Dev)
AddCreditCardRobot()
.apply {
verify<AddCreditCardRobot.Verify> {
nextPaymentProviderButtonNotDisplayed()
addCreditCardElementsDisplayed()
}
}
.close<CoreRobot>()
SubscriptionRobot.verifyAtLeastOnePlanIsShown()
}
@Test
@SmokeTest
fun selectPlusAndCancelPaymentIAPOnly() {
quark.setPaymentMethods(AppStore.GooglePlay, card = false, paypal = false, inApp = true)
SubscriptionRobot.selectPlan(Dev)
GoogleIAPRobot()
.apply {
verify<GoogleIAPRobot.Verify> {
googleIAPElementsDisplayed()
nextPaymentProviderButtonIsNotVisible()
}
}
.close<CoreRobot>()
SubscriptionRobot.verifyAtLeastOnePlanIsShown()
}
@Test
@SmokeTest
fun selectPlusAndCancelPaymentIAPAndCard() {
quark.setPaymentMethods(AppStore.GooglePlay, card = true, paypal = false, inApp = true)
SubscriptionRobot.selectPlan(Dev)
AddCreditCardRobot()
.apply {
verify<AddCreditCardRobot.Verify> { nextPaymentProviderButtonDisplayed() }
}
.switchPaymentProvider<GoogleIAPRobot>()
GoogleIAPRobot()
.apply {
verify<GoogleIAPRobot.Verify> { googleIAPElementsDisplayed() }
}
.close<CoreRobot>()
SubscriptionRobot.verifyAtLeastOnePlanIsShown()
}
@Test
@SmokeTest
fun selectPlusNoPaymentProvidersAvailable() {
quark.setPaymentMethods(AppStore.GooglePlay, card = false, paypal = false, inApp = false)
SubscriptionRobot.selectPlan(Dev)
SubscriptionRobot.verifyAtLeastOnePlanIsShown()
}
}
@@ -0,0 +1,142 @@
/*
* Copyright (c) 2023 Proton AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ProtonCore is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
*/
package me.proton.core.test.android.uitests.tests.medium.plans
import me.proton.core.domain.entity.AppStore
import me.proton.core.plan.presentation.entity.PlanCycle
import me.proton.core.plan.test.robot.SubscriptionRobot
import me.proton.core.test.android.uitests.robot.CoreexampleRobot
import me.proton.core.test.android.uitests.tests.BaseTest
import me.proton.core.test.android.uitests.tests.SmokeTest
import me.proton.core.test.quark.data.Plan
import me.proton.core.test.quark.data.User
import org.junit.After
import org.junit.Before
import org.junit.Test
class DynamicUpgradePlanTests : BaseTest() {
private val coreExampleRobot = CoreexampleRobot()
@Before
override fun setUp() {
super.setUp()
quark.jailUnban()
}
@After
fun setDefaults() {
quark.setDefaultPaymentMethods()
}
@Test
@SmokeTest
fun userWithFreePlan() {
quark.setPaymentMethods(AppStore.GooglePlay, card = true, paypal = false, inApp = false)
val freeUser = quark.userCreate().first
login(freeUser)
coreExampleRobot.plansUpgrade()
SubscriptionRobot.apply {
togglePlan(Plan.Dev)
verifyCanGetPlan(Plan.Dev)
close()
}
coreExampleRobot.verify { accountSwitcherDisplayed() }
}
@Test
fun userWithPaidPlanCardPayment() {
quark.setPaymentMethods(AppStore.GooglePlay, card = true, paypal = false, inApp = false)
val paidUser = User(plan = Plan.Unlimited)
quark.seedNewSubscriberWithCycle(paidUser, PlanCycle.YEARLY.cycleDurationMonths)
login(paidUser)
coreExampleRobot.plansUpgrade()
SubscriptionRobot.verifyNoUpgradeAvailable()
}
@Test
fun userWithPaidPlanCardAndIAPPayment1month() {
quark.setPaymentMethods(AppStore.GooglePlay, card = true, paypal = false, inApp = true)
val paidUserCycle1 = User(plan = Plan.MailPlus)
val cycle1 = PlanCycle.OTHER.apply {
cycleDurationMonths = 1
}
val user = quark.seedNewSubscriberWithCycle(paidUserCycle1, cycle1.cycleDurationMonths)
login(user)
coreExampleRobot.plansUpgrade()
SubscriptionRobot.apply {
verifyNoPaidPlansAreShown()
verifyNoUpgradeAvailable()
}
}
@Test
fun userWithPaidPlanCardAndIAPPayment12months() {
quark.setPaymentMethods(AppStore.GooglePlay, card = true, paypal = false, inApp = true)
val paidUserCycle12 = User(plan = Plan.MailPlus)
val cycle12 = PlanCycle.OTHER.apply {
cycleDurationMonths = 12
}
val user = quark.seedNewSubscriberWithCycle(paidUserCycle12, cycle12.cycleDurationMonths)
login(user)
coreExampleRobot.plansUpgrade()
SubscriptionRobot.apply {
verifyNoPaidPlansAreShown()
verifyNoUpgradeAvailable()
}
}
@Test
fun userWithPaidPlanCardAndIAPPayment15months() {
quark.setPaymentMethods(AppStore.GooglePlay, card = true, paypal = false, inApp = true)
val paidUserCycle15 = User(plan = Plan.VpnPlus)
val cycle15 = PlanCycle.OTHER.apply {
cycleDurationMonths = 15
}
val user = quark.seedNewSubscriberWithCycle(paidUserCycle15, cycle15.cycleDurationMonths)
login(user)
coreExampleRobot.plansUpgrade()
SubscriptionRobot.apply {
verifyNoPaidPlansAreShown()
verifyNoUpgradeAvailable()
}
}
@Test
fun userWithPaidPlanCardAndIAPPayment30months() {
quark.setPaymentMethods(AppStore.GooglePlay, card = true, paypal = false, inApp = true)
val paidUserCycle15 = User(plan = Plan.VpnPlus)
val cycle15 = PlanCycle.OTHER.apply {
cycleDurationMonths = 30
}
val user = quark.seedNewSubscriberWithCycle(paidUserCycle15, cycle15.cycleDurationMonths)
login(user)
coreExampleRobot.plansUpgrade()
SubscriptionRobot.apply {
verifyNoPaidPlansAreShown()
verifyNoUpgradeAvailable()
}
}
}
@@ -1,6 +1,6 @@
/*
* Copyright (c) 2021 Proton Technologies AG
* This file is part of Proton Technologies AG and ProtonCore.
* Copyright (c) 2023 Proton AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -30,8 +30,10 @@ import me.proton.core.test.android.robots.plans.SelectPlanRobot
import me.proton.core.test.android.uitests.tests.BaseTest
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
@Ignore("Replaced with SelectDynamicPlanTests")
class SelectPlanForIAPTests : BaseTest() {
private var humanVerificationRobot: HVRobot? = null
@@ -1,6 +1,6 @@
/*
* Copyright (c) 2021 Proton Technologies AG
* This file is part of Proton Technologies AG and ProtonCore.
* Copyright (c) 2023 Proton AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -33,8 +33,10 @@ import me.proton.core.test.android.uitests.tests.BaseTest
import me.proton.core.test.android.uitests.tests.SmokeTest
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
@Ignore("Replaced with SelectDynamicPlanTests")
class SelectPlanTests : BaseTest() {
private var humanVerificationRobot: HVRobot? = null
@@ -28,8 +28,10 @@ import me.proton.core.test.android.uitests.tests.BaseTest
import me.proton.core.test.android.uitests.tests.SmokeTest
import org.junit.After
import org.junit.Before
import org.junit.Ignore
import org.junit.Test
@Ignore("Replaced with DynamicUpgradePlanTests")
class UpgradePlanTests : BaseTest() {
private val selectPlanRobot = SelectPlanRobot()
@@ -3,7 +3,7 @@
"Plans": {
"Type": 1,
"Name": "free",
"Title": "Proton Free",
"Title": "Free",
"MaxDomains": 0,
"MaxAddresses": 1,
"MaxCalendars": 1,
@@ -23,6 +23,7 @@ import androidx.startup.AppInitializer
import dagger.hilt.android.HiltAndroidApp
import me.proton.android.core.coreexample.init.AccountStateHandlerInitializer
import me.proton.android.core.coreexample.init.EventManagerInitializer
import me.proton.android.core.coreexample.init.FeatureFlagInitializer
import me.proton.android.core.coreexample.init.LoggerInitializer
import me.proton.core.keytransparency.presentation.init.KeyTransparencyInitializer
import me.proton.android.core.coreexample.init.WorkManagerInitializer
@@ -47,6 +48,7 @@ class CoreExampleApp : Application() {
initializeComponent(UnAuthSessionFetcherInitializer::class.java)
initializeComponent(KeyTransparencyInitializer::class.java)
initializeComponent(LoggerInitializer::class.java)
initializeComponent(FeatureFlagInitializer::class.java)
}
}
}
@@ -0,0 +1,46 @@
/*
* Copyright (c) 2023 Proton AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ProtonCore is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ProtonCore. If not, see <https://www.gnu.org/licenses/>.
*/
package me.proton.android.core.coreexample.init
import android.content.Context
import androidx.startup.Initializer
import dagger.hilt.EntryPoint
import dagger.hilt.InstallIn
import dagger.hilt.android.EntryPointAccessors
import dagger.hilt.components.SingletonComponent
import me.proton.core.featureflag.data.FeatureFlagRefreshStarter
class FeatureFlagInitializer: Initializer<Unit> {
override fun create(context: Context) {
EntryPointAccessors.fromApplication(
context.applicationContext,
FeatureFlagInitializerEntryPoint::class.java
).featureFlagRefreshStarter().start()
}
override fun dependencies(): List<Class<out Initializer<*>?>> = listOf(
WorkManagerInitializer::class.java
)
@EntryPoint
@InstallIn(SingletonComponent::class)
interface FeatureFlagInitializerEntryPoint {
fun featureFlagRefreshStarter(): FeatureFlagRefreshStarter
}
}
@@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright (c) 2020 Proton Technologies AG
~ This file is part of Proton Technologies AG and ProtonCore.
~ Copyright (c) 2023 Proton AG
~ This file is part of Proton AG and ProtonCore.
~
~ ProtonCore is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
@@ -51,9 +51,11 @@
android:layout_marginStart="@dimen/default_gap"
android:layout_marginEnd="@dimen/default_gap"
android:layout_marginBottom="@dimen/gap_medium"
android:visibility="gone"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" />
app:layout_constraintStart_toStartOf="parent"
tools:visibility="visible" />
<me.proton.core.presentation.ui.view.ProtonProgressButton
android:id="@+id/payButton"
+8
View File
@@ -40,9 +40,17 @@ public final class me/proton/core/plan/test/MinimalUpgradeTests$DefaultImpls {
public final class me/proton/core/plan/test/robot/SubscriptionRobot {
public static final field INSTANCE Lme/proton/core/plan/test/robot/SubscriptionRobot;
public final fun close ()V
public final fun selectPlan (Lme/proton/core/test/quark/data/Plan;)V
public final fun togglePlan (Lme/proton/core/test/quark/data/Plan;)V
public final fun verifyAtLeastOnePlanIsShown ()V
public final fun verifyCanGetPlan (Lme/proton/core/test/quark/data/Plan;)V
public final fun verifyCannotManagePlansFromMobile ()V
public final fun verifyCannotUpgradeFromMobile ()V
public final fun verifyNoPaidPlansAreShown ()V
public final fun verifyNoUpgradeAvailable ()V
public final fun verifyPlanCycleDisplayed (Ljava/lang/String;)V
public final fun verifyPlanRenewalDisplayed (Ljava/lang/String;)V
public final fun verifySubscriptionIsShown ()V
public final fun verifyUpgradeIsShown ()V
public final fun verifyUpgradeYourPlanTextIsDisplayed ()V
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Proton Technologies AG
* Copyright (c) 2023 Proton AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore is free software: you can redistribute it and/or modify
@@ -19,7 +19,7 @@
package me.proton.core.plan.test
import me.proton.core.plan.test.robot.SubscriptionRobot.verifyAtLeastOnePlanIsShown
import me.proton.core.plan.test.robot.SubscriptionRobot.verifyCannotUpgradeFromMobile
import me.proton.core.plan.test.robot.SubscriptionRobot.verifyCannotManagePlansFromMobile
import me.proton.core.plan.test.robot.SubscriptionRobot.verifyNoPaidPlansAreShown
import me.proton.core.plan.test.robot.SubscriptionRobot.verifySubscriptionIsShown
import me.proton.core.plan.test.robot.SubscriptionRobot.verifyUpgradeYourPlanTextIsDisplayed
@@ -69,6 +69,6 @@ public interface MinimalSubscriptionTests {
quark.setPaymentMethods(card = false, paypal = false, inApp = false)
startSubscription(users.getUser { it.isPaid })
verifySubscriptionIsShown()
verifyCannotUpgradeFromMobile()
verifyCannotManagePlansFromMobile()
}
}
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Proton Technologies AG
* Copyright (c) 2023 Proton AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore is free software: you can redistribute it and/or modify
@@ -20,19 +20,32 @@ package me.proton.core.plan.test.robot
import androidx.test.espresso.matcher.ViewMatchers
import me.proton.core.plan.presentation.R
import me.proton.core.test.quark.data.Plan
import me.proton.test.fusion.Fusion.device
import me.proton.test.fusion.Fusion.view
import me.proton.test.fusion.FusionConfig
import me.proton.test.fusion.ui.espresso.builders.OnView
public object SubscriptionRobot {
private val toolbar = view.withId(R.id.toolbar)
private val currentPlan = view.withId(R.id.subscription)
private val planSelection = view.withId(R.id.plan_selection)
private val upgradeYourPlanText = view.withText(R.string.plans_upgrade_your_plan)
.withClassName("TextView")
private val planSelectionWithCardView =
view.withId(R.id.plan_selection).hasDescendant(view.withId(R.id.card_view))
private val upgradeYourPlanText =
view.withId(R.id.title).withText(R.string.plans_upgrade_your_plan)
private val upgradeYourPlanTitle = view.withText(R.string.plans_upgrade_your_plan)
.hasAncestor(toolbar)
private val cannotUpgrade = view.withText(R.string.plans_can_not_upgrade_from_mobile)
private val cardView = view.withId(R.id.card_view)
private val managementInfo = view.withId(R.id.management_info)
private val noUpgradeAvailableTextView = view.withText(R.string.plans_no_upgrade_available)
// region Actions
public fun close() {
device.pressBack()
}
private fun currentPlanIsDisplayed() {
currentPlan.await { checkIsDisplayed() }
@@ -53,14 +66,44 @@ public object SubscriptionRobot {
private fun planSelectionIsDisplayed() {
planSelection.scrollTo()
planSelection.await { checkIsDisplayed() }
planSelection.hasDescendant(cardView).checkIsDisplayed()
planSelectionWithCardView.await { checkIsDisplayed() }
}
private fun expandAndSelectFirstPlan() {
planSelection.hasDescendant(cardView).click()
planSelectionWithCardView.click()
view.withCustomMatcher(ViewMatchers.withSubstring("Get"))
}
private fun togglePlanItem(plan: Plan) {
view.withId(R.id.title).containsText(plan.text).scrollTo().click()
}
private fun getPlanButton(plan: Plan): OnView {
val buttonText = FusionConfig.targetContext.getString(R.string.plans_get_proton, plan.text)
return view.withId(R.id.content_button).containsText(buttonText)
}
private fun expandAndSelectPlan(plan: Plan) {
togglePlanItem(plan)
getPlanButton(plan)
.scrollTo()
.click()
}
public fun selectPlan(plan: Plan) {
planSelectionIsDisplayed()
expandAndSelectPlan(plan)
}
public fun togglePlan(plan: Plan) {
togglePlanItem(plan)
}
// endregion
// region Verifications
public fun verifySubscriptionIsShown() {
currentPlanIsDisplayed()
}
@@ -89,4 +132,29 @@ public object SubscriptionRobot {
public fun verifyCannotUpgradeFromMobile() {
cannotUpgrade.checkIsDisplayed()
}
public fun verifyNoUpgradeAvailable() {
noUpgradeAvailableTextView.await { checkIsDisplayed() }
}
public fun verifyCannotManagePlansFromMobile() {
managementInfo.checkContainsText(R.string.plans_manage_your_subscription_other)
}
public fun verifyPlanRenewalDisplayed(value: String) {
view.withId(R.id.content_renewal).withText(value).await { checkIsDisplayed() }
}
public fun verifyPlanCycleDisplayed(value: String) {
view.withId(R.id.price_cycle).withText(value).await { checkIsDisplayed() }
}
public fun verifyCanGetPlan(plan: Plan) {
getPlanButton(plan)
.scrollTo()
.checkIsDisplayed()
.checkIsEnabled()
}
// endregion
}
+1
View File
@@ -216,6 +216,7 @@ public object Module {
public const val planDomain: String = "$plan:plan-domain"
public const val planData: String = "$plan:plan-data"
public const val planPresentation: String = "$plan:plan-presentation"
public const val planTest: String = "$plan:plan-test"
public const val push: String = ":push"
public const val pushDomain: String = "$push:push-domain"
@@ -1,6 +1,6 @@
/*
* Copyright (c) 2021 Proton Technologies AG
* This file is part of Proton Technologies AG and ProtonCore.
* Copyright (c) 2023 Proton AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -28,6 +28,7 @@ import me.proton.core.test.quark.data.Plan
import me.proton.core.test.android.robots.CoreRobot
import me.proton.core.test.android.robots.CoreVerify
@Deprecated("Does not work with dynamic plans.")
class SelectPlanRobot : CoreRobot() {
/**
@@ -1,5 +1,5 @@
/*
* Copyright (c) 2022 Proton Technologies AG
* Copyright (c) 2023 Proton AG
* This file is part of Proton AG and ProtonCore.
*
* ProtonCore is free software: you can redistribute it and/or modify
@@ -31,7 +31,7 @@ public enum class Plan(public var planName: String, public var text: String) {
@Deprecated("This plan is no longer enabled.")
Plus("plus", "ProtonMail Plus"),
Free("free", "Proton Free"),
Free("free", "Free"),
MailPlus("mail2022", "Mail Plus"),
Unlimited("bundle2022", "Proton Unlimited"),
VpnPlus("vpn2022", "VPN Plus"),