Skip to content
Merged
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
2 changes: 2 additions & 0 deletions dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ up:
- swiftlint
- swiftformat
- sccache
- bitrise
- ruby
- xcode:
version: "26.2"
Expand Down Expand Up @@ -47,6 +48,7 @@ check:
react-native-lint-sample: cd platforms/react-native && pnpm sample lint
web-lint: cd platforms/web && pnpm lint
web-test: cd platforms/web && pnpm test
e2e-bitrise-validate: bitrise validate --config=e2e/bitrise.yml

commands:
# Repo-wide
Expand Down
64 changes: 64 additions & 0 deletions e2e/BITRISE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Bitrise E2E Pipeline

Checkout Kit E2E tests run in Bitrise through the `e2e` pipeline, with this app-level setup.

## Bitrise app

The pipeline runs on the allocated Bitrise app, connected to the Checkout Kit repository:

- Bitrise app: https://app.bitrise.io/app/f51f9054-053e-40f1-81e9-ae727567ae76
- Repository: `Shopify/checkout-kit`

Useful Bitrise app URLs:

| Area | URL |
|---|---|
| App overview | https://app.bitrise.io/app/f51f9054-053e-40f1-81e9-ae727567ae76 |
| Workflow/config editor | https://app.bitrise.io/app/f51f9054-053e-40f1-81e9-ae727567ae76/workflow |
| Secrets/env vars | https://app.bitrise.io/app/f51f9054-053e-40f1-81e9-ae727567ae76/secrets |
| Code signing | https://app.bitrise.io/app/f51f9054-053e-40f1-81e9-ae727567ae76/codesigning |
| Build triggers | https://app.bitrise.io/app/f51f9054-053e-40f1-81e9-ae727567ae76/triggers |
| Start build | https://app.bitrise.io/app/f51f9054-053e-40f1-81e9-ae727567ae76/build/start |

If a direct URL does not resolve in the current Bitrise UI, open the app overview and navigate to the matching area from the sidebar.

## Pipeline

The `e2e` pipeline is defined in `e2e/bitrise.yml`, and the Bitrise app reads its configuration directly from that repository path:

```text
e2e/bitrise.yml
```

The pipeline defines the E2E workflow graph and the intermediate artifacts each workflow passes to the next.

Default stacks and machine types live in `e2e/bitrise.yml`; workflows run on Linux unless they require macOS-specific tooling.

Validate the configuration locally with:

```bash
bitrise validate -c e2e/bitrise.yml
```

## Required app environment variables

The non-secret E2E defaults live in `e2e/bitrise.yml` under `app.envs`. Change them in this repository rather than in the Bitrise Workflow Editor.

| Variable | Value | Purpose |
|---|---|---|
| `E2E_STRICT` | `false` | Soft/hard failure switch for BrowserStack Maestro runs. |
| `E2E_BROWSERSTACK_API_RETRIES` | `1` | Retries for transient BrowserStack API responses. |
| `E2E_BROWSERSTACK_TIMEOUT_SECONDS` | `1800` | BrowserStack build timeout. |
| `E2E_BROWSERSTACK_POLL_SECONDS` | `30` | BrowserStack status polling interval. |

Each workflow's main `script` step sets its own wall-clock budget with the Bitrise `timeout` and `no_output_timeout` step properties instead of wrapping individual commands.

The `e2e-execute-browserstack-run` workflow fans out one parallel copy per BrowserStack run plan row. The `e2e-produce-browserstack-run-plan` workflow derives this count with `ruby e2e/scripts/e2e_matrix_to_browserstack_run_plan count` and publishes it as `E2E_BROWSERSTACK_RUN_PLAN_COUNT`, which the `e2e-execute-browserstack-run` `parallel` field reads, so it never needs manual alignment.

## Caching

The pipeline uses Bitrise cache steps for key-based pnpm/CocoaPods/Gradle cache paths.

Do not add `activate-build-cache-for-xcode` or `activate-build-cache-for-gradle`; the Bitrise Build Cache add-on is disabled for Shopify Bitrise apps.

Ruby and Node versions are pinned in `e2e/bitrise.yml` via the Bitrise `tools:` configuration (`ruby: 3.3.6`, `nodejs: 22.14.0`), which Bitrise installs before each workflow runs. Pin exact versions that the target stacks preinstall so setup stays fast and reproducible; a version the stack does not ship is installed on demand and is slower. pnpm is pinned separately through Corepack via the `packageManager` field in `platforms/react-native/package.json`.
25 changes: 16 additions & 9 deletions e2e/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,9 +61,10 @@ the React Native sample configuration, not local in-repo native SDK overrides.
## Matrix

CI runs are described by `config/matrix.yml`. The matrix expands applications, OS
version tags, and suites into BrowserStack Maestro run rows. Because Bitrise has no
built-in matrix support, `e2e/lib/e2e_matrix.rb` performs the expansion and the
pipeline parallelizes over the resulting rows.
version tags, and suites into a BrowserStack run plan. Because Bitrise has no
built-in matrix support, `e2e/lib/e2e_matrix_to_browserstack_run_plan.rb`
transforms the matrix into a BrowserStack run plan and the pipeline
parallelizes over the resulting rows.

Current applications:

Expand All @@ -81,19 +82,25 @@ Current suites:
Validate the matrix:

```bash
ruby e2e/scripts/e2e_matrix validate
ruby e2e/scripts/e2e_matrix_to_browserstack_run_plan validate
```

Expand all run rows:
Expand the BrowserStack run plan:

```bash
ruby e2e/scripts/e2e_matrix expand
ruby e2e/scripts/e2e_matrix_to_browserstack_run_plan expand
```

Expand a single run row by index:
Expand a single BrowserStack run plan row by index:

```bash
ruby e2e/scripts/e2e_matrix expand --index 0
ruby e2e/scripts/e2e_matrix_to_browserstack_run_plan expand --index 0
```

Count BrowserStack run plan rows:

```bash
ruby e2e/scripts/e2e_matrix_to_browserstack_run_plan count
```

## Files
Expand All @@ -102,7 +109,7 @@ ruby e2e/scripts/e2e_matrix expand --index 0
- `flows/` contains reusable Maestro subflows for app setup and checkout steps.
- `tests/react-native/full-guest-checkout.yaml` composes the React Native guest
checkout smoke test from those subflows.
- `config/matrix.yml`, `lib/e2e_matrix.rb`, and `scripts/` drive the CI matrix.
- `config/matrix.yml`, `lib/e2e_matrix_to_browserstack_run_plan.rb`, and `scripts/` drive the BrowserStack run plan.
- `tests/shared/launch-smoke.yaml` is the shared launch smoke suite.

## Shared app contract
Expand Down
168 changes: 168 additions & 0 deletions e2e/bitrise.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
format_version: "23"
default_step_lib_source: https://github.com/bitrise-io/bitrise-steplib.git
project_type: other

meta:
bitrise.io:
stack: ubuntu-noble-24.04-bitrise-2025-android
machine_type_id: g2.linux.medium

app:
envs:
- E2E_STRICT: "false"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The PR body says this config sets RBENV_VERSION=system, but it is missing here. Since the repo has its own .ruby-version and this Bitrise config pins another Ruby version, scripts from the checkout can still ask rbenv for the checkout version before the run plan is produced.

Could we restore the app-level override?

Suggested change
- E2E_STRICT: "false"
- RBENV_VERSION: system
- E2E_STRICT: "false"

@kieran-osgood-shopify kieran-osgood-shopify Jul 8, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for catching that, the PR body got stale as we're no longer using rbenv (we rely now on the tools version which uses asdf

tools:
  ruby: 3.3.6
  nodejs: 22.14.0

I have a ticket here: https://github.com/orgs/shop/projects/1142/views/1?sliceBy%5Bvalue%5D=kieran-osgood-shopify&pane=issue&itemId=4837082758&issue=shop%7Cissues-checkout-kit%7C1136 to resolve the ruby inconsistencies holistically across the repo as we currently have at least 3 .ruby-version files

- E2E_BROWSERSTACK_API_RETRIES: "1"
- E2E_BROWSERSTACK_TIMEOUT_SECONDS: "1800"
- E2E_BROWSERSTACK_POLL_SECONDS: "30"

tools:
ruby: 3.3.6
nodejs: 22.14.0

pipelines:
e2e:
workflows:
e2e-produce-browserstack-run-plan: {}
e2e-build-react-native-ios:
depends_on:
- e2e-produce-browserstack-run-plan
e2e-build-react-native-android:
depends_on:
- e2e-produce-browserstack-run-plan
e2e-execute-browserstack-run:
depends_on:
- e2e-produce-browserstack-run-plan
- e2e-build-react-native-ios
- e2e-build-react-native-android
parallel: $E2E_BROWSERSTACK_RUN_PLAN_COUNT
e2e-report:
depends_on:
- e2e-execute-browserstack-run
should_always_run: workflow

workflows:
e2e-produce-browserstack-run-plan:
steps:
- git-clone@8: {}
- script@1:
title: Produce BrowserStack run plan placeholder
timeout: 1800
no_output_timeout: 1800
inputs:
- content: |-
set -euo pipefail
source e2e/scripts/bitrise_ci_helpers
ruby e2e/scripts/e2e_matrix_to_browserstack_run_plan validate
e2e_log "Producing BrowserStack run plan"
ruby e2e/scripts/e2e_matrix_to_browserstack_run_plan expand > "$BITRISE_DEPLOY_DIR/browserstack-run-plan.json"
e2e_log "Publishing BrowserStack run plan count"
envman add --key E2E_BROWSERSTACK_RUN_PLAN_COUNT --value "$(ruby e2e/scripts/e2e_matrix_to_browserstack_run_plan count)"
e2e_log "Creating E2E tests zip placeholder"
mkdir -p "$BITRISE_DEPLOY_DIR/e2e"
echo "Phase 2 placeholder for E2E tests zip" > "$BITRISE_DEPLOY_DIR/e2e/e2e-tests.zip"
e2e_log "Publishing E2E tests zip path"
envman add --key E2E_TESTS_ZIP --value "$BITRISE_DEPLOY_DIR/e2e/e2e-tests.zip"
- share-pipeline-variable@1:
inputs:
- variables: E2E_BROWSERSTACK_RUN_PLAN_COUNT
- deploy-to-bitrise-io@2:
is_always_run: false
inputs:
- pipeline_intermediate_files: |-
$E2E_TESTS_ZIP:E2E_TESTS_ZIP
$BITRISE_DEPLOY_DIR/browserstack-run-plan.json:E2E_BROWSERSTACK_RUN_PLAN_JSON

e2e-build-react-native-ios:
steps:
- git-clone@8: {}
- restore-cache@3:
inputs:
- key: |-
rn-ios-{{ .OS }}-{{ .Arch }}-{{ checksum "platforms/react-native/pnpm-lock.yaml" }}-{{ checksum "platforms/react-native/sample/ios/Podfile.lock" }}
- script@1:
title: Create React Native iOS artifact placeholder
inputs:
- content: |-
set -euo pipefail
mkdir -p "$BITRISE_DEPLOY_DIR/e2e"
echo "Phase 2 placeholder for React Native iOS IPA" > "$BITRISE_DEPLOY_DIR/e2e/react-native-ios.ipa"
envman add --key E2E_REACT_NATIVE_IOS_APP_PATH --value "$BITRISE_DEPLOY_DIR/e2e/react-native-ios.ipa"
- deploy-to-bitrise-io@2:
inputs:
- pipeline_intermediate_files: |-
$E2E_REACT_NATIVE_IOS_APP_PATH:E2E_REACT_NATIVE_IOS_APP_PATH
- save-cache@1:
inputs:
- key: |-
rn-ios-{{ .OS }}-{{ .Arch }}-{{ checksum "platforms/react-native/pnpm-lock.yaml" }}-{{ checksum "platforms/react-native/sample/ios/Podfile.lock" }}
- paths: |-
platforms/react-native/node_modules
platforms/react-native/sample/ios/Pods

e2e-build-react-native-android:
steps:
- git-clone@8: {}
- restore-cache@3:
inputs:
- key: |-
rn-android-{{ checksum "platforms/react-native/pnpm-lock.yaml" }}-{{ checksum "platforms/react-native/sample/android/**/*.gradle*" }}
- script@1:
title: Create React Native Android artifact placeholder
inputs:
- content: |-
set -euo pipefail
mkdir -p "$BITRISE_DEPLOY_DIR/e2e"
echo "Phase 2 placeholder for React Native Android APK" > "$BITRISE_DEPLOY_DIR/e2e/react-native-android.apk"
envman add --key E2E_REACT_NATIVE_ANDROID_APP_PATH --value "$BITRISE_DEPLOY_DIR/e2e/react-native-android.apk"
- deploy-to-bitrise-io@2:
inputs:
- pipeline_intermediate_files: |-
$E2E_REACT_NATIVE_ANDROID_APP_PATH:E2E_REACT_NATIVE_ANDROID_APP_PATH
- save-cache@1:
inputs:
- key: |-
rn-android-{{ checksum "platforms/react-native/pnpm-lock.yaml" }}-{{ checksum "platforms/react-native/sample/android/**/*.gradle*" }}
- paths: |-
platforms/react-native/node_modules
~/.gradle/caches
~/.gradle/wrapper

e2e-execute-browserstack-run:
steps:
- git-clone@8: {}
- pull-intermediate-files@1:
inputs:
- artifact_sources: |-
e2e-produce-browserstack-run-plan
e2e-build-react-native-*
- script@1:
title: Resolve BrowserStack run plan row placeholder
timeout: 2700
no_output_timeout: 2700
inputs:
- content: |-
set -euo pipefail
source e2e/scripts/bitrise_ci_helpers
: "${BITRISE_IO_PARALLEL_INDEX:?BITRISE_IO_PARALLEL_INDEX is required for the parallel e2e-execute-browserstack-run workflow.}"
run_index="$BITRISE_IO_PARALLEL_INDEX"
mkdir -p "$BITRISE_DEPLOY_DIR/e2e/results"
: "${E2E_BROWSERSTACK_RUN_PLAN_JSON:?E2E_BROWSERSTACK_RUN_PLAN_JSON is required. Check e2e-execute-browserstack-run pull-intermediate-files artifact_sources.}"
e2e_log "Resolving BrowserStack run plan row ${run_index}"
ruby -rjson -e 'runs = JSON.parse(File.read(ARGV.fetch(0))); puts JSON.pretty_generate(runs.fetch(Integer(ARGV.fetch(1))))' "$E2E_BROWSERSTACK_RUN_PLAN_JSON" "$run_index" > "$BITRISE_DEPLOY_DIR/e2e/results/run-${run_index}.json"
- deploy-to-bitrise-io@2:
inputs:
- pipeline_intermediate_files: |-
$BITRISE_DEPLOY_DIR/e2e/results:E2E_BROWSERSTACK_RESULTS_DIR

e2e-report:
steps:
- git-clone@8: {}
- pull-intermediate-files@1:
inputs:
- artifact_sources: |-
e2e-execute-browserstack-run
- script@1:

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

title: Placeholder E2E report
inputs:
- content: |-
set -euo pipefail
echo "Phase 2 placeholder for aggregate E2E reporting"
Original file line number Diff line number Diff line change
Expand Up @@ -2,20 +2,20 @@

require "yaml"

# Expands the compact E2E test matrix in config/matrix.yml into one fully-resolved
# run per (application x os_version_tag x suite) combination.
# Transforms the compact E2E matrix in config/matrix.yml into the BrowserStack
# run plan consumed by the parallel Bitrise execution workflow.
#
# Unlike GitHub Actions, Bitrise has no built-in matrix/strategy support, so we
# do the fan-out ourselves: `expand` produces the JSON list of runs that the
# pipeline consumes, and Bitrise parallelizes over it via `run_at`/`count`.
# do the fan-out ourselves: `expand` produces the JSON list of runs, and Bitrise
# parallelizes over it via `run_at`/`count`.
#
# Today the numbers make the config and the expansion look equivalent -
# 2 applications + 1 os_version_tag + 1 suite, and 2 * 1 * 1 = 2 runs - so it can look
# like a plain YAML-to-JSON copy. The point is the multiplication, not the copy:
# adding a single os_version_tag (e.g. a minimum-supported OS) transparently duplicates
# every application x suite across that OS, turning an additive config change into
# a multiplicative set of runs without hand-writing each one.
class E2EMatrix
class E2EMatrixToBrowserStackRunPlan
attr_reader :config_path

def self.load(config_path)
Expand Down Expand Up @@ -46,6 +46,10 @@ def run_at(index)
runs.fetch(index)
end

def count
expand.length
end

def validation_errors
errors = []
errors << "version must be 1" unless @config.fetch("version", nil) == 1
Expand Down
7 changes: 7 additions & 0 deletions e2e/scripts/bitrise_ci_helpers
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/usr/bin/env bash

set -euo pipefail

e2e_log() {
printf '\n==> %s\n' "$*"
}
Loading
Loading