Skip to content

Commit e5847b5

Browse files
committed
Add React Native iOS checkout e2e flow
1 parent b39846a commit e5847b5

5 files changed

Lines changed: 249 additions & 3 deletions

File tree

e2e/README.md

Lines changed: 73 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,74 @@
1-
# Checkout Kit — End-to-end tests
1+
# Checkout Kit, end-to-end tests
22

3-
Placeholder. End-to-end tests covering Swift, Android, and (later) React Native will be folded into this directory in a follow-up.
3+
Cross-platform e2e flows driven by [Maestro](https://maestro.mobile.dev).
4+
5+
## Layout
6+
7+
Tests are grouped by the sample app they exercise. Each sample app lives under
8+
[`platforms/<name>/`](../platforms/) and has a matching folder here.
9+
10+
```
11+
e2e/
12+
├── config.yaml Shared Maestro config (all platforms)
13+
├── swift/ Targets the Swift sample (iOS only)
14+
├── android/ Targets the Android sample (Android only)
15+
└── react-native/ Targets the RN sample (cross-platform)
16+
├── ios/
17+
└── android/
18+
```
19+
20+
The Swift sample is iOS-only and the Android sample is Android-only by
21+
construction, so they don't need an inner platform split. The React Native
22+
sample ships to both platforms; its flows are split because some assertions
23+
are platform-specific (iOS accessibility-label patterns vs Android resource
24+
strings).
25+
26+
Folders are created when their first flow lands. Don't pre-create empty
27+
directories.
28+
29+
## Sample-app appIds
30+
31+
Use these in the `appId:` header of every flow. Don't invent new bundle ids.
32+
33+
| Folder | appId |
34+
| ------------------------- | ------------------------------------------------ |
35+
| `swift/` | `com.shopify.example.MobileBuyIntegration` |
36+
| `android/` | `com.shopify.checkout_kit_mobile_buy_integration_sample` |
37+
| `react-native/ios/` | `com.shopify.example.CheckoutKitReactNative` |
38+
| `react-native/android/` | `com.shopify.example.CheckoutKitReactNative` |
39+
40+
## Running
41+
42+
Each platform's runner script lives next to its sample app. Build and launch
43+
the sample on a simulator/emulator first, then run the script in a second
44+
terminal.
45+
46+
| Platform | From | Command |
47+
| ------------------ | ------------------------------- | ------------------ |
48+
| React Native, iOS | `platforms/react-native/` | `pnpm e2e:ios` |
49+
| Swift, iOS | TBD | TBD |
50+
| Android (native) | TBD | TBD |
51+
| RN, Android | TBD | TBD |
52+
53+
Maestro itself is a system CLI, not an npm dependency. Install once with:
54+
55+
```
56+
curl -fsSL "https://get.maestro.mobile.dev" | bash
57+
```
58+
59+
## Adding a flow
60+
61+
1. Drop a new `<name>.yaml` under the right folder.
62+
2. Set `appId:` from the table above.
63+
3. Keep timeouts in the existing tiers (in-app interactions ~10s, network
64+
transitions ~30s, cold starts and checkout first-paint ~60s).
65+
4. If the flow needs an npm script wrapper, add an `e2e:<platform>` script to
66+
the matching `package.json` next to existing scripts. The script should
67+
point at the folder, not an individual file, so the whole folder runs.
68+
69+
## Required sample-app accessibility
70+
71+
Maestro flows rely on testIDs / accessibility labels in the sample apps. When
72+
adding a flow, prefer querying by `id:` (stable, controlled by us) over
73+
`text:` (fragile, depends on storefront copy). If a tappable element doesn't
74+
have an id, add one to the sample first, in a separate commit.

e2e/config.yaml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
ios:
2+
snapshotKeyHonorModalViews: true
Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
appId: com.shopify.example.CheckoutKitReactNative
2+
name: Checkout reaches Pay now
3+
tags:
4+
- ios
5+
- checkout
6+
7+
# Override these for store-specific product and shipping-address data.
8+
env:
9+
PRODUCT_INDEX: ${PRODUCT_INDEX || "1"}
10+
COUNTRY: ${COUNTRY || "United States"}
11+
ADDRESS_LINE1: ${ADDRESS_LINE1 || "350 5th Ave"}
12+
CITY: ${CITY || "New York"}
13+
POSTAL_CODE: ${POSTAL_CODE || "10118"}
14+
POSTAL_FIELD: ${POSTAL_FIELD || "ZIP code"}
15+
---
16+
# Timeout tiers:
17+
# 10000 - in-app interactions (taps, animations)
18+
# 30000 - checkout step transitions (network)
19+
# 60000 - cold starts, first checkout paint
20+
21+
# Product and cart
22+
- launchApp:
23+
clearState: true
24+
arguments:
25+
AppleLocale: en_US
26+
AppleLanguages: "(en)"
27+
- extendedWaitUntil:
28+
visible:
29+
id: product-0-add-to-cart-button
30+
timeout: 60000
31+
- scrollUntilVisible:
32+
element:
33+
id: product-${PRODUCT_INDEX}-add-to-cart-button
34+
direction: DOWN
35+
timeout: 10000
36+
centerElement: true
37+
- tapOn:
38+
id: product-${PRODUCT_INDEX}-add-to-cart-button
39+
enabled: true
40+
- waitForAnimationToEnd:
41+
timeout: 10000
42+
- tapOn:
43+
id: cart-tab
44+
- extendedWaitUntil:
45+
visible:
46+
id: checkout-button
47+
timeout: 30000
48+
- tapOn:
49+
id: checkout-button
50+
enabled: true
51+
52+
# Contact
53+
- extendedWaitUntil:
54+
visible:
55+
text: "^Email( or mobile phone number)?$"
56+
timeout: 60000
57+
- tapOn:
58+
text: "^Email( or mobile phone number)?$"
59+
- inputText: "maestro.e2e@shopify.com"
60+
- tapOn: "selected"
61+
- tapOn:
62+
text: "^First name( \\(optional\\))?$"
63+
- inputText: "Maestro"
64+
- tapOn: "selected"
65+
- tapOn:
66+
text: "^Last name$"
67+
- inputText: "Shopify"
68+
- tapOn: "selected"
69+
70+
# Shipping address
71+
- scrollUntilVisible:
72+
element:
73+
text: "Country/Region"
74+
direction: DOWN
75+
timeout: 10000
76+
- tapOn:
77+
text: "Country/Region"
78+
index: 1
79+
- waitForAnimationToEnd:
80+
timeout: 3000
81+
- scrollUntilVisible:
82+
element:
83+
text: "^${COUNTRY}$"
84+
direction: UP
85+
timeout: 10000
86+
visibilityPercentage: 50
87+
centerElement: true
88+
optional: true
89+
- runFlow:
90+
when:
91+
notVisible: "^${COUNTRY}$"
92+
commands:
93+
- scrollUntilVisible:
94+
element:
95+
text: "^${COUNTRY}$"
96+
direction: DOWN
97+
timeout: 30000
98+
visibilityPercentage: 50
99+
centerElement: true
100+
- tapOn:
101+
text: "^${COUNTRY}$"
102+
- waitForAnimationToEnd:
103+
timeout: 3000
104+
105+
- scrollUntilVisible:
106+
element:
107+
text: "Address"
108+
direction: DOWN
109+
timeout: 10000
110+
- tapOn:
111+
text: "Address"
112+
index: -1
113+
- eraseText: 80
114+
- scrollUntilVisible:
115+
element:
116+
text: "^${POSTAL_FIELD}$"
117+
direction: DOWN
118+
timeout: 10000
119+
centerElement: true
120+
- inputText: "${ADDRESS_LINE1} ${CITY} ${POSTAL_CODE}"
121+
- extendedWaitUntil:
122+
visible: ".*${ADDRESS_LINE1}, ${CITY}.*${POSTAL_CODE}.*${COUNTRY}.*"
123+
timeout: 30000
124+
- waitForAnimationToEnd:
125+
timeout: 2000
126+
- tapOn:
127+
text: ".*${ADDRESS_LINE1}, ${CITY}.*${POSTAL_CODE}.*${COUNTRY}.*"
128+
index: 0
129+
retryTapIfNoChange: true
130+
- waitForAnimationToEnd:
131+
timeout: 5000
132+
- extendedWaitUntil:
133+
visible: "^${POSTAL_CODE}$"
134+
timeout: 15000
135+
- tapOn: "selected"
136+
- waitForAnimationToEnd:
137+
timeout: 5000
138+
# iOS intentionally stops at Pay now because this store/address path can show
139+
# a checkout-web "Shipping not available" banner after submit.
140+
141+
# Payment
142+
- scrollUntilVisible:
143+
element:
144+
text: "^Field container for: Card number$"
145+
direction: DOWN
146+
timeout: 30000
147+
centerElement: true
148+
- tapOn:
149+
text: "^Field container for: Card number$"
150+
- inputText: "4242424242424242"
151+
- tapOn: "selected"
152+
- tapOn: "Expiration date (MM / YY)"
153+
- inputText: "1230"
154+
- tapOn: "selected"
155+
- tapOn: "Field container for: Security code"
156+
- inputText: "123"
157+
- tapOn: "selected"
158+
- scrollUntilVisible:
159+
element:
160+
text: "^Field container for: Name on card$"
161+
direction: DOWN
162+
timeout: 30000
163+
centerElement: true
164+
- scrollUntilVisible:
165+
element:
166+
text: "^Pay now$"
167+
direction: DOWN
168+
timeout: 30000
169+
- extendedWaitUntil:
170+
visible: "^Pay now$"
171+
timeout: 30000

platforms/react-native/package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@
2424
"snapshot": "./scripts/create_snapshot",
2525
"compare-snapshot": "./scripts/compare_snapshot",
2626
"turbo": "turbo",
27-
"test": "jest"
27+
"test": "jest",
28+
"e2e:ios": "maestro --platform ios test --config ../../e2e/config.yaml ../../e2e/react-native/ios"
2829
},
2930
"devDependencies": {
3031
"@babel/core": "^7.25.2",

platforms/react-native/sample/src/screens/CatalogScreen.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ function Product({
157157
</View>
158158
) : (
159159
<Pressable
160+
testID={`${testID}-add-to-cart-button`}
160161
style={styles.addToCartButton}
161162
onPress={() => variant?.id && onAddToCart(variant.id)}>
162163
<Text style={styles.addToCartButtonText}>Add to cart</Text>

0 commit comments

Comments
 (0)