Skip to content

Commit a8adf28

Browse files
committed
Add React Native iOS checkout e2e flow
1 parent 811d673 commit a8adf28

5 files changed

Lines changed: 314 additions & 9 deletions

File tree

e2e/README.md

Lines changed: 71 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,75 @@
1-
# Checkout Kit End-to-End Tests
1+
# Checkout Kit, end-to-end tests
22

3-
This directory is reserved for cross-platform end-to-end tests. There is no runnable e2e suite checked in yet.
3+
Cross-platform e2e flows driven by [Maestro](https://maestro.mobile.dev).
44

5-
Planned coverage:
5+
## Layout
66

7-
- Swift checkout presentation and protocol lifecycle.
8-
- Android checkout presentation and protocol lifecycle.
9-
- React Native wrapper behavior.
10-
- Web component open/close and `checkout:*` events.
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.
119

12-
Until this directory contains test code, use the platform test suites and sample apps described in each platform README.
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: animation settles ~3s, local in-page
64+
interactions and optional probes ~5s, sample-app checkout transitions ~15s,
65+
and cold starts, checkout first-paint, and final submit ~60s.
66+
4. If the flow needs an npm script wrapper, add an `e2e:<platform>` script to
67+
the matching `package.json` next to existing scripts. The script should
68+
point at the folder, not an individual file, so the whole folder runs.
69+
70+
## Required sample-app accessibility
71+
72+
Maestro flows rely on testIDs / accessibility labels in the sample apps. When
73+
adding a flow, prefer querying by `id:` (stable, controlled by us) over
74+
`text:` (fragile, depends on storefront copy). If a tappable element doesn't
75+
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: 237 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,237 @@
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+
# 3000 - animation settles
27+
# 5000 - local in-page interactions and optional probes
28+
# 15000 - sample-app checkout transitions
29+
# 60000 - cold starts, first checkout paint, final submit
30+
31+
# Product and cart
32+
- launchApp:
33+
clearState: true
34+
arguments:
35+
AppleLocale: en_US
36+
AppleLanguages: "(en)"
37+
- extendedWaitUntil:
38+
visible:
39+
id: product-0-add-to-cart-button
40+
timeout: 60000
41+
- scrollUntilVisible:
42+
element:
43+
id: product-0-add-to-cart-button
44+
direction: DOWN
45+
timeout: 5000
46+
centerElement: true
47+
- tapOn:
48+
id: product-0-add-to-cart-button
49+
enabled: true
50+
- waitForAnimationToEnd:
51+
timeout: 3000
52+
- runFlow:
53+
when:
54+
visible:
55+
id: header-cart-icon
56+
commands:
57+
- tapOn:
58+
id: header-cart-icon
59+
- runFlow:
60+
when:
61+
notVisible:
62+
id: checkout-button
63+
commands:
64+
- tapOn:
65+
id: cart-tab
66+
- extendedWaitUntil:
67+
visible:
68+
id: checkout-button
69+
timeout: 15000
70+
- tapOn:
71+
id: checkout-button
72+
enabled: true
73+
74+
# Contact
75+
- extendedWaitUntil:
76+
visible:
77+
text: "^Email( or mobile phone number)?$"
78+
timeout: 60000
79+
- tapOn:
80+
text: "^Email( or mobile phone number)?$"
81+
- inputText: "maestro.e2e@shopify.com"
82+
- tapOn: "selected"
83+
- tapOn:
84+
text: "^First name( \\(optional\\))?$"
85+
- inputText: "Maestro"
86+
- tapOn: "selected"
87+
- tapOn:
88+
text: "^Last name$"
89+
- inputText: "Shopify"
90+
- tapOn: "selected"
91+
92+
# Shipping address
93+
- scrollUntilVisible:
94+
element:
95+
text: "Country/Region"
96+
direction: DOWN
97+
timeout: 5000
98+
- tapOn:
99+
text: "Country/Region"
100+
index: 1
101+
- waitForAnimationToEnd:
102+
timeout: 3000
103+
- scrollUntilVisible:
104+
element:
105+
text: "^${COUNTRY_LABEL}$"
106+
direction: UP
107+
timeout: 5000
108+
visibilityPercentage: 10
109+
optional: true
110+
- scrollUntilVisible:
111+
element:
112+
text: "^${COUNTRY_LABEL}$"
113+
direction: DOWN
114+
timeout: 5000
115+
visibilityPercentage: 10
116+
optional: true
117+
- tapOn:
118+
text: "^${COUNTRY_LABEL}$"
119+
- waitForAnimationToEnd:
120+
timeout: 3000
121+
122+
- scrollUntilVisible:
123+
element:
124+
text: "Address"
125+
direction: DOWN
126+
timeout: 5000
127+
- tapOn:
128+
text: "Address"
129+
index: -1
130+
- eraseText: 80
131+
- inputText: "${ADDRESS_LINE1}"
132+
- tapOn: "selected"
133+
- scrollUntilVisible:
134+
element:
135+
text: "^City$"
136+
direction: DOWN
137+
timeout: 5000
138+
centerElement: true
139+
- tapOn:
140+
text: "^City$"
141+
index: -1
142+
- eraseText: 80
143+
- inputText: "${CITY}"
144+
- tapOn: "selected"
145+
- scrollUntilVisible:
146+
element:
147+
text: "^${STATE_FIELD_LABEL}$"
148+
direction: DOWN
149+
timeout: 5000
150+
centerElement: true
151+
- tapOn:
152+
text: "^${STATE_FIELD_LABEL}$"
153+
index: -1
154+
- waitForAnimationToEnd:
155+
timeout: 3000
156+
- scrollUntilVisible:
157+
element:
158+
text: "^${STATE_LABEL}$"
159+
direction: UP
160+
timeout: 5000
161+
visibilityPercentage: 100
162+
optional: true
163+
- scrollUntilVisible:
164+
element:
165+
text: "^${STATE_LABEL}$"
166+
direction: DOWN
167+
timeout: 5000
168+
visibilityPercentage: 100
169+
optional: true
170+
- tapOn:
171+
text: "^${STATE_LABEL}$"
172+
- waitForAnimationToEnd:
173+
timeout: 3000
174+
- extendedWaitUntil:
175+
notVisible: "Select a state"
176+
timeout: 5000
177+
- extendedWaitUntil:
178+
visible: "^${STATE_LABEL}$"
179+
timeout: 5000
180+
- scrollUntilVisible:
181+
element:
182+
text: "^${POSTAL_FIELD_LABEL}$"
183+
direction: DOWN
184+
timeout: 5000
185+
centerElement: true
186+
- tapOn:
187+
text: "^${POSTAL_FIELD_LABEL}$"
188+
index: -1
189+
- eraseText: 80
190+
- inputText: "${POSTAL_CODE}"
191+
- tapOn: "selected"
192+
- extendedWaitUntil:
193+
visible: "^${POSTAL_CODE}$"
194+
timeout: 5000
195+
- waitForAnimationToEnd:
196+
timeout: 3000
197+
198+
# Payment
199+
- scrollUntilVisible:
200+
element:
201+
text: "^Field container for: Card number$"
202+
direction: DOWN
203+
timeout: 5000
204+
centerElement: true
205+
optional: true
206+
- runFlow:
207+
when:
208+
visible: "^Field container for: Card number$"
209+
commands:
210+
- tapOn:
211+
text: "^Field container for: Card number$"
212+
- inputText: "${CARD_NUMBER}"
213+
- tapOn: "selected"
214+
- tapOn: "Expiration date (MM / YY)"
215+
- inputText: "${CARD_EXPIRY}"
216+
- tapOn: "selected"
217+
- tapOn: "Field container for: Security code"
218+
- inputText: "${CARD_SECURITY_CODE}"
219+
- tapOn: "selected"
220+
- scrollUntilVisible:
221+
element:
222+
text: "^Field container for: Name on card$"
223+
direction: DOWN
224+
timeout: 5000
225+
centerElement: true
226+
- scrollUntilVisible:
227+
element:
228+
text: "^(Pay now|Complete order)$"
229+
direction: DOWN
230+
timeout: 5000
231+
centerElement: true
232+
- tapOn:
233+
text: "^(Pay now|Complete order)$"
234+
enabled: true
235+
- extendedWaitUntil:
236+
visible: "${POST_SUBMIT_RESULT_PATTERN}"
237+
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
@@ -134,6 +134,7 @@ function Product({
134134
</View>
135135
) : (
136136
<Pressable
137+
testID={`${testID}-add-to-cart-button`}
137138
style={styles.addToCartButton}
138139
onPress={() => variant?.id && onAddToCart(variant.id)}>
139140
<Text style={styles.addToCartButtonText}>Add to cart</Text>

0 commit comments

Comments
 (0)