Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
2e878ae
test: widen two tight async waits in CheckoutWebViewTests
kieran-osgood-shopify Aug 5, 2026
9b3d9a8
Add the E2E control link parser to all four sample apps
kieran-osgood-shopify Jul 30, 2026
94093d5
feat(e2e): add an E2EController to each sample app
kieran-osgood-shopify Jul 30, 2026
ac81c3c
feat(e2e): select tests by Maestro tag instead of scenario flags
kieran-osgood-shopify Jul 30, 2026
bb4aa6a
Replace the E2E suite cross product with Maestro tags in CI
kieran-osgood-shopify Jul 30, 2026
e189505
feat(e2e): add the cart-from-control-link test on all four targets
kieran-osgood-shopify Jul 30, 2026
3751176
feat(e2e): add the present-and-close checkout test on all four targets
kieran-osgood-shopify Jul 30, 2026
a78a1ea
feat(e2e): add the hardcoded buyer identity order test on all four ta…
kieran-osgood-shopify Jul 30, 2026
27326cc
feat(e2e): add the guest checkout order test on all four targets
kieran-osgood-shopify Jul 31, 2026
75c8187
feat(e2e): add the customer account checkout test on all four targets
kieran-osgood-shopify Jul 31, 2026
49ebe0c
fix(e2e): never repeat the account tag across both tag lists
kieran-osgood-shopify Jul 31, 2026
22c7e06
fix(e2e): name the tags and the setup error in the GitHub report
kieran-osgood-shopify Jul 31, 2026
d375528
docs(e2e): record the BrowserStack-only failures
kieran-osgood-shopify Jul 31, 2026
9fc1dca
The buyer address default matches the E2E shop country
kieran-osgood-shopify Aug 3, 2026
d6de95b
fix(e2e): record the measured clearState evidence
kieran-osgood-shopify Aug 3, 2026
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
30 changes: 21 additions & 9 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ STOREFRONT_ACCESS_TOKEN=your-public-storefront-access-token
# Optional Apple Pay merchant identifier used by accelerated checkout flows.
STOREFRONT_MERCHANT_IDENTIFIER=

# Apple team that signs the Swift sample. Leave blank to keep the Shopify team.
# Set your own team id to build the sample on a device.
# Apple team that signs the Swift samples. Leave blank to keep the Shopify team.
# Set your own team id to build the samples on a device.
DEVELOPMENT_TEAM=

# Storefront API version
Expand All @@ -25,15 +25,27 @@ CUSTOMER_ACCOUNT_API_CLIENT_ID=
CUSTOMER_ACCOUNT_API_SHOP_ID=
CUSTOMER_ACCOUNT_API_VERSION=2026-04

# Buyer identity defaults used by sample apps
# Customer account used by the Maestro account journey (optional).
# CI supplies these from Bitrise secrets. Leave both blank to skip the account tests.
E2E_CUSTOMER_ACCOUNT_EMAIL=
E2E_CUSTOMER_ACCOUNT_CODE=

# User agent suffix the samples add to the customer account login web view (optional).
# CI supplies this from Bitrise secrets. Leave it blank for normal sample use.
CUSTOM_USER_AGENT=

# Buyer identity defaults used by sample apps.
# Keep the address in the E2E shop's own country. A foreign address changes the billing
# country, the province list, the postal field label and the currency, so the checkout
# flows then look for fields that the form does not show.
EMAIL=checkout-kit@example.com
ADDRESS_1=650 King Street
ADDRESS_1=700 S Flower St
ADDRESS_2=Shopify HQ
CITY=Toronto
CITY=Los Angeles
COMPANY=Shopify
COUNTRY=CA
COUNTRY=US
FIRST_NAME=Evelyn
LAST_NAME=Hartley
PROVINCE=ON
ZIP=M5V 1M7
PHONE=+14165550100
PROVINCE=CA
ZIP=90017
PHONE=+12135550142
10 changes: 0 additions & 10 deletions .github/workflows/android-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -122,16 +122,6 @@ jobs:
run: ./gradlew assembleDebug
working-directory: platforms/android/samples/CheckoutKitAndroidDemo

- name: Upload sample test reports
if: failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: android-sample-test-reports
path: |
platforms/android/samples/CheckoutKitAndroidDemo/**/build/reports/tests/
platforms/android/samples/CheckoutKitAndroidDemo/**/build/test-results/
if-no-files-found: warn

lint:
runs-on: ubuntu-latest
timeout-minutes: 20
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ jobs:
- 'e2e/lib/**'
- 'e2e/test/**'
- 'e2e/scripts/**'
- 'e2e/tests/**'
- 'e2e/config.yaml'
- 'e2e/config/matrix.yml'
- '.ci/changed-file-filters.yml'
- '.github/workflows/ci.yml'
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/swift-build-and-test-samples.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,6 @@ jobs:
build-and-test-samples:
uses: ./.github/workflows/swift-test-workflow.yml
with:
test-path: ./Scripts/build_samples && ./Scripts/test_samples
test-path: ./Scripts/build_and_test_samples
job-name: Build & Test Sample Apps
setup-storefront-env: true
25 changes: 18 additions & 7 deletions dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,11 @@ commands:
desc: Build the android sample app and install it to the booted emulator
run: platforms/android/samples/CheckoutKitAndroidDemo/gradlew -p platforms/android/samples/CheckoutKitAndroidDemo installDebug

e2e:
desc: Run the Android sample Maestro tests, filtered by tag
syntax: "[--tags TAG[,TAG...]] [--exclude-tags TAG[,TAG...]]"
run: ./platforms/android/scripts/e2e_maestro "$@"

test:
desc: Run all library and demo app tests
run: |
Expand Down Expand Up @@ -367,6 +372,12 @@ commands:
fi
cd Samples
../Scripts/xcode_run build CheckoutKitSwiftDemo

e2e:
desc: Run the Swift sample Maestro tests, filtered by tag
syntax: "[--tags TAG[,TAG...]] [--exclude-tags TAG[,TAG...]]"
run: ./platforms/swift/Scripts/e2e_maestro "$@"

test:
desc: |
`dev swift test` - Run all tests for the ShopifyCheckoutKit-Package and the sample apps.
Expand Down Expand Up @@ -502,19 +513,19 @@ commands:
cd sample/android
USE_LOCAL_SDK=1 ./gradlew :shopify_checkout-kit-react-native:test --refresh-dependencies
e2e:
desc: Run React Native sample Maestro checkout smoke flows
syntax: "{ios|android} [--guest] [--hardcoded-buyer-identity]"
desc: Run React Native sample Maestro tests, filtered by tag
syntax: "{ios|android} [--tags TAG[,TAG...]] [--exclude-tags TAG[,TAG...]]"
run: |
echo "Usage: dev rn e2e {ios|android} [--guest] [--hardcoded-buyer-identity]" >&2
echo "Usage: dev rn e2e {ios|android} [--tags TAG[,TAG...]] [--exclude-tags TAG[,TAG...]]" >&2
exit 1
subcommands:
ios:
desc: Run the React Native iOS Maestro checkout smoke flows
syntax: "[--guest] [--hardcoded-buyer-identity]"
desc: Run the React Native iOS Maestro tests
syntax: "[--tags TAG[,TAG...]] [--exclude-tags TAG[,TAG...]]"
run: cd platforms/react-native && ./scripts/e2e_maestro_ios "$@"
android:
desc: Run the React Native Android Maestro checkout smoke flows
syntax: "[--guest] [--hardcoded-buyer-identity]"
desc: Run the React Native Android Maestro tests
syntax: "[--tags TAG[,TAG...]] [--exclude-tags TAG[,TAG...]]"
run: cd platforms/react-native && ./scripts/e2e_maestro_android "$@"
lint:
desc: Run all React Native lint checks (Swift, module, sample)
Expand Down
15 changes: 15 additions & 0 deletions e2e/BITRISE.md
Original file line number Diff line number Diff line change
Expand Up @@ -112,9 +112,24 @@ The launch smoke suite sends only non-sensitive Maestro environment values to Br

- `E2E_APP_ID`
- `E2E_READY_MARKER`
- `E2E_CONTROL_LINK`

Do not pass storefront tokens or customer data through BrowserStack Maestro environment variables without explicit review, because those values are visible in BrowserStack dashboards.

## Account journey secrets

The account journey signs a test customer in, so it needs two more Maestro values. Both are reviewed exceptions to the rule above, because the BrowserStack dashboard is private to this organization and the values never enter the repository.

| Variable | Where it is used | Purpose |
| ---------------------------- | -------------------------------------- | ---------------------------------------------------------------- |
| `E2E_CUSTOMER_ACCOUNT_EMAIL` | Maestro environment | Address the sign-in flow types on the hosted login page. |
| `E2E_CUSTOMER_ACCOUNT_CODE` | Maestro environment | Verification code the sign-in flow types. |
| `CUSTOM_USER_AGENT` | Sample app build, through `.env` | Marks the login web view as a test client. The code fails without it. |

Add all three as secret Bitrise environment variables. Leave them out of any log, any pull request, and any file in this repository. `scripts/setup_storefront_env` reads `CUSTOM_USER_AGENT` from the root `.env` and writes it into each generated sample configuration, all of which are ignored by git.

A run with these values missing skips the account tests instead of failing, so a fork without the secrets still gets a green suite.

## GitHub reporting

The `e2e-report` workflow creates commit statuses, Check Runs, and sticky PR comments using the short-lived token generated by the Bitrise GitHub App.
Expand Down
57 changes: 57 additions & 0 deletions e2e/FLAKES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# E2E flake register

A flake is a test that gives a different result from the same code. Each entry below
records one, with the evidence that proves it. Clear every open entry before release.

Add an entry when a run fails and a re-run of the same commit passes. Record the target,
the symptom, the date, and the artifact directory. Delete the entry when the fix lands.

## Open flakes

| # | Target | Symptom | Seen | Suspected cause |
|---|---|---|---|---|
| F1 | React Native iOS | `launch-smoke` fails within 10 seconds. The driver answers `viewHierarchy` with HTTP 500 and `kAXErrorInvalidUIElement`, or Maestro reports `App crashed or stopped`. The screenshot shows the splash screen and the crash log directory holds no report. | 2026-07-31, 2 runs of 3 | Maestro reads the accessibility tree while the app still builds it. `extendedWaitUntil` retries a missing element, but it does not retry a driver error. |
| F2 | React Native Android | The first `cart-checkout-ready` assert in `bootstrap-cart-from-link.yaml` fails after 53 seconds. The retry in that flow then passes. | 2026-07-31, 1 run of 2 | Metro builds the bundle on request, so the first cart create starts late. |
| F3 | React Native Android on BrowserStack | `checkout-hardcoded-buyer-identity` fails on one commit and passes on the next, with no change to the test or the sample. PR #561 build `0f61b12e` fails after 152 seconds. PR #562 build `0f61b12e` passes. | 2026-07-31, 1 run of 2 | Unknown. Read the Maestro log for both sessions before you treat it as a flake, because [B2](#browserstack-only-failures) may explain both. |

## BrowserStack-only failures

These fail on every BrowserStack run and pass on every local run, so they are not flakes.
They block the whole matrix. Remove this section when the matrix reports green.

| # | Target | Symptom | Suspected cause |
|---|---|---|---|
| B1 | Swift iOS and React Native iOS | Every test that opens the control link fails 1 second after launch. `launch-smoke` passes, so the app starts and the ready marker appears. | Fixed, and proved by two probe builds. BrowserStack ran Maestro 1.39.10, which drives iOS `openLink` through `xcrun simctl openurl`. That command accepts a simulator only, so a real device answers `Invalid device` and exit code 148. BrowserStack added iOS `openLink` in 2.0.7, so the build now pins `maestroVersion`. Maestro 2.4.0 alone still fails, because it implements iOS `clearState` by uninstalling the app and the BrowserStack reinstall reports success without restoring the app. Eleven measured builds separate the two faults: eight with `clearState` failed on five different units, and three without it passed, two of them on units that had just failed. The flag is therefore deterministic, not flaky, and a retry cannot recover it. With both changes `cart-from-control-link` passes in 17 seconds and `openLink` returns in 36 milliseconds through the XCTest driver. |
| B2 | Kotlin Android and React Native Android | `checkout-hardcoded-buyer-identity` fails 2 to 6 minutes in. `launch-smoke`, `cart-from-control-link` and `checkout-present-and-close` all pass, so the control link and the cart work. | Fixed. Two separate faults. First, the flow matched `^First name$` and the field reads `First name (optional)`, so the tap timed out. Second, Bitrise holds no address secrets, so `scripts/setup_storefront_env` fell back to `Toronto, ON, M5V 1M7, CA`. A Canadian address sets the billing country, the province list, the postal field label and the currency, and the flow expects `ZIP code`. The defaults are now the US fixture. Both targets now pass. |
| B3 | Kotlin Android and React Native Android | `checkout-customer-account` fails after 69 seconds, at `Assert that "^Email( address)?$" is visible`. | Confirmed, and it is a missing secret rather than a defect. The sign-in web view opens `http://null/oauth/authorize` with an empty `client_id`, because Bitrise supplies no `CUSTOMER_ACCOUNT_API_CLIENT_ID` and no `CUSTOMER_ACCOUNT_API_SHOP_ID`. The sample reads both at build time. `account_enabled?` now requires all four account values, so the run excludes the account tag until someone adds the two secrets. |
| B4 | Kotlin Android | `checkout-guest` fails after 127 seconds, at `Scrolling DOWN until "Country/Region" is visible`. React Native Android passes the same test in 358 seconds. | Confirmed, and it is a sample defect rather than a BrowserStack fault, so it reproduces anywhere. The Android sample gives a guest cart a Canadian buyer identity: `CartRepository.kt:102` returns `CartBuyerIdentityInput(countryCode = CA)`. Swift returns `CartInput(lines:)` and React Native returns `{}`, so both take the shop market. Checkout then renders a Canadian form and totals in CAD, while the shared fixture is a United States address. The label node matches the text but reports `height=0` and the same bounds through all six swipes, so the scroll can never satisfy it. The React Native log proves the contrast: `United States` is already visible there, so the country picker step is skipped. |

## Runner limits, not product faults

| # | Symptom | Cause |
|---|---|---|
| R1 | A second Maestro run fails with `Connection refused: localhost:7001`. | The iOS driver and the Android driver both bind port 7001. Run one target at a time. |

## Traps that give a false result

These produce a wrong verdict rather than an unstable one. The flows already avoid them.
Keep them out of new flows.

- `visibilityPercentage` under 100 rounds down to zero. `scrollUntilVisible` then stops
before it moves, and the tap that follows lands on whatever already sat there. Use
`centerElement: true`.
- Android `hideKeyboard` sends a Back press when no keyboard is open, which closes the
checkout sheet. Only run `dismiss-active-field.yaml` after an `inputText`.
- Maestro reports a tap that changes nothing as `COMPLETED`. The log line reads
`Nothing changed in the UI`. Assert the result of a tap, never the tap itself.
- A runner that does not set `E2E_DEVICE_ID` lets Maestro pick the device. It can pick the
other platform, and the run then reports the wrong target.
- An unsigned iOS build carries no `application-identifier`, so every keychain write returns
`-34018` and the sample drops the customer from the cart. Checkout then opens as a guest
and the account test fails far from the cause. Build the sample with `CODE_SIGN_IDENTITY=-`.

## Evidence

Maestro writes one directory per run to `~/.maestro/tests/<timestamp>/`. It holds
`maestro.log`, the failure screenshot, and one commands file per test. Read `maestro.log`,
because the commands file carries no order and no status.
Loading
Loading