Skip to content

Commit b2a7b59

Browse files
committed
Add React Native iOS checkout e2e flow
1 parent 2497ce7 commit b2a7b59

5 files changed

Lines changed: 314 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: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
platform:
2+
ios:
3+
snapshotKeyHonorModalViews: true
Lines changed: 235 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,235 @@
1+
appId: com.shopify.example.CheckoutKitReactNative
2+
name: Checkout submits and shows result
3+
tags:
4+
- ios
5+
- checkout
6+
7+
env:
8+
# CI shipping fixture
9+
COUNTRY_LABEL: "United States"
10+
ADDRESS_LINE1: "700 S Flower St"
11+
CITY: "Los Angeles"
12+
STATE_FIELD_LABEL: "State"
13+
STATE_LABEL: "California"
14+
POSTAL_CODE: "90017"
15+
POSTAL_FIELD_LABEL: "ZIP code"
16+
17+
# CI payment fixture
18+
CARD_NUMBER: "1"
19+
CARD_EXPIRY: "1230"
20+
CARD_SECURITY_CODE: "123"
21+
22+
# Accepted terminal checkout states for this smoke test.
23+
POST_SUBMIT_RESULT_PATTERN: ".*(Thank you|Your order|Order confirmed|confirmation|Shipping not available|There was a problem|declined|couldn.t process).*"
24+
---
25+
# Timeout tiers:
26+
# 10000 - in-app interactions (taps, animations)
27+
# 30000 - checkout step transitions (network)
28+
# 60000 - cold starts, first checkout paint, final submit
29+
30+
# Product and cart
31+
- launchApp:
32+
clearState: true
33+
arguments:
34+
AppleLocale: en_US
35+
AppleLanguages: "(en)"
36+
- extendedWaitUntil:
37+
visible:
38+
id: product-0-add-to-cart-button
39+
timeout: 60000
40+
- scrollUntilVisible:
41+
element:
42+
id: product-0-add-to-cart-button
43+
direction: DOWN
44+
timeout: 10000
45+
centerElement: true
46+
- tapOn:
47+
id: product-0-add-to-cart-button
48+
enabled: true
49+
- waitForAnimationToEnd:
50+
timeout: 10000
51+
- runFlow:
52+
when:
53+
visible:
54+
id: header-cart-icon
55+
commands:
56+
- tapOn:
57+
id: header-cart-icon
58+
- runFlow:
59+
when:
60+
notVisible:
61+
id: checkout-button
62+
commands:
63+
- tapOn:
64+
id: cart-tab
65+
- extendedWaitUntil:
66+
visible:
67+
id: checkout-button
68+
timeout: 30000
69+
- tapOn:
70+
id: checkout-button
71+
enabled: true
72+
73+
# Contact
74+
- extendedWaitUntil:
75+
visible:
76+
text: "^Email( or mobile phone number)?$"
77+
timeout: 60000
78+
- tapOn:
79+
text: "^Email( or mobile phone number)?$"
80+
- inputText: "maestro.e2e@shopify.com"
81+
- tapOn: "selected"
82+
- tapOn:
83+
text: "^First name( \\(optional\\))?$"
84+
- inputText: "Maestro"
85+
- tapOn: "selected"
86+
- tapOn:
87+
text: "^Last name$"
88+
- inputText: "Shopify"
89+
- tapOn: "selected"
90+
91+
# Shipping address
92+
- scrollUntilVisible:
93+
element:
94+
text: "Country/Region"
95+
direction: DOWN
96+
timeout: 10000
97+
- tapOn:
98+
text: "Country/Region"
99+
index: 1
100+
- waitForAnimationToEnd:
101+
timeout: 10000
102+
- scrollUntilVisible:
103+
element:
104+
text: "^${COUNTRY_LABEL}$"
105+
direction: UP
106+
timeout: 10000
107+
visibilityPercentage: 10
108+
optional: true
109+
- scrollUntilVisible:
110+
element:
111+
text: "^${COUNTRY_LABEL}$"
112+
direction: DOWN
113+
timeout: 10000
114+
visibilityPercentage: 10
115+
optional: true
116+
- tapOn:
117+
text: "^${COUNTRY_LABEL}$"
118+
- waitForAnimationToEnd:
119+
timeout: 10000
120+
121+
- scrollUntilVisible:
122+
element:
123+
text: "Address"
124+
direction: DOWN
125+
timeout: 10000
126+
- tapOn:
127+
text: "Address"
128+
index: -1
129+
- eraseText: 80
130+
- inputText: "${ADDRESS_LINE1}"
131+
- tapOn: "selected"
132+
- scrollUntilVisible:
133+
element:
134+
text: "^City$"
135+
direction: DOWN
136+
timeout: 10000
137+
centerElement: true
138+
- tapOn:
139+
text: "^City$"
140+
index: -1
141+
- eraseText: 80
142+
- inputText: "${CITY}"
143+
- tapOn: "selected"
144+
- scrollUntilVisible:
145+
element:
146+
text: "^${STATE_FIELD_LABEL}$"
147+
direction: DOWN
148+
timeout: 10000
149+
centerElement: true
150+
- tapOn:
151+
text: "^${STATE_FIELD_LABEL}$"
152+
index: -1
153+
- waitForAnimationToEnd:
154+
timeout: 10000
155+
- scrollUntilVisible:
156+
element:
157+
text: "^${STATE_LABEL}$"
158+
direction: UP
159+
timeout: 10000
160+
visibilityPercentage: 100
161+
optional: true
162+
- scrollUntilVisible:
163+
element:
164+
text: "^${STATE_LABEL}$"
165+
direction: DOWN
166+
timeout: 10000
167+
visibilityPercentage: 100
168+
optional: true
169+
- tapOn:
170+
text: "^${STATE_LABEL}$"
171+
- waitForAnimationToEnd:
172+
timeout: 10000
173+
- extendedWaitUntil:
174+
notVisible: "Select a state"
175+
timeout: 10000
176+
- extendedWaitUntil:
177+
visible: "^${STATE_LABEL}$"
178+
timeout: 10000
179+
- scrollUntilVisible:
180+
element:
181+
text: "^${POSTAL_FIELD_LABEL}$"
182+
direction: DOWN
183+
timeout: 10000
184+
centerElement: true
185+
- tapOn:
186+
text: "^${POSTAL_FIELD_LABEL}$"
187+
index: -1
188+
- eraseText: 80
189+
- inputText: "${POSTAL_CODE}"
190+
- tapOn: "selected"
191+
- extendedWaitUntil:
192+
visible: "^${POSTAL_CODE}$"
193+
timeout: 10000
194+
- waitForAnimationToEnd:
195+
timeout: 10000
196+
197+
# Payment
198+
- scrollUntilVisible:
199+
element:
200+
text: "^Field container for: Card number$"
201+
direction: DOWN
202+
timeout: 10000
203+
centerElement: true
204+
optional: true
205+
- runFlow:
206+
when:
207+
visible: "^Field container for: Card number$"
208+
commands:
209+
- tapOn:
210+
text: "^Field container for: Card number$"
211+
- inputText: "${CARD_NUMBER}"
212+
- tapOn: "selected"
213+
- tapOn: "Expiration date (MM / YY)"
214+
- inputText: "${CARD_EXPIRY}"
215+
- tapOn: "selected"
216+
- tapOn: "Field container for: Security code"
217+
- inputText: "${CARD_SECURITY_CODE}"
218+
- tapOn: "selected"
219+
- scrollUntilVisible:
220+
element:
221+
text: "^Field container for: Name on card$"
222+
direction: DOWN
223+
timeout: 30000
224+
centerElement: true
225+
- scrollUntilVisible:
226+
element:
227+
text: "^(Pay now|Complete order)$"
228+
direction: DOWN
229+
timeout: 30000
230+
- tapOn:
231+
text: "^(Pay now|Complete order)$"
232+
enabled: true
233+
- extendedWaitUntil:
234+
visible: "${POST_SUBMIT_RESULT_PATTERN}"
235+
timeout: 60000

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)