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
187 changes: 187 additions & 0 deletions e2e/android/checkout-completion.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
appId: com.shopify.checkout_kit_mobile_buy_integration_sample
name: Checkout completes
tags:
- android
- checkout

# Override these for store-specific product and shipping-address data.
env:
PRODUCT_INDEX: ${PRODUCT_INDEX || "1"}
COUNTRY: ${COUNTRY || "United States"}
ADDRESS_LINE1: ${ADDRESS_LINE1 || "350 5th Ave"}
CITY: ${CITY || "New York"}
POSTAL_CODE: ${POSTAL_CODE || "10118"}
POSTAL_FIELD: ${POSTAL_FIELD || "ZIP code"}
---
# Timeout tiers:
# 10000 - in-app interactions (taps, animations)
# 30000 - checkout step transitions (network)
# 60000 - cold starts, first checkout paint, final confirmation

# 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: 10000
centerElement: true
- tapOn:
id: product-${PRODUCT_INDEX}-grid-item
- scrollUntilVisible:
element:
id: add-to-cart-button
direction: DOWN
timeout: 30000
centerElement: true
- tapOn:
id: add-to-cart-button
enabled: true
- waitForAnimationToEnd:
timeout: 10000
- tapOn:
id: cart-tab
- extendedWaitUntil:
visible:
id: checkout-button
timeout: 30000
- 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"
- hideKeyboard
- tapOn:
text: "^First name( \\(optional\\))?$"
- inputText: "Maestro"
- hideKeyboard
- tapOn:
text: "^Last name$"
- inputText: "Shopify"
- hideKeyboard

# Shipping address
- scrollUntilVisible:
element:
text: "Country/Region"
direction: DOWN
timeout: 10000
- tapOn:
text: "Country/Region"
index: 1
- waitForAnimationToEnd:
timeout: 3000
- scrollUntilVisible:
element:
text: "^${COUNTRY}$"
direction: UP
timeout: 10000
visibilityPercentage: 50
centerElement: true
optional: true
- runFlow:
when:
notVisible: "^${COUNTRY}$"
commands:
- scrollUntilVisible:
element:
text: "^${COUNTRY}$"
direction: DOWN
timeout: 30000
visibilityPercentage: 50
centerElement: true
- tapOn:
text: "^${COUNTRY}$"
- waitForAnimationToEnd:
timeout: 3000

- scrollUntilVisible:
element:
text: "Address"
direction: DOWN
timeout: 10000
- tapOn:
text: "Address"
index: -1
- eraseText: 80
- scrollUntilVisible:
element:
text: "^${POSTAL_FIELD}$"
direction: DOWN
timeout: 10000
centerElement: true
- inputText: "${ADDRESS_LINE1} ${CITY} ${POSTAL_CODE}"
- extendedWaitUntil:
visible: ".*${ADDRESS_LINE1}, ${CITY}.*${POSTAL_CODE}.*${COUNTRY}.*"
timeout: 30000
- waitForAnimationToEnd:
timeout: 2000
- tapOn:
text: ".*${ADDRESS_LINE1}, ${CITY}.*${POSTAL_CODE}.*${COUNTRY}.*"
index: 0
retryTapIfNoChange: true
- waitForAnimationToEnd:
timeout: 5000
- extendedWaitUntil:
visible: "^${POSTAL_CODE}$"
timeout: 15000
- hideKeyboard
- waitForAnimationToEnd:
timeout: 5000

# Payment
- scrollUntilVisible:
element:
text: "^Card number$"
direction: DOWN
timeout: 30000
centerElement: true
- tapOn:
text: "^Card number$"
- inputText: "4242424242424242"
- hideKeyboard
- tapOn: "Expiration date (MM / YY)"
- inputText: "1230"
- hideKeyboard
- tapOn:
text: "^Security code$"
- inputText: "123"
- hideKeyboard
- scrollUntilVisible:
element:
text: "^Name on card$"
direction: DOWN
timeout: 30000
centerElement: true
- scrollUntilVisible:
element:
text: "^Pay now$"
direction: DOWN
timeout: 30000
- extendedWaitUntil:
visible: "^Pay now$"
timeout: 30000
- tapOn:
text: "^Pay now$"
enabled: true
- extendedWaitUntil:
visible: ".*(Thank you|[Oo]rder (is )?confirmed).*"
timeout: 60000
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,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 @@ -98,7 +101,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 @@ -139,9 +146,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 @@ -53,6 +53,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 @@ -253,6 +254,7 @@ private fun CheckoutButton(
modifier = modifier
) {
Button(
modifier = Modifier.testTag("checkout-button"),
shape = RectangleShape,
onClick = onClick,
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.res.vectorResource
import androidx.compose.ui.semantics.contentDescription
Expand Down Expand Up @@ -62,20 +63,23 @@ fun BottomAppBarWithNavigation(
ImageVector.vectorResource(R.drawable.home),
stringResource(id = R.string.navigation_home),
currentScreen,
"home-tab",
)
NavigationItem(
navController,
Screen.Products,
ImageVector.vectorResource(R.drawable.product),
stringResource(id = R.string.navigation_shop),
currentScreen,
"products-tab",
)
NavigationItem(
navController,
Screen.Settings,
ImageVector.vectorResource(R.drawable.profile),
stringResource(id = R.string.navigation_log_in),
currentScreen,
"settings-tab",
)
}
}
Expand All @@ -88,6 +92,7 @@ fun NavigationItem(
icon: ImageVector,
label: String,
currentScreen: Screen,
testTag: String,
) {
val isActiveScreen = currentScreen == screen
val color = if (isActiveScreen) {
Expand All @@ -99,9 +104,11 @@ fun NavigationItem(
Column {
IconButton(
onClick = { navController.navigate(screen.route) },
modifier = Modifier.semantics {
this.contentDescription = "$label icon"
}
modifier = Modifier
.testTag(testTag)
.semantics {
this.contentDescription = "$label icon"
}
) {
Icon(imageVector = icon, contentDescription = label, tint = color)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.testTag
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.dp
Expand Down Expand Up @@ -119,6 +120,7 @@ fun ProductsView(
Product(
product = product,
imageHeight = if (largeScreen) defaultProductImageHeightLg else defaultProductImageHeight,
testTag = "product-${index}-grid-item",
onProductClick = { productId ->
productsViewModel.productClicked(navController, productId)
}
Expand All @@ -135,10 +137,12 @@ fun ProductsView(
fun Product(
product: Product,
imageHeight: Dp,
testTag: String,
onProductClick: (id: ID) -> Unit,
) {
Column(verticalArrangement = Arrangement.spacedBy(10.dp), modifier = Modifier
.wrapContentWidth()
.testTag(testTag)
.clickable {
onProductClick(product.id)
}) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import androidx.compose.runtime.Composable
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.unit.dp
Expand All @@ -50,7 +51,9 @@ fun AddToCartButton(
horizontalAlignment = Alignment.CenterHorizontally,
) {
TextButton(
modifier = Modifier.fillMaxWidth(),
modifier = Modifier
.fillMaxWidth()
.testTag("add-to-cart-button"),
enabled = !loading,
onClick = onClick,
border = BorderStroke(1.dp, MaterialTheme.colorScheme.onBackground),
Expand Down
Loading
Loading