Skip to content

Commit ff1f026

Browse files
committed
Add native Android checkout e2e flow
1 parent 88c0b25 commit ff1f026

8 files changed

Lines changed: 572 additions & 9 deletions

File tree

e2e/README.md

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ 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+
Checkout flows submit payment, wait for a post-submit checkout result, close
27+
checkout, and assert that the sample app cart is empty.
28+
2629
Folders are created when their first flow lands. Don't pre-create empty
2730
directories.
2831

@@ -47,7 +50,7 @@ terminal.
4750
| ------------------ | ------------------------------- | ------------------ |
4851
| React Native, iOS | `platforms/react-native/` | `pnpm e2e:ios` |
4952
| Swift, iOS | `platforms/swift/` | `./Scripts/e2e_maestro_ios` |
50-
| Android (native) | TBD | TBD |
53+
| Android (native) | `platforms/android/` | `./scripts/e2e_maestro_android` |
5154
| RN, Android | `platforms/react-native/` | `pnpm e2e:android` |
5255

5356
Maestro itself is a system CLI, not an npm dependency. Install once with:
@@ -56,6 +59,31 @@ Maestro itself is a system CLI, not an npm dependency. Install once with:
5659
curl -fsSL "https://get.maestro.mobile.dev" | bash
5760
```
5861

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+
5987
## Adding a flow
6088

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