Skip to content

Commit 02aa352

Browse files
committed
Add native Android checkout e2e flow
1 parent e074c5a commit 02aa352

8 files changed

Lines changed: 566 additions & 13 deletions

File tree

e2e/README.md

Lines changed: 28 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,8 @@ sample ships to both platforms; its flows are split because some assertions
2323
are platform-specific (iOS accessibility-label patterns vs Android resource
2424
strings).
2525

26-
The React Native Android checkout flow submits payment and waits for the order
27-
confirmation screen. The React Native iOS checkout flow currently stops at
28-
`Pay now`; the store/address path can show a checkout-web shipping availability
29-
banner after submit, which is outside the Checkout Kit integration contract.
26+
Checkout flows submit payment, wait for a post-submit checkout result, close
27+
checkout, and assert that the sample app cart is empty.
3028

3129
Folders are created when their first flow lands. Don't pre-create empty
3230
directories.
@@ -52,7 +50,7 @@ terminal.
5250
| ------------------ | ------------------------------- | ------------------ |
5351
| React Native, iOS | `platforms/react-native/` | `pnpm e2e:ios` |
5452
| Swift, iOS | `platforms/swift/` | `./Scripts/e2e_maestro_ios` |
55-
| Android (native) | TBD | TBD |
53+
| Android (native) | `platforms/android/` | `./scripts/e2e_maestro_android` |
5654
| RN, Android | `platforms/react-native/` | `pnpm e2e:android` |
5755

5856
Maestro itself is a system CLI, not an npm dependency. Install once with:
@@ -61,6 +59,31 @@ Maestro itself is a system CLI, not an npm dependency. Install once with:
6159
curl -fsSL "https://get.maestro.mobile.dev" | bash
6260
```
6361

62+
To pin the native Android runner to a specific emulator, set
63+
`MAESTRO_ANDROID_UDID`:
64+
65+
```
66+
MAESTRO_ANDROID_UDID=emulator-5556 ./scripts/e2e_maestro_android
67+
```
68+
69+
If local Android runs fail before `launchApp` with `deviceInfo`,
70+
`io.grpc.StatusRuntimeException: UNAVAILABLE`, or `tcp:7001 closed`, Maestro
71+
failed to start its on-device Android driver. The native Android runner retries
72+
that failure once with a local fallback that installs and starts the driver
73+
manually, then runs Maestro with `--no-reinstall-driver`.
74+
75+
The fallback auto-detects the Android device when exactly one device is
76+
connected. If multiple devices are connected, set `MAESTRO_ANDROID_UDID`.
77+
To force the fallback path manually, run:
78+
79+
```
80+
MAESTRO_ANDROID_UDID=emulator-5556 MAESTRO_ANDROID_MANUAL_DRIVER=1 ./scripts/e2e_maestro_android
81+
```
82+
83+
The fallback auto-detects Homebrew formula installs of Maestro. For other
84+
install layouts, set `MAESTRO_CLIENT_JAR` to the local `maestro-client.jar`.
85+
Set `MAESTRO_ANDROID_AUTO_DRIVER_FALLBACK=0` to disable the automatic retry.
86+
6487
## Adding a flow
6588

6689
1. Drop a new `<name>.yaml` under the right folder.
Lines changed: 283 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,283 @@
1+
appId: com.shopify.checkout_kit_mobile_buy_integration_sample
2+
name: Checkout submits and shows result
3+
tags:
4+
- android
5+
- checkout
6+
7+
env:
8+
PRODUCT_INDEX: "0"
9+
10+
# CI shipping fixture
11+
COUNTRY_LABEL: "United States"
12+
ADDRESS_LINE1: "700 S Flower St"
13+
ADDRESS_LINE1_VISIBLE_PATTERN: ".*700.*Flower St$"
14+
CITY: "Los Angeles"
15+
STATE_FIELD_LABEL: "State"
16+
STATE_LABEL: "California"
17+
POSTAL_CODE: "90017"
18+
POSTAL_FIELD_LABEL: "ZIP code"
19+
20+
# CI payment fixture
21+
CARD_NUMBER: "1"
22+
CARD_EXPIRY_DISPLAY: "12 / 30"
23+
CARD_SECURITY_CODE: "123"
24+
CARDHOLDER_NAME: "Maestro Shopify"
25+
26+
# Accepted terminal checkout states for this smoke test.
27+
POST_SUBMIT_RESULT_PATTERN: ".*(Thank you|Your order|Order confirmed|confirmation|Shipping not available|There was a problem|declined|couldn.t process).*"
28+
---
29+
# Timeout tiers:
30+
# 3000 - animation settles
31+
# 5000 - local in-page interactions and optional probes
32+
# 15000 - sample-app checkout transitions
33+
# 60000 - cold starts, first checkout paint, final submit
34+
35+
# Product and cart
36+
- launchApp:
37+
clearState: true
38+
- extendedWaitUntil:
39+
visible:
40+
id: products-tab
41+
timeout: 60000
42+
- tapOn:
43+
id: products-tab
44+
- extendedWaitUntil:
45+
visible:
46+
id: product-0-grid-item
47+
timeout: 60000
48+
- scrollUntilVisible:
49+
element:
50+
id: product-${PRODUCT_INDEX}-grid-item
51+
direction: DOWN
52+
timeout: 5000
53+
centerElement: true
54+
- tapOn:
55+
id: product-${PRODUCT_INDEX}-grid-item
56+
- scrollUntilVisible:
57+
element:
58+
id: add-to-cart-button
59+
direction: DOWN
60+
timeout: 15000
61+
centerElement: true
62+
- tapOn:
63+
id: add-to-cart-button
64+
enabled: true
65+
- waitForAnimationToEnd:
66+
timeout: 3000
67+
- tapOn:
68+
id: cart-tab
69+
- extendedWaitUntil:
70+
visible:
71+
id: checkout-button
72+
timeout: 15000
73+
- tapOn:
74+
id: checkout-button
75+
enabled: true
76+
77+
# Contact
78+
- extendedWaitUntil:
79+
visible:
80+
text: "^Email( or mobile phone number)?$"
81+
timeout: 60000
82+
- tapOn:
83+
text: "^Email( or mobile phone number)?$"
84+
- inputText: "maestro.e2e@shopify.com"
85+
- extendedWaitUntil:
86+
visible: "^maestro.e2e@shopify.com$"
87+
timeout: 5000
88+
- tapOn:
89+
text: "^First name( \\(optional\\))?$"
90+
- inputText: "Maestro"
91+
- extendedWaitUntil:
92+
visible: "^Maestro$"
93+
timeout: 5000
94+
- tapOn:
95+
text: "^Last name$"
96+
- inputText: "Shopify"
97+
- extendedWaitUntil:
98+
visible: "^Shopify$"
99+
timeout: 5000
100+
101+
# Shipping address
102+
- scrollUntilVisible:
103+
element:
104+
text: "Country/Region"
105+
direction: DOWN
106+
timeout: 5000
107+
- tapOn:
108+
text: "Country/Region"
109+
index: 1
110+
- waitForAnimationToEnd:
111+
timeout: 3000
112+
- scrollUntilVisible:
113+
element:
114+
text: "^${COUNTRY_LABEL}$"
115+
direction: UP
116+
timeout: 5000
117+
visibilityPercentage: 10
118+
optional: true
119+
- scrollUntilVisible:
120+
element:
121+
text: "^${COUNTRY_LABEL}$"
122+
direction: DOWN
123+
timeout: 5000
124+
visibilityPercentage: 10
125+
optional: true
126+
- tapOn:
127+
text: "^${COUNTRY_LABEL}$"
128+
- waitForAnimationToEnd:
129+
timeout: 3000
130+
131+
- scrollUntilVisible:
132+
element:
133+
text: "Address"
134+
direction: DOWN
135+
timeout: 5000
136+
- tapOn:
137+
text: "Address"
138+
index: -1
139+
- eraseText: 80
140+
- inputText: "${ADDRESS_LINE1}"
141+
- runFlow:
142+
when:
143+
visible: "Close suggestions"
144+
commands:
145+
- tapOn: "Close suggestions"
146+
- waitForAnimationToEnd:
147+
timeout: 3000
148+
- extendedWaitUntil:
149+
visible:
150+
id: shipping-address1
151+
text: "${ADDRESS_LINE1_VISIBLE_PATTERN}"
152+
timeout: 5000
153+
- scrollUntilVisible:
154+
element:
155+
text: "^City$"
156+
direction: DOWN
157+
timeout: 5000
158+
centerElement: true
159+
- tapOn:
160+
text: "^City$"
161+
index: -1
162+
- eraseText: 80
163+
- inputText: "${CITY}"
164+
- extendedWaitUntil:
165+
visible: "^${CITY}$"
166+
timeout: 5000
167+
- scrollUntilVisible:
168+
element:
169+
text: "^${STATE_FIELD_LABEL}$"
170+
direction: DOWN
171+
timeout: 5000
172+
centerElement: true
173+
- runFlow:
174+
when:
175+
notVisible: "^${STATE_LABEL}$"
176+
commands:
177+
- tapOn:
178+
text: "^${STATE_FIELD_LABEL}$"
179+
- waitForAnimationToEnd:
180+
timeout: 3000
181+
- scrollUntilVisible:
182+
element:
183+
text: "^${STATE_LABEL}$"
184+
direction: UP
185+
timeout: 5000
186+
visibilityPercentage: 100
187+
optional: true
188+
- scrollUntilVisible:
189+
element:
190+
text: "^${STATE_LABEL}$"
191+
direction: DOWN
192+
timeout: 5000
193+
visibilityPercentage: 100
194+
optional: true
195+
- tapOn:
196+
text: "^${STATE_LABEL}$"
197+
- waitForAnimationToEnd:
198+
timeout: 3000
199+
- extendedWaitUntil:
200+
notVisible: "Select a state"
201+
timeout: 5000
202+
- extendedWaitUntil:
203+
visible: "^${STATE_LABEL}$"
204+
timeout: 5000
205+
- scrollUntilVisible:
206+
element:
207+
text: "^${POSTAL_FIELD_LABEL}$"
208+
direction: DOWN
209+
timeout: 5000
210+
centerElement: true
211+
- tapOn:
212+
text: "^${POSTAL_FIELD_LABEL}$"
213+
index: -1
214+
- eraseText: 80
215+
- inputText: "${POSTAL_CODE}"
216+
- extendedWaitUntil:
217+
visible: "^${POSTAL_CODE}$"
218+
timeout: 5000
219+
- waitForAnimationToEnd:
220+
timeout: 3000
221+
222+
# Payment
223+
- scrollUntilVisible:
224+
element:
225+
id: number
226+
direction: DOWN
227+
timeout: 5000
228+
centerElement: true
229+
- tapOn:
230+
text: "Card number"
231+
index: -1
232+
- inputText: "${CARD_NUMBER}"
233+
- tapOn:
234+
id: expiry
235+
- inputText: "1"
236+
- waitForAnimationToEnd:
237+
timeout: 3000
238+
- inputText: "2"
239+
- waitForAnimationToEnd:
240+
timeout: 3000
241+
- inputText: "3"
242+
- waitForAnimationToEnd:
243+
timeout: 3000
244+
- inputText: "0"
245+
- extendedWaitUntil:
246+
visible: "^${CARD_EXPIRY_DISPLAY}$"
247+
timeout: 5000
248+
- tapOn:
249+
text: "Security code"
250+
index: -1
251+
- inputText: "${CARD_SECURITY_CODE}"
252+
- extendedWaitUntil:
253+
visible: "^${CARD_SECURITY_CODE}$"
254+
timeout: 5000
255+
- scrollUntilVisible:
256+
element:
257+
id: name
258+
direction: DOWN
259+
timeout: 5000
260+
centerElement: true
261+
- extendedWaitUntil:
262+
visible: "^${CARDHOLDER_NAME}$"
263+
timeout: 5000
264+
- scrollUntilVisible:
265+
element:
266+
text: "^Pay now$"
267+
direction: DOWN
268+
timeout: 5000
269+
centerElement: true
270+
- tapOn: "Pay now"
271+
- extendedWaitUntil:
272+
visible: "${POST_SUBMIT_RESULT_PATTERN}"
273+
timeout: 60000
274+
- tapOn: "Close Checkout"
275+
- extendedWaitUntil:
276+
visible:
277+
id: products-tab
278+
timeout: 15000
279+
- tapOn:
280+
id: cart-tab
281+
- extendedWaitUntil:
282+
visible: "^Your cart is empty$"
283+
timeout: 15000

platforms/android/samples/MobileBuyIntegration/app/src/main/java/com/shopify/checkout_kit_mobile_buy_integration_sample/CheckoutKitApp.kt

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,8 +32,11 @@ import androidx.compose.runtime.setValue
3232
import androidx.compose.ui.Modifier
3333
import androidx.compose.ui.layout.ContentScale
3434
import androidx.compose.ui.platform.LocalContext
35+
import androidx.compose.ui.platform.testTag
3536
import androidx.compose.ui.res.painterResource
3637
import androidx.compose.ui.res.stringResource
38+
import androidx.compose.ui.semantics.semantics
39+
import androidx.compose.ui.semantics.testTagsAsResourceId
3740
import androidx.compose.ui.unit.dp
3841
import androidx.navigation.compose.rememberNavController
3942
import com.shopify.checkout_kit_mobile_buy_integration_sample.cart.CartViewModel
@@ -76,7 +79,11 @@ fun CheckoutKitAppRoot(
7679

7780
CheckoutKitSampleTheme(darkTheme = useDarkTheme) {
7881
Surface(
79-
modifier = Modifier.fillMaxSize(),
82+
modifier = Modifier
83+
.fillMaxSize()
84+
.semantics {
85+
testTagsAsResourceId = true
86+
},
8087
) {
8188
val navController = rememberNavController()
8289
var currentScreen by remember { mutableStateOf<Screen>(Screen.Product) }
@@ -117,9 +124,12 @@ fun CheckoutKitAppRoot(
117124
)
118125
},
119126
actions = {
120-
IconButton(onClick = {
121-
navController.navigate(Screen.Cart.route)
122-
}) {
127+
IconButton(
128+
modifier = Modifier.testTag("cart-tab"),
129+
onClick = {
130+
navController.navigate(Screen.Cart.route)
131+
}
132+
) {
123133
BadgedBox(badge = {
124134
if (totalQuantity > 0) {
125135
Badge(

platforms/android/samples/MobileBuyIntegration/app/src/main/java/com/shopify/checkout_kit_mobile_buy_integration_sample/cart/CartView.kt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import androidx.compose.runtime.setValue
3030
import androidx.compose.ui.Alignment
3131
import androidx.compose.ui.Modifier
3232
import androidx.compose.ui.graphics.RectangleShape
33+
import androidx.compose.ui.platform.testTag
3334
import androidx.compose.ui.res.stringResource
3435
import androidx.compose.ui.text.style.TextAlign
3536
import androidx.compose.ui.text.style.TextDecoration
@@ -226,6 +227,7 @@ private fun CheckoutButton(
226227
modifier = modifier
227228
) {
228229
Button(
230+
modifier = Modifier.testTag("checkout-button"),
229231
shape = RectangleShape,
230232
onClick = onClick,
231233
) {

0 commit comments

Comments
 (0)