Skip to content

Commit

Permalink
Add E2E test for sepa debit saved payment method flows.
Browse files Browse the repository at this point in the history
  • Loading branch information
jaynewstrom-stripe committed Oct 17, 2023
1 parent d4621ce commit 9b8e511
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 35 deletions.
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 ->
this@PlaygroundTestDriver.playgroundState = 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)
}

0 comments on commit 9b8e511

Please sign in to comment.