Skip to content

Commit 2e6436d

Browse files
committed
Add native Android checkout e2e flow
1 parent 01b3db5 commit 2e6436d

8 files changed

Lines changed: 568 additions & 9 deletions

File tree

e2e/README.md

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ terminal.
5252
| ------------------ | ------------------------------- | ------------------ |
5353
| React Native, iOS | `platforms/react-native/` | `pnpm e2e:ios` |
5454
| Swift, iOS | `platforms/swift/` | `./Scripts/e2e_maestro_ios` |
55-
| Android (native) | TBD | TBD |
55+
| Android (native) | `platforms/android/` | `./scripts/e2e_maestro_android` |
5656
| RN, Android | `platforms/react-native/` | `pnpm e2e:android` |
5757

5858
Maestro itself is a system CLI, not an npm dependency. Install once with:
@@ -61,6 +61,31 @@ Maestro itself is a system CLI, not an npm dependency. Install once with:
6161
curl -fsSL "https://get.maestro.mobile.dev" | bash
6262
```
6363

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

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

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
@@ -54,8 +54,11 @@ import androidx.compose.runtime.setValue
5454
import androidx.compose.ui.Modifier
5555
import androidx.compose.ui.layout.ContentScale
5656
import androidx.compose.ui.platform.LocalContext
57+
import androidx.compose.ui.platform.testTag
5758
import androidx.compose.ui.res.painterResource
5859
import androidx.compose.ui.res.stringResource
60+
import androidx.compose.ui.semantics.semantics
61+
import androidx.compose.ui.semantics.testTagsAsResourceId
5962
import androidx.compose.ui.unit.dp
6063
import androidx.navigation.compose.rememberNavController
6164
import com.shopify.checkout_kit_mobile_buy_integration_sample.cart.CartViewModel
@@ -98,7 +101,11 @@ fun CheckoutKitAppRoot(
98101

99102
CheckoutKitSampleTheme(darkTheme = useDarkTheme) {
100103
Surface(
101-
modifier = Modifier.fillMaxSize(),
104+
modifier = Modifier
105+
.fillMaxSize()
106+
.semantics {
107+
testTagsAsResourceId = true
108+
},
102109
) {
103110
val navController = rememberNavController()
104111
var currentScreen by remember { mutableStateOf<Screen>(Screen.Product) }
@@ -139,9 +146,12 @@ fun CheckoutKitAppRoot(
139146
)
140147
},
141148
actions = {
142-
IconButton(onClick = {
143-
navController.navigate(Screen.Cart.route)
144-
}) {
149+
IconButton(
150+
modifier = Modifier.testTag("cart-tab"),
151+
onClick = {
152+
navController.navigate(Screen.Cart.route)
153+
}
154+
) {
145155
BadgedBox(badge = {
146156
if (totalQuantity > 0) {
147157
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
@@ -52,6 +52,7 @@ import androidx.compose.runtime.setValue
5252
import androidx.compose.ui.Alignment
5353
import androidx.compose.ui.Modifier
5454
import androidx.compose.ui.graphics.RectangleShape
55+
import androidx.compose.ui.platform.testTag
5556
import androidx.compose.ui.res.stringResource
5657
import androidx.compose.ui.text.style.TextAlign
5758
import androidx.compose.ui.text.style.TextDecoration
@@ -248,6 +249,7 @@ private fun CheckoutButton(
248249
modifier = modifier
249250
) {
250251
Button(
252+
modifier = Modifier.testTag("checkout-button"),
251253
shape = RectangleShape,
252254
onClick = onClick,
253255
) {

0 commit comments

Comments
 (0)