diff --git a/e2e/README.md b/e2e/README.md index 87e8eb5f..e304979d 100644 --- a/e2e/README.md +++ b/e2e/README.md @@ -51,7 +51,7 @@ terminal. | Platform | From | Command | | ------------------ | ------------------------------- | ------------------ | | React Native, iOS | `platforms/react-native/` | `pnpm e2e:ios` | -| Swift, iOS | TBD | TBD | +| Swift, iOS | `platforms/swift/` | `./Scripts/e2e_maestro_ios` | | Android (native) | TBD | TBD | | RN, Android | `platforms/react-native/` | `pnpm e2e:android` | diff --git a/e2e/swift/checkout-completion.yaml b/e2e/swift/checkout-completion.yaml new file mode 100644 index 00000000..d5975837 --- /dev/null +++ b/e2e/swift/checkout-completion.yaml @@ -0,0 +1,190 @@ +appId: com.shopify.example.MobileBuyIntegration +name: Checkout completes +tags: + - swift + - ios + - checkout + +# Override these for store-specific product and shipping-address data. +env: + PRODUCT_INDEX: ${PRODUCT_INDEX || "1"} + COUNTRY: ${COUNTRY || "United States"} + ADDRESS_LINE1: ${ADDRESS_LINE1 || "350 5th Ave"} + CITY: ${CITY || "New York"} + POSTAL_CODE: ${POSTAL_CODE || "10118"} + POSTAL_FIELD: ${POSTAL_FIELD || "ZIP code"} +--- +# Timeout tiers: +# 10000 - in-app interactions (taps, animations) +# 30000 - checkout step transitions (network) +# 60000 - cold starts, first checkout paint, final confirmation + +# Product and cart +- launchApp: + clearState: true + arguments: + AppleLocale: en_US + AppleLanguages: "(en)" +- extendedWaitUntil: + visible: + id: product-0-grid-item + timeout: 60000 +- scrollUntilVisible: + element: + id: product-${PRODUCT_INDEX}-grid-item + direction: DOWN + timeout: 10000 + centerElement: true +- tapOn: + id: product-${PRODUCT_INDEX}-grid-item +- extendedWaitUntil: + visible: + id: add-to-cart-button + timeout: 10000 +- tapOn: + id: add-to-cart-button + enabled: true +- extendedWaitUntil: + visible: "^Added$" + timeout: 30000 +- tapOn: + id: product-sheet-close-button +- waitForAnimationToEnd: + timeout: 10000 +- tapOn: + id: cart-tab +- extendedWaitUntil: + visible: + id: checkout-button + timeout: 30000 +- tapOn: + id: checkout-button + enabled: true + +# Contact +- extendedWaitUntil: + visible: + text: "^Email( or mobile phone number)?$" + timeout: 60000 +- tapOn: + text: "^Email( or mobile phone number)?$" + index: -1 +- inputText: "maestro.e2e@shopify.com" +- tapOn: "selected" +- tapOn: + text: "^First name( \\(optional\\))?$" + index: -1 +- inputText: "Maestro" +- tapOn: "selected" +- tapOn: + text: "^Last name$" + index: -1 +- inputText: "Shopify" +- tapOn: "selected" + +# Shipping address +- scrollUntilVisible: + element: + text: "Country/Region" + direction: DOWN + timeout: 10000 +- tapOn: + text: "Country/Region" + index: 1 +- waitForAnimationToEnd: + timeout: 3000 +- scrollUntilVisible: + element: + text: "^${COUNTRY}$" + direction: UP + timeout: 10000 + visibilityPercentage: 50 + centerElement: true + optional: true +- runFlow: + when: + notVisible: "^${COUNTRY}$" + commands: + - scrollUntilVisible: + element: + text: "^${COUNTRY}$" + direction: DOWN + timeout: 30000 + visibilityPercentage: 50 + centerElement: true +- tapOn: + text: "^${COUNTRY}$" +- waitForAnimationToEnd: + timeout: 3000 + +- scrollUntilVisible: + element: + text: "Address" + direction: DOWN + timeout: 10000 +- tapOn: + text: "Address" + index: -1 +- eraseText: 80 +- scrollUntilVisible: + element: + text: "^${POSTAL_FIELD}$" + direction: DOWN + timeout: 10000 + centerElement: true +- inputText: "${ADDRESS_LINE1} ${CITY} ${POSTAL_CODE}" +- extendedWaitUntil: + visible: ".*${ADDRESS_LINE1}, ${CITY}.*${POSTAL_CODE}.*${COUNTRY}.*" + timeout: 30000 +- waitForAnimationToEnd: + timeout: 2000 +- tapOn: + text: ".*${ADDRESS_LINE1}, ${CITY}.*${POSTAL_CODE}.*${COUNTRY}.*" + index: 0 + retryTapIfNoChange: true +- waitForAnimationToEnd: + timeout: 5000 +- extendedWaitUntil: + visible: "^${POSTAL_CODE}$" + timeout: 15000 +- tapOn: "selected" +- waitForAnimationToEnd: + timeout: 5000 + +# Payment +- scrollUntilVisible: + element: + text: "^Field container for: Card number$" + direction: DOWN + timeout: 30000 + centerElement: true +- tapOn: + text: "^Field container for: Card number$" +- inputText: "4242424242424242" +- tapOn: "selected" +- tapOn: "Expiration date (MM / YY)" +- inputText: "1230" +- tapOn: "selected" +- tapOn: "Field container for: Security code" +- inputText: "123" +- tapOn: "selected" +- scrollUntilVisible: + element: + text: "^Field container for: Name on card$" + direction: DOWN + timeout: 30000 + centerElement: true +- scrollUntilVisible: + element: + text: "^Pay now$" + direction: DOWN + timeout: 30000 +- extendedWaitUntil: + visible: "^Pay now$" + timeout: 30000 +- tapOn: + text: "^Pay now$" + enabled: true +- extendedWaitUntil: + visible: ".*(Thank you|[Oo]rder (is )?confirmed).*" + timeout: 60000 diff --git a/platforms/swift/Samples/MobileBuyIntegration/MobileBuyIntegration.xcodeproj/project.pbxproj b/platforms/swift/Samples/MobileBuyIntegration/MobileBuyIntegration.xcodeproj/project.pbxproj index 2a81f732..7f779e48 100644 --- a/platforms/swift/Samples/MobileBuyIntegration/MobileBuyIntegration.xcodeproj/project.pbxproj +++ b/platforms/swift/Samples/MobileBuyIntegration/MobileBuyIntegration.xcodeproj/project.pbxproj @@ -159,7 +159,7 @@ mainGroup = 4EBBA75E2A5F0CE200193E19; packageReferences = ( CB00000012345678 /* XCRemoteSwiftPackageReference "apollo-ios" */, - CB2370002FB21BF100F0D914 /* XCLocalSwiftPackageReference "../../../../../checkout-kit" */, + CB2370002FB21BF100F0D914 /* XCLocalSwiftPackageReference "../../../.." */, ); productRefGroup = 4EBBA7682A5F0CE200193E19 /* Products */; projectDirPath = ""; @@ -443,9 +443,9 @@ /* End XCConfigurationList section */ /* Begin XCLocalSwiftPackageReference section */ - CB2370002FB21BF100F0D914 /* XCLocalSwiftPackageReference "../../../../../checkout-kit" */ = { + CB2370002FB21BF100F0D914 /* XCLocalSwiftPackageReference "../../../.." */ = { isa = XCLocalSwiftPackageReference; - relativePath = "../../../../../checkout-kit"; + relativePath = "../../../.."; }; /* End XCLocalSwiftPackageReference section */ @@ -473,17 +473,17 @@ }; CB001E302F3CDA0300286F69 /* ShopifyCheckoutProtocol */ = { isa = XCSwiftPackageProductDependency; - package = CB2370002FB21BF100F0D914 /* XCLocalSwiftPackageReference "../../../../../checkout-kit" */; + package = CB2370002FB21BF100F0D914 /* XCLocalSwiftPackageReference "../../../.." */; productName = ShopifyCheckoutProtocol; }; CB1B10B42E4CDDB0001713F8 /* ShopifyCheckoutKit */ = { isa = XCSwiftPackageProductDependency; - package = CB2370002FB21BF100F0D914 /* XCLocalSwiftPackageReference "../../../../../checkout-kit" */; + package = CB2370002FB21BF100F0D914 /* XCLocalSwiftPackageReference "../../../.." */; productName = ShopifyCheckoutKit; }; CBED2D4E2F3F5D1B00EC866A /* ShopifyAcceleratedCheckouts */ = { isa = XCSwiftPackageProductDependency; - package = CB2370002FB21BF100F0D914 /* XCLocalSwiftPackageReference "../../../../../checkout-kit" */; + package = CB2370002FB21BF100F0D914 /* XCLocalSwiftPackageReference "../../../.." */; productName = ShopifyAcceleratedCheckouts; }; /* End XCSwiftPackageProductDependency section */ diff --git a/platforms/swift/Samples/MobileBuyIntegration/MobileBuyIntegration/Sources/App/SceneDelegate.swift b/platforms/swift/Samples/MobileBuyIntegration/MobileBuyIntegration/Sources/App/SceneDelegate.swift index 832315d6..bc06d5d2 100644 --- a/platforms/swift/Samples/MobileBuyIntegration/MobileBuyIntegration/Sources/App/SceneDelegate.swift +++ b/platforms/swift/Samples/MobileBuyIntegration/MobileBuyIntegration/Sources/App/SceneDelegate.swift @@ -108,6 +108,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { // Catalog grid view productGridController.tabBarItem.image = UIImage(systemName: "square.grid.2x2") productGridController.tabBarItem.title = "Catalog" + productGridController.tabBarItem.accessibilityIdentifier = "catalog-tab" productGridController.navigationItem.titleView = logoImageView catalogCartButton = createCartButtonWithBadge() productGridController.navigationItem.rightBarButtonItem = UIBarButtonItem(customView: catalogCartButton!) @@ -115,6 +116,7 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { // Product Gallery productGalleryController.tabBarItem.image = UIImage(systemName: "appwindow.swipe.rectangle") productGalleryController.tabBarItem.title = "Products" + productGalleryController.tabBarItem.accessibilityIdentifier = "products-tab" productGalleryController.navigationItem.titleView = logoImageView galleryCartButton = createCartButtonWithBadge() productGalleryController.navigationItem.rightBarButtonItem = UIBarButtonItem(customView: galleryCartButton!) @@ -122,16 +124,19 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate { // Cart (UI Kit) swiftUICartController.tabBarItem.image = UIImage(systemName: "cart") swiftUICartController.tabBarItem.title = "Cart" + swiftUICartController.tabBarItem.accessibilityIdentifier = "cart-tab" swiftUICartController.navigationItem.title = "Cart (SwiftUI)" // Account accountController.tabBarItem.image = UIImage(systemName: "person.circle") accountController.tabBarItem.title = "Log in" + accountController.tabBarItem.accessibilityIdentifier = "account-tab" subscribeToAuthStateChanges() // Settings settingsController.tabBarItem.image = UIImage(systemName: "gearshape.2") settingsController.tabBarItem.title = "Settings" + settingsController.tabBarItem.accessibilityIdentifier = "settings-tab" } private func subscribeToAuthStateChanges() { diff --git a/platforms/swift/Samples/MobileBuyIntegration/MobileBuyIntegration/Sources/Scenes/Cart/CartView.swift b/platforms/swift/Samples/MobileBuyIntegration/MobileBuyIntegration/Sources/Scenes/Cart/CartView.swift index c332155f..c8ee752f 100644 --- a/platforms/swift/Samples/MobileBuyIntegration/MobileBuyIntegration/Sources/Scenes/Cart/CartView.swift +++ b/platforms/swift/Samples/MobileBuyIntegration/MobileBuyIntegration/Sources/Scenes/Cart/CartView.swift @@ -103,7 +103,7 @@ struct CartView: View { ) .disabled(isBusy) .foregroundColor(.white) - .accessibilityIdentifier("checkoutSheetButton") + .accessibilityIdentifier("checkout-button") } .padding(.horizontal, 20) .padding(.bottom, 20) diff --git a/platforms/swift/Samples/MobileBuyIntegration/MobileBuyIntegration/Sources/Scenes/ProductGridView.swift b/platforms/swift/Samples/MobileBuyIntegration/MobileBuyIntegration/Sources/Scenes/ProductGridView.swift index 32c9d3e8..4029d55d 100644 --- a/platforms/swift/Samples/MobileBuyIntegration/MobileBuyIntegration/Sources/Scenes/ProductGridView.swift +++ b/platforms/swift/Samples/MobileBuyIntegration/MobileBuyIntegration/Sources/Scenes/ProductGridView.swift @@ -38,8 +38,9 @@ struct ProductGridView: View { ScrollView { LazyVGrid(columns: columns, spacing: 2) { if let products = productCache.collection, !products.isEmpty { - ForEach(products, id: \.id) { product in + ForEach(Array(products.enumerated()), id: \.element.id) { index, product in ProductGridItem(product: product) + .accessibilityIdentifier("product-\(index)-grid-item") .onTapGesture { selectProductAndShowSheet(for: product) } @@ -88,6 +89,7 @@ struct ProductSheetView: View { .padding() .foregroundStyle(.white) } + .accessibilityIdentifier("product-sheet-close-button") .padding([.top, .trailing], 16) } .edgesIgnoringSafeArea(.top) diff --git a/platforms/swift/Samples/MobileBuyIntegration/MobileBuyIntegration/Sources/Scenes/ProductView.swift b/platforms/swift/Samples/MobileBuyIntegration/MobileBuyIntegration/Sources/Scenes/ProductView.swift index 5ba649a2..e9a16246 100644 --- a/platforms/swift/Samples/MobileBuyIntegration/MobileBuyIntegration/Sources/Scenes/ProductView.swift +++ b/platforms/swift/Samples/MobileBuyIntegration/MobileBuyIntegration/Sources/Scenes/ProductView.swift @@ -134,6 +134,7 @@ struct ProductView: View { .foregroundStyle(.white) .cornerRadius(DesignSystem.cornerRadius) .disabled(!variant.availableForSale || loading) + .accessibilityIdentifier("add-to-cart-button") if variant.availableForSale { AcceleratedCheckoutButtons(variantID: variant.id, quantity: 1) diff --git a/platforms/swift/Scripts/e2e_maestro_ios b/platforms/swift/Scripts/e2e_maestro_ios new file mode 100755 index 00000000..f640e8c5 --- /dev/null +++ b/platforms/swift/Scripts/e2e_maestro_ios @@ -0,0 +1,5 @@ +#!/usr/bin/env bash + +set -euo pipefail + +maestro --platform ios test --config ../../e2e/config.yaml ../../e2e/swift