Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 26 additions & 1 deletion e2e/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ terminal.
| ------------------ | ------------------------------- | ------------------ |
| React Native, iOS | `platforms/react-native/` | `pnpm e2e:ios` |
| Swift, iOS | `platforms/swift/` | `./Scripts/e2e_maestro_ios` |
| Android (native) | TBD | TBD |
| Android (native) | `platforms/android/` | `./scripts/e2e_maestro_android` |
| RN, Android | `platforms/react-native/` | `pnpm e2e:android` |

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

To pin the native Android runner to a specific emulator, set
`MAESTRO_ANDROID_UDID`:

```
MAESTRO_ANDROID_UDID=emulator-5556 ./scripts/e2e_maestro_android
```

If local Android runs fail before `launchApp` with `deviceInfo`,
`io.grpc.StatusRuntimeException: UNAVAILABLE`, or `tcp:7001 closed`, Maestro
failed to start its on-device Android driver. The native Android runner retries
that failure once with a local fallback that installs and starts the driver
manually, then runs Maestro with `--no-reinstall-driver`.

The fallback auto-detects the Android device when exactly one device is
connected. If multiple devices are connected, set `MAESTRO_ANDROID_UDID`.
To force the fallback path manually, run:

```
MAESTRO_ANDROID_UDID=emulator-5556 MAESTRO_ANDROID_MANUAL_DRIVER=1 ./scripts/e2e_maestro_android
```

The fallback auto-detects Homebrew formula installs of Maestro. For other
install layouts, set `MAESTRO_CLIENT_JAR` to the local `maestro-client.jar`.
Set `MAESTRO_ANDROID_AUTO_DRIVER_FALLBACK=0` to disable the automatic retry.

## Adding a flow

1. Drop a new `<name>.yaml` under the right folder.
Expand Down
288 changes: 288 additions & 0 deletions e2e/android/checkout-completion.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,288 @@
appId: com.shopify.checkout_kit_mobile_buy_integration_sample
name: Checkout submits and shows result
tags:
- android
- checkout

env:
PRODUCT_INDEX: "1"

# CI shipping fixture
COUNTRY_LABEL: "United States"
ADDRESS_LINE1: "700 S Flower St"
ADDRESS_LINE1_VISIBLE_PATTERN: ".*700.*Flower St$"
CITY: "Los Angeles"
STATE_FIELD_LABEL: "State"
STATE_LABEL: "California"
POSTAL_CODE: "90017"
POSTAL_FIELD_LABEL: "ZIP code"

# CI payment fixture
CARD_NUMBER: "4242424242424242"
CARD_EXPIRY: "1230"
CARD_SECURITY_CODE: "123"

# Accepted terminal checkout states for this smoke test.
POST_SUBMIT_RESULT_PATTERN: ".*(Thank you|Your order|Order confirmed|confirmation|Shipping not available|There was a problem|declined|couldn.t process).*"
---
# Timeout tiers:
# 3000 - animation settles
# 5000 - local in-page interactions and optional probes
# 15000 - sample-app checkout transitions
# 60000 - cold starts, first checkout paint, final submit

# Product and cart
- launchApp:
clearState: true
- extendedWaitUntil:
visible:
id: products-tab
timeout: 60000
- tapOn:
id: products-tab
- extendedWaitUntil:
visible:
id: product-0-grid-item
timeout: 60000
- scrollUntilVisible:
element:
id: product-${PRODUCT_INDEX}-grid-item
direction: DOWN
timeout: 5000
centerElement: true
- tapOn:
id: product-${PRODUCT_INDEX}-grid-item
- scrollUntilVisible:
element:
id: add-to-cart-button
direction: DOWN
timeout: 15000
centerElement: true
- tapOn:
id: add-to-cart-button
enabled: true
- waitForAnimationToEnd:
timeout: 3000
- tapOn:
id: cart-tab
- extendedWaitUntil:
visible:
id: checkout-button
timeout: 15000
- tapOn:
id: checkout-button
enabled: true

# Contact
- extendedWaitUntil:
visible:
text: "^Email( or mobile phone number)?$"
timeout: 60000
- tapOn:
text: "^Email( or mobile phone number)?$"
- inputText: "maestro.e2e@shopify.com"
- extendedWaitUntil:
visible: "^maestro.e2e@shopify.com$"
timeout: 5000
- tapOn:
text: "^First name( \\(optional\\))?$"
- inputText: "Maestro"
- extendedWaitUntil:
visible: "^Maestro$"
timeout: 5000
- tapOn:
text: "^Last name$"
- inputText: "Shopify"
- extendedWaitUntil:
visible: "^Shopify$"
timeout: 5000

# Shipping address
- scrollUntilVisible:
element:
text: "Country/Region"
direction: DOWN
timeout: 5000
- tapOn:
text: "Country/Region"
index: 1
- waitForAnimationToEnd:
timeout: 3000
- scrollUntilVisible:
element:
text: "^${COUNTRY_LABEL}$"
direction: UP
timeout: 5000
visibilityPercentage: 10
optional: true
- scrollUntilVisible:
element:
text: "^${COUNTRY_LABEL}$"
direction: DOWN
timeout: 5000
visibilityPercentage: 10
optional: true
- tapOn:
text: "^${COUNTRY_LABEL}$"
- waitForAnimationToEnd:
timeout: 3000

- scrollUntilVisible:
element:
text: "Address"
direction: DOWN
timeout: 5000
- tapOn:
text: "Address"
index: -1
- eraseText: 80
- inputText: "${ADDRESS_LINE1}"
- runFlow:
when:
visible: "Close suggestions"
commands:
- tapOn: "Close suggestions"
- waitForAnimationToEnd:
timeout: 3000
- extendedWaitUntil:
visible:
id: billing-address1
text: "${ADDRESS_LINE1_VISIBLE_PATTERN}"
timeout: 5000
- scrollUntilVisible:
element:
text: "^City$"
direction: DOWN
timeout: 5000
centerElement: true
- tapOn:
text: "^City$"
index: -1
- eraseText: 80
- inputText: "${CITY}"
- extendedWaitUntil:
visible: "^${CITY}$"
timeout: 5000
- scrollUntilVisible:
element:
id: Select1
direction: DOWN
timeout: 5000
centerElement: true
- runFlow:
when:
notVisible: "^${STATE_LABEL}$"
commands:
- tapOn:
id: Select1
- waitForAnimationToEnd:
timeout: 3000
- scrollUntilVisible:
element:
text: "^${STATE_LABEL}$"
direction: UP
timeout: 5000
visibilityPercentage: 100
optional: true
- scrollUntilVisible:
element:
text: "^${STATE_LABEL}$"
direction: DOWN
timeout: 5000
visibilityPercentage: 100
optional: true
- tapOn:
text: "^${STATE_LABEL}$"
- waitForAnimationToEnd:
timeout: 3000
- extendedWaitUntil:
notVisible: "Select a state"
timeout: 5000
- extendedWaitUntil:
visible: "^${STATE_LABEL}$"
timeout: 5000
- scrollUntilVisible:
element:
text: "^${POSTAL_FIELD_LABEL}$"
direction: DOWN
timeout: 5000
centerElement: true
- tapOn:
text: "^${POSTAL_FIELD_LABEL}$"
index: -1
- eraseText: 80
- inputText: "${POSTAL_CODE}"
- extendedWaitUntil:
visible: "^${POSTAL_CODE}$"
timeout: 5000
- waitForAnimationToEnd:
timeout: 3000

# Payment
- scrollUntilVisible:
element:
text: "^Complete order$"
direction: DOWN
timeout: 5000
centerElement: true
optional: true
- runFlow:
when:
notVisible: "^Complete order$"
commands:
- scrollUntilVisible:
element:
text: "^Card number$"
direction: UP
timeout: 5000
centerElement: true
optional: true
- scrollUntilVisible:
element:
text: "^Card number$"
direction: DOWN
timeout: 5000
centerElement: true
optional: true
- runFlow:
when:
visible: "^Card number$"
commands:
- tapOn:
text: "^Card number$"
- inputText: "${CARD_NUMBER}"
- tapOn: "Expiration date (MM / YY)"
- inputText: "${CARD_EXPIRY}"
- tapOn:
text: "^Security code$"
- inputText: "${CARD_SECURITY_CODE}"
- scrollUntilVisible:
element:
text: "^Name on card$"
direction: DOWN
timeout: 5000
centerElement: true
- scrollUntilVisible:
element:
text: "^Pay now$"
direction: DOWN
timeout: 5000
centerElement: true
optional: true
- runFlow:
when:
visible: "^Pay now$"
commands:
- tapOn:
text: "^Pay now$"
enabled: true
- runFlow:
when:
visible: "^Complete order$"
commands:
- tapOn:
text: "^Complete order$"
enabled: true
- extendedWaitUntil:
visible: "${POST_SUBMIT_RESULT_PATTERN}"
timeout: 60000
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,11 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.layout.ContentScale
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.semantics.testTagsAsResourceId
import androidx.compose.ui.unit.dp
import androidx.navigation.compose.rememberNavController
import com.shopify.checkout_kit_mobile_buy_integration_sample.cart.CartViewModel
Expand Down Expand Up @@ -76,7 +79,11 @@ fun CheckoutKitAppRoot(

CheckoutKitSampleTheme(darkTheme = useDarkTheme) {
Surface(
modifier = Modifier.fillMaxSize(),
modifier = Modifier
.fillMaxSize()
.semantics {
testTagsAsResourceId = true
},
) {
val navController = rememberNavController()
var currentScreen by remember { mutableStateOf<Screen>(Screen.Product) }
Expand Down Expand Up @@ -117,9 +124,12 @@ fun CheckoutKitAppRoot(
)
},
actions = {
IconButton(onClick = {
navController.navigate(Screen.Cart.route)
}) {
IconButton(
modifier = Modifier.testTag("cart-tab"),
onClick = {
navController.navigate(Screen.Cart.route)
}
) {
BadgedBox(badge = {
if (totalQuantity > 0) {
Badge(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.RectangleShape
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.text.style.TextDecoration
Expand Down Expand Up @@ -226,6 +227,7 @@ private fun CheckoutButton(
modifier = modifier
) {
Button(
modifier = Modifier.testTag("checkout-button"),
shape = RectangleShape,
onClick = onClick,
) {
Expand Down
Loading
Loading