Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add E2E test for sepa debit saved payment method flows. #7452

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package com.stripe.android.lpm

import androidx.compose.ui.test.assertContentDescriptionEquals
import androidx.compose.ui.test.hasTestTag
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performTextInput
import androidx.test.ext.junit.runners.AndroidJUnit4
import com.stripe.android.BasePlaygroundTest
Expand Down Expand Up @@ -34,11 +36,7 @@ internal class TestSepaDebit : BasePlaygroundTest() {
testDriver.confirmNewOrGuestComplete(
testParameters = testParameters,
) {
rules.compose.onNodeWithText("IBAN").apply {
performTextInput(
"DE89370400440532013000"
)
}
fillOutIban()
}
}

Expand All @@ -50,11 +48,7 @@ internal class TestSepaDebit : BasePlaygroundTest() {
settings[CheckoutModeSettingsDefinition] = CheckoutMode.PAYMENT_WITH_SETUP
}
) {
rules.compose.onNodeWithText("IBAN").apply {
performTextInput(
"DE89370400440532013000"
)
}
fillOutIban()
}
}

Expand All @@ -66,11 +60,7 @@ internal class TestSepaDebit : BasePlaygroundTest() {
settings[CheckoutModeSettingsDefinition] = CheckoutMode.SETUP
}
) {
rules.compose.onNodeWithText("IBAN").apply {
performTextInput(
"DE89370400440532013000"
)
}
fillOutIban()
}
}

Expand All @@ -79,11 +69,7 @@ internal class TestSepaDebit : BasePlaygroundTest() {
testDriver.confirmCustom(
testParameters = testParameters,
populateCustomLpmFields = {
rules.compose.onNodeWithText("IBAN").apply {
performTextInput(
"DE89370400440532013000"
)
}
fillOutIban()
},
verifyCustomLpmFields = {
rules.compose.onNodeWithText("IBAN").apply {
Expand All @@ -94,4 +80,61 @@ internal class TestSepaDebit : BasePlaygroundTest() {
}
)
}

@Test
fun testSepaDebitDefaultReturningUserFlowWithoutShowingFlowController() {
val testParameters = testParameters.copyPlaygroundSettings { settings ->
settings[AutomaticPaymentMethodsSettingsDefinition] = true
settings[CheckoutModeSettingsDefinition] = CheckoutMode.SETUP
}

// Create the payment method and set it as default by going through the whole flow.
val playgroundState = testDriver.confirmNewOrGuestComplete(
testParameters = testParameters,
) {
fillOutIban()
}

testDriver.confirmCustomWithDefaultSavedPaymentMethod(
customerId = playgroundState?.customerConfig?.id,
testParameters = testParameters,
afterBuyAction = {
rules.compose.onNode(hasTestTag("SEPA_MANDATE_CONTINUE_BUTTON"))
.performClick()
}
)
}

@Test
fun testSepaDebitDefaultReturningUserFlowWithShowingFlowController() {
val testParameters = testParameters.copyPlaygroundSettings { settings ->
settings[AutomaticPaymentMethodsSettingsDefinition] = true
settings[CheckoutModeSettingsDefinition] = CheckoutMode.SETUP
}

// Create the payment method and set it as default by going through the whole flow.
val playgroundState = testDriver.confirmNewOrGuestComplete(
testParameters = testParameters,
) {
fillOutIban()
}

testDriver.confirmCustomWithDefaultSavedPaymentMethod(
customerId = playgroundState?.customerConfig?.id,
testParameters = testParameters,
beforeBuyAction = { selectors ->
selectors.multiStepSelect.click()
selectors.paymentSelection.click()
selectors.continueButton.click()
}
)
}

private fun fillOutIban() {
rules.compose.onNodeWithText("IBAN").apply {
performTextInput(
"DE89370400440532013000"
)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,18 @@ import com.google.common.truth.Truth.assertThat
import com.karumi.shot.ScreenshotTest
import com.stripe.android.paymentsheet.PAYMENT_OPTION_CARD_TEST_TAG
import com.stripe.android.paymentsheet.example.playground.PaymentSheetPlaygroundActivity
import com.stripe.android.paymentsheet.example.playground.PlaygroundState
import com.stripe.android.paymentsheet.example.playground.settings.CheckoutMode
import com.stripe.android.paymentsheet.example.playground.settings.CheckoutModeSettingsDefinition
import com.stripe.android.paymentsheet.example.playground.settings.CustomerSettingsDefinition
import com.stripe.android.paymentsheet.example.playground.settings.CustomerType
import com.stripe.android.paymentsheet.example.playground.settings.IntegrationType
import com.stripe.android.paymentsheet.example.playground.settings.IntegrationTypeSettingsDefinition
import com.stripe.android.test.core.ui.BrowserUI
import com.stripe.android.test.core.ui.Selectors
import com.stripe.android.test.core.ui.UiAutomatorText
import kotlinx.coroutines.launch
import org.junit.Assert.fail
import org.junit.Assume
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
Expand All @@ -55,13 +59,17 @@ internal class PlaygroundTestDriver(
private val device: UiDevice,
private val composeTestRule: ComposeTestRule,
) : ScreenshotTest {
@Volatile
private var resultValue: String? = null
private lateinit var testParameters: TestParameters
private lateinit var selectors: Selectors

private val currentActivity = Array<Activity?>(1) { null }
private var application: Application? = null

@Volatile
private var playgroundState: PlaygroundState? = null

private val activityLifecycleCallbacks = object : Application.ActivityLifecycleCallbacks {
override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {}
override fun onActivityStarted(activity: Activity) {}
Expand Down Expand Up @@ -201,6 +209,36 @@ internal class PlaygroundTestDriver(
teardown()
}

fun confirmCustomWithDefaultSavedPaymentMethod(
customerId: String?,
testParameters: TestParameters,
beforeBuyAction: (Selectors) -> Unit = {},
afterBuyAction: (Selectors) -> Unit = {},
) {
if (customerId == null) {
fail("No customer id")
return
}

setup(
testParameters.copyPlaygroundSettings { settings ->
settings[IntegrationTypeSettingsDefinition] = IntegrationType.FlowController
settings[CustomerSettingsDefinition] = CustomerType.Existing(customerId)
}
)
launchCustom(clickMultiStep = false)

beforeBuyAction(selectors)

selectors.playgroundBuyButton.click()

afterBuyAction(selectors)

doAuthorization()

teardown()
}

private fun pressMultiStepSelect() {
selectors.multiStepSelect.click()
waitForNotPlaygroundActivity()
Expand All @@ -221,7 +259,7 @@ internal class PlaygroundTestDriver(
testParameters: TestParameters,
values: FieldPopulator.Values = FieldPopulator.Values(),
populateCustomLpmFields: () -> Unit = {},
) {
): PlaygroundState? {
setup(testParameters)
launchComplete()

Expand All @@ -241,11 +279,15 @@ internal class PlaygroundTestDriver(
testParameters.useBrowser
)

val result = playgroundState

pressBuy()

doAuthorization()

teardown()

return result
}

/**
Expand Down Expand Up @@ -378,16 +420,18 @@ internal class PlaygroundTestDriver(
waitForNotPlaygroundActivity()
}

private fun launchCustom() {
private fun launchCustom(clickMultiStep: Boolean = true) {
selectors.reload.click()
Espresso.onIdle()
selectors.composeTestRule.waitForIdle()

selectors.multiStepSelect.waitForEnabled()
selectors.multiStepSelect.click()
if (clickMultiStep) {
selectors.multiStepSelect.click()

// PaymentOptionsActivity is now on screen
waitForNotPlaygroundActivity()
// PaymentOptionsActivity is now on screen
waitForNotPlaygroundActivity()
}
}

private fun doAuthorization() {
Expand Down Expand Up @@ -513,6 +557,13 @@ internal class PlaygroundTestDriver(
resultValue = it?.message
}
}

activity.lifecycleScope.launch {
activity.viewModel.state.collect { playgroundState ->
[email protected] = playgroundState
}
}

launchPlayground.countDown()
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,8 @@ internal class Selectors(
}
)

val playgroundBuyButton = ComposeButton(composeTestRule, hasTestTag(CHECKOUT_TEST_TAG))

val addPaymentMethodButton = AddPaymentMethodButton(device)

val selectBrowserPrompt = UiAutomatorText("Verify your payment", device = device)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,7 @@ import com.stripe.android.paymentsheet.example.playground.model.CheckoutRequest

internal object CustomerSettingsDefinition :
PlaygroundSettingDefinition<CustomerType>,
PlaygroundSettingDefinition.Saveable<CustomerType> by EnumSaveable(
key = "customer",
values = CustomerType.values(),
defaultValue = CustomerType.GUEST,
),
PlaygroundSettingDefinition.Saveable<CustomerType>,
PlaygroundSettingDefinition.Displayable<CustomerType> {
override val displayName: String = "Customer"
override val options: List<PlaygroundSettingDefinition.Displayable.Option<CustomerType>> =
Expand All @@ -35,11 +31,36 @@ internal object CustomerSettingsDefinition :
) {
configurationBuilder.customer(playgroundState.customerConfig)
}

override val key: String = "customer"
override val defaultValue: CustomerType = CustomerType.GUEST

override fun convertToValue(value: String): CustomerType {
val hardcodedCustomerTypes = mapOf(
CustomerType.GUEST.value to CustomerType.GUEST,
CustomerType.NEW.value to CustomerType.NEW,
CustomerType.RETURNING.value to CustomerType.RETURNING,
)
return if (value.startsWith("cus_")) {
CustomerType.Existing(value)
} else if (hardcodedCustomerTypes.containsKey(value)) {
hardcodedCustomerTypes[value]!!
} else {
defaultValue
}
}

override fun convertToString(value: CustomerType): String {
return value.value
}
}

enum class CustomerType(override val value: String) : ValueEnum {
GUEST("guest"),
NEW("new"),
RETURNING("returning"),
SNAPSHOT("snapshot"),
sealed class CustomerType(val value: String) {
object GUEST : CustomerType("guest")

object NEW : CustomerType("new")

object RETURNING : CustomerType("returning")

class Existing(customerId: String) : CustomerType(customerId)
}