diff --git a/e2e/README.md b/e2e/README.md index 9a317eccf..160c2b568 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -148,10 +148,10 @@ ruby e2e/scripts/e2e_matrix_to_browserstack_run_plan count - `tests/shared/launch-smoke.yaml` is the shared launch smoke test. - `tests/shared/checkout-present-and-close.yaml` seeds a cart through the control link, presents checkout, closes it, and asserts dismissal. -- `tests/react-native/checkout-guest.yaml` composes the React Native guest - checkout smoke test from those subflows. -- `tests/react-native/checkout-hardcoded-buyer-identity.yaml` verifies checkout - from a bootstrapped cart with hardcoded buyer identity. +- `tests/shared/checkout-hardcoded-buyer-identity.yaml` orders from a cart that + already carries the contact and the delivery address. +- `tests/shared/checkout-guest.yaml` orders from an empty identity, so checkout + asks for the contact and the delivery address as well as the payment. - `scripts/run_local_e2e` builds and installs any of the four local targets. - `scripts/run_maestro` is their single Maestro invocation. It holds the environment contract and target-specific test-file selection in one place. diff --git a/e2e/flows/app/hide-keyboard.yaml b/e2e/flows/app/hide-keyboard.yaml index 9b14a8004..a0b9d67b3 100644 --- a/e2e/flows/app/hide-keyboard.yaml +++ b/e2e/flows/app/hide-keyboard.yaml @@ -10,14 +10,6 @@ appId: ${E2E_APP_ID} - runFlow: when: platform: ios - visible: "Done" commands: - - tapOn: "Done" - - waitForAnimationToEnd -- runFlow: - when: - platform: ios - visible: "selected" - commands: - - tapOn: "selected" + - tapOn: "^(Done|selected)$" - waitForAnimationToEnd diff --git a/e2e/flows/checkout/assert-hosted-text-field-value.yaml b/e2e/flows/checkout/assert-hosted-text-field-value.yaml new file mode 100644 index 000000000..cfe6b5cb5 --- /dev/null +++ b/e2e/flows/checkout/assert-hosted-text-field-value.yaml @@ -0,0 +1,42 @@ +appId: ${E2E_APP_ID} +--- +- runFlow: + when: + platform: ios + commands: + - scrollUntilVisible: + element: + text: "^Field container for: ${FIELD_LABEL}$" + direction: UP + centerElement: true + - runFlow: + when: + true: ${HAS_NEXT_FIELD == 'true'} + commands: + - extendedWaitUntil: + visible: + text: "^${EXPECTED_TEXT}$" + below: + text: "^${FIELD_LABEL}$" + above: + text: "^${NEXT_FIELD_LABEL}$" + - runFlow: + when: + true: ${HAS_NEXT_FIELD == 'false'} + commands: + - extendedWaitUntil: + visible: + text: "^${EXPECTED_TEXT}$" + below: + text: "^${FIELD_LABEL}$" +- runFlow: + when: + platform: android + commands: + - scrollUntilVisible: + element: + text: "^${FIELD_LABEL}$" + direction: UP + centerElement: true + - extendedWaitUntil: + visible: "^${EXPECTED_TEXT}$" diff --git a/e2e/flows/checkout/dismiss-active-field.yaml b/e2e/flows/checkout/dismiss-active-field.yaml index 1f51ca805..417df4131 100644 --- a/e2e/flows/checkout/dismiss-active-field.yaml +++ b/e2e/flows/checkout/dismiss-active-field.yaml @@ -1,9 +1,3 @@ appId: ${E2E_APP_ID} --- - runFlow: ../app/hide-keyboard.yaml -- runFlow: - when: - visible: "Close suggestions" - commands: - - tapOn: "Close suggestions" - - waitForAnimationToEnd diff --git a/e2e/flows/checkout/fill-billing-address.yaml b/e2e/flows/checkout/fill-billing-address.yaml index 1519aff0a..ad55de65c 100644 --- a/e2e/flows/checkout/fill-billing-address.yaml +++ b/e2e/flows/checkout/fill-billing-address.yaml @@ -1,9 +1,7 @@ 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. +# Checkout may offer the shipping address or display an open billing form. Stop at +# whichever state the journey presents and fill the form only when it is open. - scrollUntilVisible: element: text: "^(Billing address|Use shipping address as billing address)$" @@ -15,29 +13,30 @@ appId: ${E2E_APP_ID} 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: "^${BILLING_POSTAL_FIELD_LABEL}$" - - inputText: "${BILLING_POSTAL_CODE}" - - runFlow: dismiss-active-field.yaml + - runFlow: + file: fill-text-field.yaml + env: + LOCATOR: "^First name( \\(optional\\))?$" + TEXT_TO_INPUT: "${BILLING_FIRST_NAME}" + - runFlow: + file: fill-text-field.yaml + env: + LOCATOR: "^Last name$" + TEXT_TO_INPUT: "${BILLING_LAST_NAME}" + - runFlow: + file: fill-text-field.yaml + env: + LOCATOR: "^Address$" + TEXT_TO_INPUT: "${BILLING_ADDRESS_LINE1}" + DISMISS_SUGGESTIONS: "true" + - runFlow: + file: fill-text-field.yaml + env: + LOCATOR: "^City$" + TEXT_TO_INPUT: "${BILLING_CITY}" + - runFlow: + file: fill-text-field.yaml + env: + LOCATOR: "^${BILLING_POSTAL_FIELD_LABEL}$" + TEXT_TO_INPUT: "${BILLING_POSTAL_CODE}" - waitForAnimationToEnd diff --git a/e2e/flows/checkout/fill-contact.yaml b/e2e/flows/checkout/fill-contact.yaml index 3e0d6dde2..22516ebbb 100644 --- a/e2e/flows/checkout/fill-contact.yaml +++ b/e2e/flows/checkout/fill-contact.yaml @@ -7,19 +7,3 @@ appId: ${E2E_APP_ID} - runFlow: dismiss-active-field.yaml - extendedWaitUntil: visible: "^${EMAIL}$" -- tapOn: - text: "^First name( \\(optional\\))?$" -- waitForAnimationToEnd -- inputText: "${FIRST_NAME}" -- runFlow: dismiss-active-field.yaml -- scrollUntilVisible: - element: - text: "^Last name$" - direction: DOWN - visibilityPercentage: 100 - centerElement: true -- tapOn: - text: "^Last name$" -- waitForAnimationToEnd -- inputText: "${LAST_NAME}" -- runFlow: dismiss-active-field.yaml diff --git a/e2e/flows/checkout/fill-hosted-text-field.yaml b/e2e/flows/checkout/fill-hosted-text-field.yaml new file mode 100644 index 000000000..f493f067f --- /dev/null +++ b/e2e/flows/checkout/fill-hosted-text-field.yaml @@ -0,0 +1,39 @@ +appId: ${E2E_APP_ID} +--- +- runFlow: + when: + platform: ios + commands: + - runFlow: + when: + true: ${SCROLL_TO_FIELD == 'true'} + commands: + - runFlow: + file: scroll-until-visible-bidirectional.yaml + env: + LOCATOR: "^Field container for: ${FIELD_LABEL}$" + INITIAL_DIRECTION: "DOWN" + # The first post-scroll tap positions the hosted container. The second gives + # its iframe input focus before text entry. + - tapOn: + text: "^Field container for: ${FIELD_LABEL}$" + - waitForAnimationToEnd + - tapOn: + text: "^Field container for: ${FIELD_LABEL}$" +- runFlow: + when: + platform: android + commands: + - runFlow: + when: + true: ${SCROLL_TO_FIELD == 'true'} + commands: + - runFlow: + file: scroll-until-visible-bidirectional.yaml + env: + LOCATOR: "^${FIELD_LABEL}$" + INITIAL_DIRECTION: "DOWN" + - tapOn: + text: "^${FIELD_LABEL}$" +- waitForAnimationToEnd +- inputText: "${TEXT_TO_INPUT}" diff --git a/e2e/flows/checkout/fill-payment-card.yaml b/e2e/flows/checkout/fill-payment-card.yaml index 0504c8515..1018199f3 100644 --- a/e2e/flows/checkout/fill-payment-card.yaml +++ b/e2e/flows/checkout/fill-payment-card.yaml @@ -1,41 +1,81 @@ appId: ${E2E_APP_ID} --- -- scrollUntilVisible: - element: - text: "^Card number$" - direction: DOWN - centerElement: true -- tapOn: - 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_DISPLAY}$" -- runFlow: dismiss-active-field.yaml -- scrollUntilVisible: - element: - text: "^Expiration date \\(MM / YY\\)$" - direction: DOWN - centerElement: true -- tapOn: - text: "^Expiration date \\(MM / YY\\)$" -- waitForAnimationToEnd -- inputText: "1" +# Enter Card number and dismiss its keyboard before moving to Expiration. +- runFlow: + file: fill-hosted-text-field.yaml + env: + FIELD_LABEL: "Card number" + SCROLL_TO_FIELD: "true" + TEXT_TO_INPUT: "${CARD_NUMBER}" +- runFlow: + when: + platform: ios + visible: "^(Done|selected)$" + commands: + - runFlow: dismiss-active-field.yaml + +# Enter expiration in two pairs so Checkout has time to format the separator. +- runFlow: + file: fill-hosted-text-field.yaml + env: + FIELD_LABEL: "Expiration date \\(MM / YY\\)" + SCROLL_TO_FIELD: "false" + TEXT_TO_INPUT: "1" - inputText: "2" - waitForAnimationToEnd - inputText: "3" - inputText: "0" - runFlow: dismiss-active-field.yaml -- tapOn: - text: "^Security code$" -- waitForAnimationToEnd -- inputText: "${CARD_SECURITY_CODE}" -- runFlow: dismiss-active-field.yaml -- tapOn: - text: "^Name on card$" -- waitForAnimationToEnd -- eraseText -- inputText: "${CARD_HOLDER_NAME}" + +- runFlow: + file: fill-hosted-text-field.yaml + env: + FIELD_LABEL: "Security code" + SCROLL_TO_FIELD: "false" + TEXT_TO_INPUT: "${CARD_SECURITY_CODE}" - runFlow: dismiss-active-field.yaml + +# Preserve Checkout's cardholder prefill and only supply the name when it is absent. +- runFlow: + when: + notVisible: + text: "^${CARD_HOLDER_NAME}$" + below: + text: "^Security code$" + commands: + - tapOn: + text: "^Name on card$" + - waitForAnimationToEnd + - inputText: "${CARD_HOLDER_NAME}" + - runFlow: dismiss-active-field.yaml + +# Validate every hosted field from the bottom up after input is complete. +- runFlow: + file: assert-hosted-text-field-value.yaml + env: + FIELD_LABEL: "Name on card" + NEXT_FIELD_LABEL: "" + HAS_NEXT_FIELD: "false" + EXPECTED_TEXT: "${CARD_HOLDER_NAME}" +- runFlow: + file: assert-hosted-text-field-value.yaml + env: + FIELD_LABEL: "Security code" + NEXT_FIELD_LABEL: "Name on card" + HAS_NEXT_FIELD: "true" + EXPECTED_TEXT: "${CARD_SECURITY_CODE}" +- runFlow: + file: assert-hosted-text-field-value.yaml + env: + FIELD_LABEL: "Expiration date \\(MM / YY\\)" + NEXT_FIELD_LABEL: "Security code" + HAS_NEXT_FIELD: "true" + EXPECTED_TEXT: "12 / 30" +- runFlow: + file: assert-hosted-text-field-value.yaml + env: + FIELD_LABEL: "Card number" + NEXT_FIELD_LABEL: "Expiration date \\(MM / YY\\)" + HAS_NEXT_FIELD: "true" + EXPECTED_TEXT: "${CARD_NUMBER_DISPLAY}" - waitForAnimationToEnd diff --git a/e2e/flows/checkout/fill-shipping-address.yaml b/e2e/flows/checkout/fill-shipping-address.yaml index 3efa178a8..58ea9f09f 100644 --- a/e2e/flows/checkout/fill-shipping-address.yaml +++ b/e2e/flows/checkout/fill-shipping-address.yaml @@ -1,9 +1,13 @@ appId: ${E2E_APP_ID} --- +# Every scroll below asks for `centerElement`. A `visibilityPercentage` under 100 rounds +# down to zero instead, which lets the scroll stop before it moves, and the tap that +# follows then lands on whatever already sat there. - scrollUntilVisible: element: text: "Country/Region" direction: DOWN + centerElement: true # If the country is already selected and visible, skip this. Otherwise open the # picker, only scroll if needed, then select the configured country. @@ -15,79 +19,70 @@ appId: ${E2E_APP_ID} text: "Country/Region" - waitForAnimationToEnd - runFlow: - when: - notVisible: "^${COUNTRY_LABEL}$" - commands: - - scrollUntilVisible: - element: - text: "^${COUNTRY_LABEL}$" - direction: DOWN - visibilityPercentage: 10 - optional: true - - runFlow: - when: - notVisible: "^${COUNTRY_LABEL}$" - commands: - - scrollUntilVisible: - element: - text: "^${COUNTRY_LABEL}$" - direction: UP - visibilityPercentage: 10 + file: scroll-until-visible-bidirectional.yaml + env: + LOCATOR: "^${COUNTRY_LABEL}$" + INITIAL_DIRECTION: "DOWN" - tapOn: text: "^${COUNTRY_LABEL}$" - waitForAnimationToEnd +- runFlow: + file: fill-text-field.yaml + env: + LOCATOR: "^First name( \\(optional\\))?$" + TEXT_TO_INPUT: "${FIRST_NAME}" +- runFlow: + file: fill-text-field.yaml + env: + LOCATOR: "^Last name$" + TEXT_TO_INPUT: "${LAST_NAME}" +- runFlow: + file: fill-text-field.yaml + env: + LOCATOR: "Address" + TEXT_TO_INPUT: "${ADDRESS_LINE1}" + DISMISS_SUGGESTIONS: "true" +- runFlow: + file: fill-text-field.yaml + env: + LOCATOR: "^City$" + TEXT_TO_INPUT: "${CITY}" - scrollUntilVisible: element: - text: "Address" + text: "^${STATE_FIELD_LABEL}$" direction: DOWN centerElement: true - tapOn: - text: "Address" + text: "^${STATE_FIELD_LABEL}$" - waitForAnimationToEnd -- inputText: "${ADDRESS_LINE1}" -- runFlow: dismiss-active-field.yaml -- scrollUntilVisible: - element: - text: "^City$" - direction: DOWN - centerElement: true +- runFlow: + file: scroll-until-visible-bidirectional.yaml + env: + LOCATOR: "^${STATE_LABEL}$" + INITIAL_DIRECTION: "UP" - tapOn: - text: "^City$" + text: "^${STATE_LABEL}$" - waitForAnimationToEnd -- inputText: "${CITY}" -- runFlow: dismiss-active-field.yaml +- runFlow: + file: fill-text-field.yaml + env: + LOCATOR: "^${POSTAL_FIELD_LABEL}$" + TEXT_TO_INPUT: "${POSTAL_CODE}" + +# A shop decides whether the delivery address asks for a phone number, and it can make +# that field required. The scroll is optional so a shop without the field still passes. - scrollUntilVisible: element: - text: "^${STATE_FIELD_LABEL}$" + text: "^Phone$" direction: DOWN centerElement: true -- tapOn: - text: "^${STATE_FIELD_LABEL}$" -- waitForAnimationToEnd -- runFlow: - when: - notVisible: "^${STATE_LABEL}$" - commands: - - scrollUntilVisible: - element: - text: "^${STATE_LABEL}$" - direction: DOWN - visibilityPercentage: 10 - optional: true + optional: true - runFlow: when: - notVisible: "^${STATE_LABEL}$" + visible: "^Phone$" commands: - - scrollUntilVisible: - element: - text: "^${STATE_LABEL}$" - direction: UP - visibilityPercentage: 10 -- tapOn: - text: "^${STATE_LABEL}$" -- waitForAnimationToEnd -- tapOn: - text: "^${POSTAL_FIELD_LABEL}$" -- waitForAnimationToEnd -- inputText: "${POSTAL_CODE}" -- runFlow: dismiss-active-field.yaml + - tapOn: + text: "^Phone$" + - waitForAnimationToEnd + - inputText: "${PHONE}" + - runFlow: dismiss-active-field.yaml diff --git a/e2e/flows/checkout/fill-text-field.yaml b/e2e/flows/checkout/fill-text-field.yaml new file mode 100644 index 000000000..f61daf5e2 --- /dev/null +++ b/e2e/flows/checkout/fill-text-field.yaml @@ -0,0 +1,24 @@ +appId: ${E2E_APP_ID} +--- +- scrollUntilVisible: + element: + text: "${LOCATOR}" + direction: DOWN + centerElement: true +- tapOn: + text: "${LOCATOR}" +- waitForAnimationToEnd +- inputText: "${TEXT_TO_INPUT}" +- runFlow: dismiss-active-field.yaml +- extendedWaitUntil: + visible: "^${TEXT_TO_INPUT}$" +- runFlow: + when: + true: ${typeof DISMISS_SUGGESTIONS !== 'undefined' && DISMISS_SUGGESTIONS == 'true'} + commands: + - runFlow: + when: + visible: "Close suggestions" + commands: + - tapOn: "Close suggestions" + - waitForAnimationToEnd diff --git a/e2e/flows/checkout/scroll-until-visible-bidirectional.yaml b/e2e/flows/checkout/scroll-until-visible-bidirectional.yaml new file mode 100644 index 000000000..593cce322 --- /dev/null +++ b/e2e/flows/checkout/scroll-until-visible-bidirectional.yaml @@ -0,0 +1,32 @@ +appId: ${E2E_APP_ID} +--- +- runFlow: + when: + true: ${INITIAL_DIRECTION == 'UP'} + commands: + - scrollUntilVisible: + element: + text: "${LOCATOR}" + direction: UP + centerElement: true + optional: true + - scrollUntilVisible: + element: + text: "${LOCATOR}" + direction: DOWN + centerElement: true +- runFlow: + when: + true: ${INITIAL_DIRECTION == 'DOWN'} + commands: + - scrollUntilVisible: + element: + text: "${LOCATOR}" + direction: DOWN + centerElement: true + optional: true + - scrollUntilVisible: + element: + text: "${LOCATOR}" + direction: UP + centerElement: true diff --git a/e2e/flows/checkout/use-shipping-address-as-billing-address.yaml b/e2e/flows/checkout/use-shipping-address-as-billing-address.yaml new file mode 100644 index 000000000..4ec783365 --- /dev/null +++ b/e2e/flows/checkout/use-shipping-address-as-billing-address.yaml @@ -0,0 +1,8 @@ +appId: ${E2E_APP_ID} +--- +- scrollUntilVisible: + element: + text: "^Use shipping address as billing address$" + direction: DOWN + centerElement: true +- assertVisible: "^Use shipping address as billing address$" diff --git a/e2e/test/checkout_payment_fixture_test.rb b/e2e/test/checkout_payment_fixture_test.rb deleted file mode 100644 index 63f30f751..000000000 --- a/e2e/test/checkout_payment_fixture_test.rb +++ /dev/null @@ -1,31 +0,0 @@ -# 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 diff --git a/e2e/tests/react-native/checkout-guest.yaml b/e2e/tests/shared/checkout-guest.yaml similarity index 50% rename from e2e/tests/react-native/checkout-guest.yaml rename to e2e/tests/shared/checkout-guest.yaml index 958dfcafc..82b212d9b 100644 --- a/e2e/tests/react-native/checkout-guest.yaml +++ b/e2e/tests/shared/checkout-guest.yaml @@ -1,33 +1,40 @@ appId: ${E2E_APP_ID} -name: React Native checkout - guest +name: Checkout as a guest tags: - checkout + # This test submits a real order, so it belongs to the expensive tier. - 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 env: - # Sample app buyer identity configuration + # The cart carries no contact and no delivery address, so checkout asks a guest for + # both. That makes this the longest real order, and the widest cover of the form. E2E_CART_PARAMS: "productIndex=0&quantity=1&buyerIdentityMode=guest" - # Checkout contact fixture + # Checkout contact fixture. EMAIL: "maestro.e2e@shopify.com" FIRST_NAME: "Maestro" LAST_NAME: "Shopify" - # Checkout shipping fixture + # Checkout delivery fixture. The shop opens on its own country, so this address picks a + # different one and makes the flow use the country picker. The two field labels follow + # the country: the United States names them State and ZIP code. COUNTRY_LABEL: "United States" ADDRESS_LINE1: "700 S Flower St" CITY: "Los Angeles" STATE_FIELD_LABEL: "State" STATE_LABEL: "California" - POSTAL_CODE: "90017" POSTAL_FIELD_LABEL: "ZIP code" + POSTAL_CODE: "90017" + # The shop requires a phone number on the delivery address. 555-01xx is the number range + # reserved for fiction, so this value reaches nobody. + PHONE: "213 555 0142" - # Checkout payment fixture + # Checkout payment fixture. The shop runs its credit card gateway in test mode, which + # accepts this number only. Checkout groups the digits, so the flow needs both forms. CARD_NUMBER: "1" + CARD_NUMBER_DISPLAY: "1" CARD_SECURITY_CODE: "123" + CARD_HOLDER_NAME: "Maestro Shopify" # Accepted successful checkout states for this smoke test. POST_SUBMIT_RESULT_PATTERN: ".*(Thank you|Your order|Order confirmed|confirmation).*" @@ -37,6 +44,7 @@ env: - runFlow: ../../flows/checkout/fill-contact.yaml - runFlow: ../../flows/checkout/fill-shipping-address.yaml - runFlow: ../../flows/checkout/fill-payment-card.yaml +- runFlow: ../../flows/checkout/use-shipping-address-as-billing-address.yaml - runFlow: ../../flows/checkout/submit.yaml - runFlow: ../../flows/checkout/assert-complete.yaml - runFlow: ../../flows/checkout/assert-returned-to-empty-cart.yaml diff --git a/e2e/tests/shared/checkout-hardcoded-buyer-identity.yaml b/e2e/tests/shared/checkout-hardcoded-buyer-identity.yaml index ffba5d9c1..9ad5e9c67 100644 --- a/e2e/tests/shared/checkout-hardcoded-buyer-identity.yaml +++ b/e2e/tests/shared/checkout-hardcoded-buyer-identity.yaml @@ -6,9 +6,8 @@ tags: - 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. + # The cart carries the contact and a selected reusable delivery address, so checkout + # uses that address for billing and asks for payment only. E2E_CART_PARAMS: "productIndex=0&quantity=1&buyerIdentityMode=hardcoded" # Checkout payment fixture. The shop runs the bogus gateway, which rejects real card @@ -16,17 +15,7 @@ env: 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_FIELD_LABEL: "Postal code" - BILLING_POSTAL_CODE: "M5V 1M7" + CARD_HOLDER_NAME: "Evelyn Hartley" # Accepted successful checkout states for this smoke test. POST_SUBMIT_RESULT_PATTERN: ".*(Thank you|Your order|Order confirmed|confirmation).*" @@ -34,7 +23,7 @@ env: - 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/use-shipping-address-as-billing-address.yaml - runFlow: ../../flows/checkout/submit.yaml - runFlow: ../../flows/checkout/assert-complete.yaml - runFlow: ../../flows/checkout/assert-returned-to-empty-cart.yaml diff --git a/platforms/android/samples/CheckoutKitAndroidDemo/app/src/main/java/com/shopify/checkoutkit/androiddemo/cart/data/DemoBuyerIdentity.kt b/platforms/android/samples/CheckoutKitAndroidDemo/app/src/main/java/com/shopify/checkoutkit/androiddemo/cart/data/DemoBuyerIdentity.kt index 044be913e..b5860ecbe 100644 --- a/platforms/android/samples/CheckoutKitAndroidDemo/app/src/main/java/com/shopify/checkoutkit/androiddemo/cart/data/DemoBuyerIdentity.kt +++ b/platforms/android/samples/CheckoutKitAndroidDemo/app/src/main/java/com/shopify/checkoutkit/androiddemo/cart/data/DemoBuyerIdentity.kt @@ -39,7 +39,6 @@ object DemoBuyerIdentity { ) ), selected = Optional.present(true), - oneTimeUse = Optional.present(true), ) ) ) diff --git a/platforms/android/samples/CheckoutKitAndroidDemo/app/src/test/java/com/shopify/checkoutkit/androiddemo/cart/data/DemoBuyerIdentityTest.kt b/platforms/android/samples/CheckoutKitAndroidDemo/app/src/test/java/com/shopify/checkoutkit/androiddemo/cart/data/DemoBuyerIdentityTest.kt index df60dcfc8..af2a7ec80 100644 --- a/platforms/android/samples/CheckoutKitAndroidDemo/app/src/test/java/com/shopify/checkoutkit/androiddemo/cart/data/DemoBuyerIdentityTest.kt +++ b/platforms/android/samples/CheckoutKitAndroidDemo/app/src/test/java/com/shopify/checkoutkit/androiddemo/cart/data/DemoBuyerIdentityTest.kt @@ -17,12 +17,12 @@ class DemoBuyerIdentityTest { } @Test - fun `delivery carries one pre-selected one-time address`() { + fun `delivery carries one pre-selected reusable address`() { val addresses = DemoBuyerIdentity.delivery.addresses.getOrThrow()!! assertThat(addresses).hasSize(1) assertThat(addresses.first().selected.getOrThrow()).isTrue() - assertThat(addresses.first().oneTimeUse.getOrThrow()).isTrue() + assertThat(addresses.first().oneTimeUse.getOrNull()).isNull() } @Test diff --git a/platforms/react-native/sample/src/__tests__/utils.test.ts b/platforms/react-native/sample/src/__tests__/utils.test.ts new file mode 100644 index 000000000..b8fd6c759 --- /dev/null +++ b/platforms/react-native/sample/src/__tests__/utils.test.ts @@ -0,0 +1,53 @@ +import Config from 'react-native-config'; +import {BuyerIdentityMode} from '../auth/types'; +import type {AppConfig} from '../context/Config'; +import {createBuyerIdentityCartInput} from '../utils'; + +jest.mock('react-native-config', () => ({ + EMAIL: 'buyer@example.com', + ADDRESS_1: '151 O Connor Street', + ADDRESS_2: '', + CITY: 'Ottawa', + COMPANY: '', + COUNTRY: 'CA', + FIRST_NAME: 'Evelyn', + LAST_NAME: 'Hartley', + PROVINCE: 'ON', + ZIP: 'K2P 2L8', + PHONE: '+16135550142', +})); + +describe('createBuyerIdentityCartInput', () => { + it('adds a selected reusable delivery address to a hardcoded cart', () => { + const input = createBuyerIdentityCartInput({ + buyerIdentityMode: BuyerIdentityMode.Hardcoded, + } as AppConfig); + + expect(input).toEqual({ + buyerIdentity: { + email: Config.EMAIL, + }, + delivery: { + addresses: [ + { + address: { + deliveryAddress: { + address1: Config.ADDRESS_1, + address2: Config.ADDRESS_2, + city: Config.CITY, + company: Config.COMPANY, + countryCode: Config.COUNTRY, + firstName: Config.FIRST_NAME, + lastName: Config.LAST_NAME, + phone: Config.PHONE, + provinceCode: Config.PROVINCE, + zip: Config.ZIP, + }, + }, + selected: true, + }, + ], + }, + }); + }); +}); diff --git a/platforms/react-native/sample/src/utils.ts b/platforms/react-native/sample/src/utils.ts index d061a08be..f34b83b6e 100644 --- a/platforms/react-native/sample/src/utils.ts +++ b/platforms/react-native/sample/src/utils.ts @@ -27,20 +27,27 @@ export function createBuyerIdentityCartInput( return { buyerIdentity: { email: EMAIL, - deliveryAddressPreferences: { - deliveryAddress: { - address1: ADDRESS_1, - address2: ADDRESS_2, - city: CITY, - company: COMPANY, - country: COUNTRY, - firstName: FIRST_NAME, - lastName: LAST_NAME, - phone: PHONE, - province: PROVINCE, - zip: ZIP, + }, + delivery: { + addresses: [ + { + address: { + deliveryAddress: { + address1: ADDRESS_1, + address2: ADDRESS_2, + city: CITY, + company: COMPANY, + countryCode: COUNTRY, + firstName: FIRST_NAME, + lastName: LAST_NAME, + phone: PHONE, + provinceCode: PROVINCE, + zip: ZIP, + }, + }, + selected: true, }, - }, + ], }, }; case BuyerIdentityMode.CustomerAccount: diff --git a/platforms/swift/Samples/CheckoutKitSwiftDemo/CheckoutKitSwiftDemo/Sources/Api/StorefrontClient.swift b/platforms/swift/Samples/CheckoutKitSwiftDemo/CheckoutKitSwiftDemo/Sources/Api/StorefrontClient.swift index 8f712e0d8..191da463f 100644 --- a/platforms/swift/Samples/CheckoutKitSwiftDemo/CheckoutKitSwiftDemo/Sources/Api/StorefrontClient.swift +++ b/platforms/swift/Samples/CheckoutKitSwiftDemo/CheckoutKitSwiftDemo/Sources/Api/StorefrontClient.swift @@ -90,8 +90,7 @@ class StorefrontInputFactory { address: Storefront.CartAddressInput( deliveryAddress: .some(deliveryAddress) ), - selected: .some(true), - oneTimeUse: .some(true) + selected: .some(true) ) ]) ) diff --git a/platforms/swift/Samples/CheckoutKitSwiftDemo/CheckoutKitSwiftDemoTests/Api/StorefrontInputFactoryTests.swift b/platforms/swift/Samples/CheckoutKitSwiftDemo/CheckoutKitSwiftDemoTests/Api/StorefrontInputFactoryTests.swift new file mode 100644 index 000000000..c8906d1f3 --- /dev/null +++ b/platforms/swift/Samples/CheckoutKitSwiftDemo/CheckoutKitSwiftDemoTests/Api/StorefrontInputFactoryTests.swift @@ -0,0 +1,27 @@ +@testable import CheckoutKitSwiftDemo +import XCTest + +@MainActor +final class StorefrontInputFactoryTests: XCTestCase { + func testHardcodedCartUsesASelectedReusableDeliveryAddress() { + let originalBuyerIdentityMode = appConfiguration.buyerIdentityMode + defer { appConfiguration.buyerIdentityMode = originalBuyerIdentityMode } + appConfiguration.buyerIdentityMode = .hardcoded + + let input = StorefrontInputFactory.shared.createCartInput() + + guard case let .some(delivery) = input.delivery, + case let .some(addresses) = delivery.addresses, + let address = addresses.first + else { + return XCTFail("Expected a delivery address") + } + XCTAssertEqual(addresses.count, 1) + guard case .some(true) = address.selected else { + return XCTFail("Expected the delivery address to be selected") + } + guard case .none = address.oneTimeUse else { + return XCTFail("Expected one-time use to be omitted") + } + } +}