Skip to content
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
4 changes: 0 additions & 4 deletions e2e/config/matrix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,6 @@ tags:
exclude:
- flaky
- wip
# The remaining `full` tests place a real order and live under tests/react-native/,
# so they cannot run on the Swift and Kotlin rows. Drop this entry when the shared
# ordering tests replace them.
- full
applications:
- id: react-native-ios
target: react-native
Expand Down
4 changes: 3 additions & 1 deletion e2e/flows/checkout/assert-complete.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
appId: ${E2E_APP_ID}
---
# The payment provider and the order creation both run before the page changes, so this
# wait covers two remote systems rather than a render.
- extendedWaitUntil:
visible: "${POST_SUBMIT_RESULT_PATTERN}"
timeout: 10000
timeout: 60000
8 changes: 6 additions & 2 deletions e2e/flows/checkout/assert-returned-to-empty-cart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,14 @@ appId: ${E2E_APP_ID}
---
- runFlow: close.yaml

# The four samples return to four different screens after checkout closes, so the flow
# asserts nothing about which screen that is. The cart control belongs to the app chrome
# and stays on screen everywhere, which makes it both the "we are back" signal and the
# way to the cart.
- extendedWaitUntil:
visible:
id: catalog-tab
timeout: 10000
id: cart-tab
timeout: 30000

- tapOn:
id: cart-tab
Expand Down
9 changes: 9 additions & 0 deletions e2e/flows/checkout/dismiss-active-field.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,15 @@ appId: ${E2E_APP_ID}
platform: android
commands:
- hideKeyboard
# iOS keeps the keyboard above the page until the accessory bar closes it. The next field
# then stays behind the keyboard, and the tap that follows lands on a key instead.
- runFlow:
when:
platform: ios
visible: "Done"
commands:
- tapOn: "Done"
- waitForAnimationToEnd
- runFlow:
when:
platform: ios
Expand Down
43 changes: 43 additions & 0 deletions e2e/flows/checkout/fill-billing-address.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
appId: ${E2E_APP_ID}
---
# Checkout offers a "use the shipping address" control only when it can copy the address
# the cart supplied. The samples build their carts through different mutations, so one
# target gets the control and another gets an empty billing form. The scroll below stops
# at whichever the target shows, and the branch fills the form only when there is one.
- scrollUntilVisible:
element:
text: "^(Billing address|Use shipping address as billing address)$"
direction: DOWN
centerElement: true
- runFlow:
when:
visible: "^Billing address$"
commands:
# Country/Region and Province already hold the shop defaults, so this flow leaves
# them alone and supplies an address that suits those defaults.
- scrollUntilVisible:
element:
text: "^First name( \\(optional\\))?$"
direction: DOWN
centerElement: true
- tapOn:
text: "^First name( \\(optional\\))?$"
- inputText: "${BILLING_FIRST_NAME}"
- runFlow: dismiss-active-field.yaml
- tapOn:
text: "^Last name$"
- inputText: "${BILLING_LAST_NAME}"
- runFlow: dismiss-active-field.yaml
- tapOn:
text: "^Address$"
- inputText: "${BILLING_ADDRESS_LINE1}"
- runFlow: dismiss-active-field.yaml
- tapOn:
text: "^City$"
- inputText: "${BILLING_CITY}"
- runFlow: dismiss-active-field.yaml
- tapOn:
text: "^Postal code$"
- inputText: "${BILLING_POSTAL_CODE}"
- runFlow: dismiss-active-field.yaml
- waitForAnimationToEnd
9 changes: 8 additions & 1 deletion e2e/flows/checkout/fill-payment-card.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,9 @@ appId: ${E2E_APP_ID}
text: "^Card number$"
- waitForAnimationToEnd
- inputText: "${CARD_NUMBER}"
# Checkout groups the digits, so the field never reads back the value the test typed.
- extendedWaitUntil:
visible: "^${CARD_NUMBER}$"
visible: "^${CARD_NUMBER_DISPLAY}$"
- runFlow: dismiss-active-field.yaml
- scrollUntilVisible:
element:
Expand All @@ -31,4 +32,10 @@ appId: ${E2E_APP_ID}
- waitForAnimationToEnd
- inputText: "${CARD_SECURITY_CODE}"
- runFlow: dismiss-active-field.yaml
- tapOn:
text: "^Name on card$"
- waitForAnimationToEnd
- eraseText
- inputText: "${CARD_HOLDER_NAME}"
- runFlow: dismiss-active-field.yaml
- waitForAnimationToEnd
26 changes: 24 additions & 2 deletions e2e/flows/checkout/submit.yaml
Original file line number Diff line number Diff line change
@@ -1,10 +1,32 @@
appId: ${E2E_APP_ID}
---
# The keyboard from the last payment field can still cover the foot of the page, so the
# control has to reach the middle of the screen before the tap. `centerElement` does that.
# A `visibilityPercentage` below 100 rounds down to zero, which lets the scroll stop early,
# and a tap that lands on the keyboard does nothing while Maestro still reports it as done.
- scrollUntilVisible:
element:
text: "^(Pay now|Complete order)$"
text: "^(Pay now|Complete order|Review order)$"
direction: DOWN
visibilityPercentage: 90
centerElement: true

# A shop that shows shipping options keeps the pay action behind a review step. One button
# carries both labels, so the review tap comes first and the pay tap follows it.
- runFlow:
when:
visible: "^Review order$"
commands:
- tapOn:
text: "^Review order$"
enabled: true
- extendedWaitUntil:
visible: "^(Pay now|Complete order)$"
timeout: 30000
- scrollUntilVisible:
element:
text: "^(Pay now|Complete order)$"
direction: DOWN
centerElement: true
- tapOn:
text: "^(Pay now|Complete order)$"
enabled: true
31 changes: 31 additions & 0 deletions e2e/test/checkout_payment_fixture_test.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# frozen_string_literal: true

require "minitest/autorun"
require "yaml"

class CheckoutPaymentFixtureTest < Minitest::Test
E2E_ROOT = File.expand_path("..", __dir__)
CHECKOUT_PATH = File.join(E2E_ROOT, "tests", "shared", "checkout-hardcoded-buyer-identity.yaml")
PAYMENT_FLOW_PATH = File.join(E2E_ROOT, "flows", "checkout", "fill-payment-card.yaml")

def yaml_documents(path)
File.read(path).split(/^---\s*$/).map { |document| YAML.safe_load(document) }
end

def test_successful_checkout_uses_the_bogus_gateway_approval_number
checkout = yaml_documents(CHECKOUT_PATH).first

assert_equal "1", checkout.fetch("env").fetch("CARD_NUMBER")
assert_equal "1", checkout.fetch("env").fetch("CARD_NUMBER_DISPLAY")
end

def test_payment_flow_replaces_the_prefilled_cardholder_name
commands = yaml_documents(PAYMENT_FLOW_PATH).last
name_field_index = commands.index { |command| command.is_a?(Hash) && command.dig("tapOn", "text") == "^Name on card$" }
erase_index = commands.index("eraseText")
cardholder_name_index = commands.index { |command| command == {"inputText" => "${CARD_HOLDER_NAME}"} }

assert_equal name_field_index + 2, erase_index
assert_equal erase_index + 1, cardholder_name_index
end
end
4 changes: 2 additions & 2 deletions e2e/test/e2e_matrix_to_browserstack_run_plan_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ def test_runs_carry_default_tags_and_the_other_platform_exclusion
android_run = run_for("kotlin-android")

assert_equal ["launch", "checkout"], ios_run.fetch("include_tags")
assert_equal ["flaky", "wip", "full", "android-only"], ios_run.fetch("exclude_tags")
assert_equal ["flaky", "wip", "full", "ios-only"], android_run.fetch("exclude_tags")
assert_equal ["flaky", "wip", "android-only"], ios_run.fetch("exclude_tags")
assert_equal ["flaky", "wip", "ios-only"], android_run.fetch("exclude_tags")
end

def test_an_application_overrides_the_default_tags
Expand Down
3 changes: 3 additions & 0 deletions e2e/tests/react-native/checkout-guest.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@ name: React Native checkout - guest
tags:
- checkout
- full
# Only React Native runs this today, and every row now runs the whole tests folder.
# The shared guest test replaces this file, and this tag goes with it.
- wip

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This gets removed in #561 when we promote guest to run on kotlin/swift


env:
# Sample app buyer identity configuration
Expand Down
24 changes: 0 additions & 24 deletions e2e/tests/react-native/checkout-hardcoded-buyer-identity.yaml

This file was deleted.

39 changes: 39 additions & 0 deletions e2e/tests/shared/checkout-hardcoded-buyer-identity.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
appId: ${E2E_APP_ID}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Moved here from e2e/tests/react-native

name: Checkout with hardcoded buyer identity
tags:
- checkout
# This test submits a real order, so it belongs to the expensive tier.
- full

env:
# The cart carries the contact and the delivery address, so checkout asks for payment
# only. That makes this the shortest real order, and the first one to fix when checkout
# breaks.
E2E_CART_PARAMS: "productIndex=0&quantity=1&buyerIdentityMode=hardcoded"

# Checkout payment fixture. The shop runs the bogus gateway, which rejects real card
# brands and reads a single digit instead: 1 approves, 2 declines, 3 fails the gateway.
CARD_NUMBER: "1"
CARD_NUMBER_DISPLAY: "1"
CARD_SECURITY_CODE: "123"
CARD_HOLDER_NAME: "Maestro Shopify"

# Checkout billing fixture. The cart supplies a one-time delivery address, which the
# billing section cannot copy, so the test types a billing address of its own. Country
# and province keep the shop defaults, so this address stays in Canada.
BILLING_FIRST_NAME: "Maestro"
BILLING_LAST_NAME: "Shopify"
BILLING_ADDRESS_LINE1: "620 King Street West"
BILLING_CITY: "Toronto"
BILLING_POSTAL_CODE: "M5V 1M7"

# Accepted successful checkout states for this smoke test.
POST_SUBMIT_RESULT_PATTERN: ".*(Thank you|Your order|Order confirmed|confirmation).*"
---
- runFlow: ../../flows/app/bootstrap-cart-from-link.yaml
- runFlow: ../../flows/checkout/present.yaml
- runFlow: ../../flows/checkout/fill-payment-card.yaml
- runFlow: ../../flows/checkout/fill-billing-address.yaml
- runFlow: ../../flows/checkout/submit.yaml
- runFlow: ../../flows/checkout/assert-complete.yaml
- runFlow: ../../flows/checkout/assert-returned-to-empty-cart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,15 @@ def customerAccountRedirectHost = customerAccountRedirect?.host ?: "callback"
// Demo buyer identity (prefill toggle in Settings)
def prefillEmail = properties.getProperty("EMAIL", properties.getProperty("PREFILL_EMAIL", "test.buyer@example.com"))
def prefillPhone = properties.getProperty("PHONE", properties.getProperty("PREFILL_PHONE", "+14165550100"))
def prefillFirstName = propertyOrDefault("FIRST_NAME", "Evelyn")
def prefillLastName = propertyOrDefault("LAST_NAME", "Hartley")
def prefillAddress1 = propertyOrDefault("ADDRESS_1", "650 King Street")
def prefillAddress2 = propertyOrDefault("ADDRESS_2", "Shopify HQ")
def prefillCompany = propertyOrDefault("COMPANY", "Shopify")
def prefillCity = propertyOrDefault("CITY", "Toronto")
def prefillProvince = propertyOrDefault("PROVINCE", "ON")
def prefillZip = propertyOrDefault("ZIP", "M5V 1M7")
def prefillCountry = propertyOrDefault("COUNTRY", "CA")

if (!storefrontDomain || !accessToken) {
println("**** Please add a .env file with STOREFRONT_DOMAIN and STOREFRONT_ACCESS_TOKEN set *****")
Expand Down Expand Up @@ -93,6 +102,15 @@ android {
buildConfigField "String", "customerAccountApiGraphQLBaseUrl", "\"$customerAccountApiGraphQLBaseUrl\""
buildConfigField "String", "prefillEmail", "\"$prefillEmail\""
buildConfigField "String", "prefillPhone", "\"$prefillPhone\""
buildConfigField "String", "prefillFirstName", "\"$prefillFirstName\""
buildConfigField "String", "prefillLastName", "\"$prefillLastName\""
buildConfigField "String", "prefillAddress1", "\"$prefillAddress1\""
buildConfigField "String", "prefillAddress2", "\"$prefillAddress2\""
buildConfigField "String", "prefillCompany", "\"$prefillCompany\""
buildConfigField "String", "prefillCity", "\"$prefillCity\""
buildConfigField "String", "prefillProvince", "\"$prefillProvince\""
buildConfigField "String", "prefillZip", "\"$prefillZip\""
buildConfigField "String", "prefillCountry", "\"$prefillCountry\""
}

signingConfigs {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -146,9 +146,10 @@ fun CheckoutKitAppRoot(
)
},
actions = {
IconButton(onClick = {
navController.navigate(Screen.Cart.route)
}) {
IconButton(
onClick = { navController.navigate(Screen.Cart.route) },
modifier = Modifier.testTag(AccessibilityIdentifiers.Tabs.CART),
) {
BadgedBox(badge = {
if (totalQuantity > 0) {
Badge(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,10 @@ object AccessibilityIdentifiers {
object Cart {
const val CHECKOUT_READY = "cart-checkout-ready"
const val CHECKOUT_BUTTON = "checkout-button"
const val EMPTY_MESSAGE = "cart-empty-message"
}

object Tabs {
const val CART = "cart-tab"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ private fun EmptyCartMessage(
) {
Header2(
text = stringResource(id = R.string.cart_empty),
modifier = Modifier.testTag(AccessibilityIdentifiers.Cart.EMPTY_MESSAGE),
)
BodyMedium(
stringResource(id = R.string.cart_emtpy_description),
Expand Down
Loading
Loading