Skip to content

Commit

Permalink
Add initial CustomerSheet end-to-end tests.
Browse files Browse the repository at this point in the history
  • Loading branch information
samer-stripe committed May 24, 2024
1 parent 33efc82 commit bbf2a42
Show file tree
Hide file tree
Showing 7 changed files with 246 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,14 @@ import com.stripe.android.paymentsheet.example.playground.settings.CollectAddres
import com.stripe.android.paymentsheet.example.playground.settings.CollectEmailSettingsDefinition
import com.stripe.android.paymentsheet.example.playground.settings.CollectNameSettingsDefinition
import com.stripe.android.paymentsheet.example.playground.settings.CollectPhoneSettingsDefinition
import com.stripe.android.paymentsheet.example.playground.settings.Country
import com.stripe.android.paymentsheet.example.playground.settings.CountrySettingsDefinition
import com.stripe.android.paymentsheet.example.playground.settings.CustomerSettingsDefinition
import com.stripe.android.paymentsheet.example.playground.settings.CustomerSheetPaymentMethodModeDefinition
import com.stripe.android.paymentsheet.example.playground.settings.CustomerType
import com.stripe.android.paymentsheet.example.playground.settings.DefaultBillingAddress
import com.stripe.android.paymentsheet.example.playground.settings.DefaultBillingAddressSettingsDefinition
import com.stripe.android.paymentsheet.example.playground.settings.PaymentMethodMode
import com.stripe.android.paymentsheet.example.samples.ui.shared.PAYMENT_METHOD_SELECTOR_TEST_TAG
import com.stripe.android.paymentsheet.ui.SAVED_PAYMENT_OPTION_TEST_TAG
import com.stripe.android.test.core.AuthorizeAction
Expand Down Expand Up @@ -312,4 +318,112 @@ internal class TestCard : BasePlaygroundTest() {
values = FieldPopulator.Values(cardNumber = "4000000000003220")
)
}

@Test
fun testCardInCustomerSheet() {
testDriver.savePaymentMethodInCustomerSheet(
TestParameters.create(paymentMethodCode = "card").copyPlaygroundSettings { settings ->
settings[CustomerSettingsDefinition] = CustomerType.NEW
settings[CountrySettingsDefinition] = Country.US
settings[CustomerSheetPaymentMethodModeDefinition] = PaymentMethodMode.CreateAndAttach
},
populateCustomLpmFields = {
populateCardDetails()
},
)
}

@Test
fun testCardWithSetupIntentInCustomerSheet() {
testDriver.savePaymentMethodInCustomerSheet(
TestParameters.create(paymentMethodCode = "card").copyPlaygroundSettings { settings ->
settings[CustomerSettingsDefinition] = CustomerType.NEW
settings[CountrySettingsDefinition] = Country.US
settings[CustomerSheetPaymentMethodModeDefinition] = PaymentMethodMode.SetupIntent
},
populateCustomLpmFields = {
populateCardDetails()
},
)
}

@Test
fun testCardWithNonUsMerchantInCustomerSheet() {
testDriver.savePaymentMethodInCustomerSheet(
TestParameters.create(paymentMethodCode = "card").copyPlaygroundSettings { settings ->
settings[CustomerSettingsDefinition] = CustomerType.NEW
settings[CountrySettingsDefinition] = Country.FR
settings[CustomerSheetPaymentMethodModeDefinition] = PaymentMethodMode.CreateAndAttach
},
populateCustomLpmFields = {
populateCardDetails()
},
)
}

@Test
fun testCardWithSetupIntentAndNonUsMerchantInCustomerSheet() {
testDriver.savePaymentMethodInCustomerSheet(
TestParameters.create(paymentMethodCode = "card").copyPlaygroundSettings { settings ->
settings[CustomerSettingsDefinition] = CustomerType.NEW
settings[CountrySettingsDefinition] = Country.FR
settings[CustomerSheetPaymentMethodModeDefinition] = PaymentMethodMode.SetupIntent
},
populateCustomLpmFields = {
populateCardDetails()
},
)
}

@Test
fun testCardWithBillingDetailsCollectionInCustomerSheet() {
testDriver.savePaymentMethodInCustomerSheet(
TestParameters.create(
paymentMethodCode = "card",
) { settings ->
settings[CustomerSettingsDefinition] = CustomerType.NEW
settings[CountrySettingsDefinition] = Country.US
settings[DefaultBillingAddressSettingsDefinition] = DefaultBillingAddress.Off
settings[CollectNameSettingsDefinition] =
PaymentSheet.BillingDetailsCollectionConfiguration.CollectionMode.Always
settings[CollectEmailSettingsDefinition] =
PaymentSheet.BillingDetailsCollectionConfiguration.CollectionMode.Always
settings[CollectPhoneSettingsDefinition] =
PaymentSheet.BillingDetailsCollectionConfiguration.CollectionMode.Always
settings[CollectAddressSettingsDefinition] =
PaymentSheet.BillingDetailsCollectionConfiguration.AddressCollectionMode.Full
},
populateCustomLpmFields = {
populateCardDetails()
populateEmail()
populateName("Name on card")
populateAddress()
populatePhoneNumber()
},
)
}

@Test
fun testCardWithBillingDetailsCollectionWithDefaultsInCustomerSheet() {
testDriver.savePaymentMethodInCustomerSheet(
TestParameters.create(
paymentMethodCode = "card",
) { settings ->
settings[CustomerSettingsDefinition] = CustomerType.NEW
settings[CountrySettingsDefinition] = Country.US
settings[DefaultBillingAddressSettingsDefinition] = DefaultBillingAddress.On
settings[CollectNameSettingsDefinition] =
PaymentSheet.BillingDetailsCollectionConfiguration.CollectionMode.Always
settings[CollectEmailSettingsDefinition] =
PaymentSheet.BillingDetailsCollectionConfiguration.CollectionMode.Always
settings[CollectPhoneSettingsDefinition] =
PaymentSheet.BillingDetailsCollectionConfiguration.CollectionMode.Always
settings[CollectAddressSettingsDefinition] =
PaymentSheet.BillingDetailsCollectionConfiguration.AddressCollectionMode.Full
},
populateCustomLpmFields = {
populateCardDetails()
},
)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import androidx.compose.ui.test.SemanticsNodeInteraction
import androidx.compose.ui.test.assertIsDisplayed
import androidx.compose.ui.test.assertTextEquals
import androidx.compose.ui.test.hasTestTag
import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.junit4.ComposeTestRule
import androidx.compose.ui.test.onAllNodesWithTag
import androidx.compose.ui.test.onAllNodesWithText
Expand Down Expand Up @@ -313,6 +314,54 @@ internal class PlaygroundTestDriver(
teardown()
}

fun savePaymentMethodInCustomerSheet(
testParameters: TestParameters,
values: FieldPopulator.Values = FieldPopulator.Values(),
populateCustomLpmFields: FieldPopulator.() -> Unit = {},
): PlaygroundState? {
setup(
testParameters.copyPlaygroundSettings { settings ->
settings.updateConfigurationData { configurationData ->
configurationData.copy(
integrationType = PlaygroundConfigurationData.IntegrationType.CustomerSheet
)
}
}
)

launchCustomerSheet()

if (isManagePaymentMethodScreen()) {
addPaymentMethodNode().performClick()
}

selectors.paymentSelection.click()

val fieldPopulator = FieldPopulator(
selectors,
testParameters,
populateCustomLpmFields,
{},
values,
)
fieldPopulator.populateFields()

val result = playgroundState

pressCustomerSheetSave()

waitForManageSavedPaymentMethods()

pressCustomerSheetConfirm()

Espresso.onIdle()
composeTestRule.waitForIdle()

teardown()

return result
}

private fun pressMultiStepSelect() {
selectors.multiStepSelect.click()
waitForNotPlaygroundActivity()
Expand All @@ -325,6 +374,20 @@ internal class PlaygroundTestDriver(
}
}

private fun pressCustomerSheetSave() {
Espresso.onIdle()
composeTestRule.waitForIdle()

selectors.customerSheetSaveButton.click()
}

private fun pressCustomerSheetConfirm() {
Espresso.onIdle()
composeTestRule.waitForIdle()

selectors.customerSheetConfirmButton.click()
}

/**
* This will open the payment sheet complete flow from the playground with a new or
* guest user and complete the confirmation including any browser interactions.
Expand Down Expand Up @@ -840,6 +903,17 @@ internal class PlaygroundTestDriver(
}
}

private fun launchCustomerSheet() {
selectors.reload.click()
Espresso.onIdle()
selectors.composeTestRule.waitForIdle()

selectors.multiStepSelect.waitForEnabled()
selectors.multiStepSelect.click()

waitForNotPlaygroundActivity()
}

private fun doAuthorization() {
selectors.apply {
val checkoutMode =
Expand Down Expand Up @@ -1048,6 +1122,12 @@ internal class PlaygroundTestDriver(
}.isSuccess
}

private fun isManagePaymentMethodScreen(): Boolean {
return runCatching {
composeTestRule.onNodeWithText("Manage your payment methods").assertIsDisplayed()
}.isSuccess
}

private fun addPaymentMethodNode(): SemanticsNodeInteraction {
waitForAddPaymentMethodNode()
return composeTestRule.onNodeWithTag(ADD_PAYMENT_METHOD_NODE_TAG)
Expand All @@ -1058,6 +1138,14 @@ internal class PlaygroundTestDriver(
composeTestRule.waitUntilAtLeastOneExists(hasTestTag(ADD_PAYMENT_METHOD_NODE_TAG), 5000L)
}

@OptIn(ExperimentalTestApi::class)
private fun waitForManageSavedPaymentMethods() {
composeTestRule.waitUntilAtLeastOneExists(
hasText("Manage your payment methods"),
DEFAULT_UI_TIMEOUT.inWholeMilliseconds
)
}

private companion object {
const val ADD_PAYMENT_METHOD_NODE_TAG = "${SAVED_PAYMENT_METHOD_CARD_TEST_TAG}_+ Add"
const val FINANCIAL_CONNECTIONS_ACTIVITY =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@ import androidx.test.uiautomator.UiObject
import androidx.test.uiautomator.UiSelector
import androidx.test.uiautomator.Until
import com.google.common.truth.Truth.assertThat
import com.stripe.android.customersheet.ui.CUSTOMER_SHEET_CONFIRM_BUTTON_TEST_TAG
import com.stripe.android.customersheet.ui.CUSTOMER_SHEET_SAVE_BUTTON_TEST_TAG
import com.stripe.android.model.PaymentMethod.Type.Blik
import com.stripe.android.model.PaymentMethod.Type.CashAppPay
import com.stripe.android.paymentsheet.example.playground.RELOAD_TEST_TAG
Expand Down Expand Up @@ -74,6 +76,16 @@ internal class Selectors(
}
)

val customerSheetSaveButton = ComposeButton(
composeTestRule,
hasTestTag(CUSTOMER_SHEET_SAVE_BUTTON_TEST_TAG)
)

val customerSheetConfirmButton = ComposeButton(
composeTestRule,
hasTestTag(CUSTOMER_SHEET_CONFIRM_BUTTON_TEST_TAG)
)

val externalPaymentMethodSucceedButton = ComposeButton(
composeTestRule,
hasTestTag(FawryActivity.COMPLETED_BUTTON_TEST_TAG)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,13 +106,16 @@ internal class PaymentSheetPlaygroundActivity : AppCompatActivity(), ExternalPay
val localPlaygroundSettings = playgroundSettings ?: return@setContent

val playgroundState by viewModel.state.collectAsState()
val customerAdapter by viewModel.customerAdapter.collectAsState()

val customerSheet = playgroundState?.asCustomerState()?.let { customerPlaygroundState ->
rememberCustomerSheet(
configuration = customerPlaygroundState.customerSheetConfiguration(),
customerAdapter = customerPlaygroundState.adapter,
callback = viewModel::onCustomerSheetCallback
)
customerAdapter?.let { adapter ->
rememberCustomerSheet(
configuration = customerPlaygroundState.customerSheetConfiguration(),
customerAdapter = adapter,
callback = viewModel::onCustomerSheetCallback
)
}
}

PlaygroundTheme(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ import kotlinx.coroutines.launch
import kotlinx.serialization.json.Json
import java.io.IOException

@OptIn(ExperimentalCustomerSheetApi::class)
internal class PaymentSheetPlaygroundViewModel(
application: Application,
launchUri: Uri?,
Expand All @@ -61,6 +62,7 @@ internal class PaymentSheetPlaygroundViewModel(
val state = MutableStateFlow<PlaygroundState?>(null)
val flowControllerState = MutableStateFlow<FlowControllerState?>(null)
val customerSheetState = MutableStateFlow<CustomerSheetState?>(null)
val customerAdapter = MutableStateFlow<CustomerAdapter?>(null)

init {
viewModelScope.launch(Dispatchers.IO) {
Expand All @@ -76,6 +78,7 @@ internal class PaymentSheetPlaygroundViewModel(
state.value = null
flowControllerState.value = null
customerSheetState.value = null
customerAdapter.value = null

if (playgroundSettings.configurationData.value.integrationType.isPaymentFlow()) {
prepareCheckout(playgroundSettings)
Expand Down Expand Up @@ -149,22 +152,22 @@ internal class PaymentSheetPlaygroundViewModel(

snapshot.saveToSharedPreferences(getApplication())

state.value = PlaygroundState.Customer(
snapshot = snapshot,
adapter = CustomerAdapter.create(
context = getApplication(),
customerEphemeralKeyProvider = {
fetchEphemeralKey(snapshot)
},
setupIntentClientSecretProvider = if (
snapshot[CustomerSheetPaymentMethodModeDefinition] == PaymentMethodMode.SetupIntent
) {
{ customerId -> createSetupIntentClientSecret(snapshot, customerId) }
} else {
null
}
)
val adapter = CustomerAdapter.create(
context = getApplication(),
customerEphemeralKeyProvider = {
fetchEphemeralKey(snapshot)
},
setupIntentClientSecretProvider = if (
snapshot[CustomerSheetPaymentMethodModeDefinition] == PaymentMethodMode.SetupIntent
) {
{ customerId -> createSetupIntentClientSecret(snapshot, customerId) }
} else {
null
}
)

state.value = PlaygroundState.Customer(snapshot = snapshot)
customerAdapter.value = adapter
customerSheetState.value = CustomerSheetState()
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package com.stripe.android.paymentsheet.example.playground

import androidx.compose.runtime.Stable
import com.stripe.android.customersheet.CustomerAdapter
import com.stripe.android.customersheet.CustomerSheet
import com.stripe.android.customersheet.ExperimentalCustomerSheetApi
import com.stripe.android.paymentsheet.PaymentSheet
Expand Down Expand Up @@ -50,7 +49,6 @@ internal sealed interface PlaygroundState {
@OptIn(ExperimentalCustomerSheetApi::class)
data class Customer(
private val snapshot: PlaygroundSettings.Snapshot,
val adapter: CustomerAdapter,
) : PlaygroundState {
override val integrationType = snapshot.configurationData.integrationType
override val countryCode = snapshot[CountrySettingsDefinition]
Expand Down
Loading

0 comments on commit bbf2a42

Please sign in to comment.