From 331a95faa41f8afee6c9845b5e859e1919b623cd Mon Sep 17 00:00:00 2001 From: Mike Pitre <12040919+mikepitre@users.noreply.github.com> Date: Tue, 13 Oct 2026 22:28:30 -0400 Subject: [PATCH 01/10] feat(expo): stop a run of failing tests early with --max-failures A build whose app never reaches its home fails every test of `integration/expo-native/` the same way. Each test waits 60 seconds for the home, and CI tries it twice, so the run spent about two minutes on each of its tests before it could say that the build is broken. `run` now takes `--max-failures ` and passes it to e2e. e2e stops once `n` tests have failed and reports the tests it did not start as skipped by the limit. The CLI already turns such a skip into a failed result that says `not run: failure-limit`, and a new test pins that, so a test the limit stopped never counts as passed. `run` starts e2e once for each settings group, so the limit counts the failures of one group, and the groups after it still start. The workflow passes 5 on each platform. None of its last 40 runs failed, so a healthy build stays far below the limit. The changeset is empty, as it is in the other pull requests of the stack. Co-Authored-By: Claude Opus 5.5 --- .changeset/expo-verify-e2e-small-fixes.md | 2 ++ .claude/skills/verify-clerk-expo/SKILL.md | 4 ++-- .github/workflows/verify-e2e.yml | 2 +- integration/expo-native/src/core/MANIFEST | 6 ++--- integration/expo-native/src/core/cli.ts | 7 ++++-- integration/expo-native/src/core/e2e.ts | 1 + integration/expo-native/src/core/types.ts | 1 + integration/expo-native/test/cli.test.ts | 2 ++ integration/expo-native/test/e2e.test.ts | 27 +++++++++++++++++++++-- 9 files changed, 42 insertions(+), 10 deletions(-) create mode 100644 .changeset/expo-verify-e2e-small-fixes.md diff --git a/.changeset/expo-verify-e2e-small-fixes.md b/.changeset/expo-verify-e2e-small-fixes.md new file mode 100644 index 00000000000..a845151cc84 --- /dev/null +++ b/.changeset/expo-verify-e2e-small-fixes.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/.claude/skills/verify-clerk-expo/SKILL.md b/.claude/skills/verify-clerk-expo/SKILL.md index 8b0cebbda83..6abeb72d1fc 100644 --- a/.claude/skills/verify-clerk-expo/SKILL.md +++ b/.claude/skills/verify-clerk-expo/SKILL.md @@ -110,7 +110,7 @@ $ integration/expo-native/bin/control-clerk-expo run native-auth-view native-js- $ integration/expo-native/bin/control-clerk-expo screen --platform ios # the UI tree that is on screen now; --png adds a screenshot ``` -`run --all` runs every committed spec. `run` also takes `--grep `, `--retries `, `--no-video`, and `--wait `. `--retries ` runs a failed test again, up to `n` more times. The default is 0, so a run of your own change shows exactly what happened. The wait covers a free lane and another `run` in this worktree that holds the device. A spec limited to one platform with `test(title, { platforms: ['ios'] }, fn)` reports as skipped on the other. +`run --all` runs every committed spec. `run` also takes `--grep `, `--retries `, `--max-failures `, `--no-video`, and `--wait `. `--retries ` runs a failed test again, up to `n` more times. The default is 0, so a run of your own change shows exactly what happened. `--max-failures ` stops a settings group once `n` of its tests have failed. A test that the limit stopped before it started prints `FAIL` with `not run: failure-limit`, and `run` exits 1. Without the flag, a run has no limit. The wait covers a free lane and another `run` in this worktree that holds the device. A spec limited to one platform with `test(title, { platforms: ['ios'] }, fn)` reports as skipped on the other. For a JS change on a local device, edit the source and `run` the spec, with no `up` in between. For a change to a native input, the same `run` rebuilds the dev client first. On a remote device, commit and push, then `run`. @@ -270,7 +270,7 @@ For a remote device, `down` ends the runner job and waits for it to finish, and - `src/host.ts`, `src/fixture.ts`, `src/native-build.ts`, and `src/freshness.ts` are this repository's own: the build of the test app, the native build that CI keeps between runs, the Metro ports, and the check that Metro serves current JS. `specs/app.ts` names the test app and its entry for a dev client, and `specs/native.ts` holds the per-platform locators for the native views and the locators of the home's links. Both are this repository's own too. - `npm test --prefix integration/expo-native` runs the CLI's unit tests, with no network, key, or device. `npm run typecheck --prefix integration/expo-native` runs `tsc`. The `Expo Native Runner Tests` job in `.github/workflows/ci.yml` runs both on Linux when a pull request changes the package, the test app, or a package the test app links. - `run --github-report` hands the results of the run to `@e2e-dev/github` as one report. The reporter writes the report to the job summary. With a `GITHUB_TOKEN` that may write pull request comments, it also posts one comment on the pull request and updates that comment on later runs. The reporter never changes the exit code, and nothing is reported for a run with a tainted file. -- `.github/workflows/verify-e2e.yml`, the `Verify end-to-end tests` workflow, runs `up --backend local`, `run --all --retries 1 --github-report`, and `down` on a runner for each platform, with a device and a Clerk application for each. It starts on a pull request to `main` that changes the package, the test app, or one of the four packages the test app links. It runs for a pull request that is not a draft, and a draft or a pull request from a fork gets a notice instead. Start it by hand with `gh workflow run verify-e2e.yml --ref `. A failing spec shows on the pull request and is not required for a merge, and a test that fails and then passes on its one retry is `flaky` and does not fail the job. The runners are GitHub-hosted, `macos-26` and `ubuntu-24.04`, and the repository variables `VERIFY_CI_RUNNER_IOS` and `VERIFY_CI_RUNNER_ANDROID` name other labels. The Platform API key comes from the `MOBILE_VERIFICATION_PLATFORM_API_KEY` repository secret. The workflow sets `VERIFY_LOCAL_BUILD=standalone` and `VERIFY_NATIVE_CACHE` ([freshness.md](references/freshness.md)). It uploads each run's `run.json`, app log, driver logs, video, screenshots, and e2e's `report.json`, `junit.xml`, summary, and failure pages for three days, and only when no secret is found in the run. `bin/boot-ios-simulators.sh wait` waits until a booted simulator is ready, and this workflow and `.github/workflows/verify-remote.yml` call it. +- `.github/workflows/verify-e2e.yml`, the `Verify end-to-end tests` workflow, runs `up --backend local`, `run --all --retries 1 --max-failures 5 --github-report`, and `down` on a runner for each platform, with a device and a Clerk application for each. It starts on a pull request to `main` that changes the package, the test app, or one of the four packages the test app links. It runs for a pull request that is not a draft, and a draft or a pull request from a fork gets a notice instead. Start it by hand with `gh workflow run verify-e2e.yml --ref `. A failing spec shows on the pull request and is not required for a merge, and a test that fails and then passes on its one retry is `flaky` and does not fail the job. The runners are GitHub-hosted, `macos-26` and `ubuntu-24.04`, and the repository variables `VERIFY_CI_RUNNER_IOS` and `VERIFY_CI_RUNNER_ANDROID` name other labels. The Platform API key comes from the `MOBILE_VERIFICATION_PLATFORM_API_KEY` repository secret. The workflow sets `VERIFY_LOCAL_BUILD=standalone` and `VERIFY_NATIVE_CACHE` ([freshness.md](references/freshness.md)). It uploads each run's `run.json`, app log, driver logs, video, screenshots, and e2e's `report.json`, `junit.xml`, summary, and failure pages for three days, and only when no secret is found in the run. `bin/boot-ios-simulators.sh wait` waits until a booted simulator is ready, and this workflow and `.github/workflows/verify-remote.yml` call it. - The `Expo` workflow, `.github/workflows/expo-native-build.yml`, runs nothing on a device. It builds the test app as a Release app on Expo SDK 54, 55, and 57 for each platform, and runs the Android unit tests of `@clerk/expo-biometrics`. - `.github/workflows/verify-attach.yml` publishes the evidence that `attach` hands to a session's runner, with `scripts/verify-attach.mjs`. It reads the manifest that `src/core/remote/handoff.ts` writes. It and `src/core/publish.ts` each replace the block the other wrote, each applies the same rules to the comments and to the start of the line, and each lays the files out in the same tables. The script is checked out alone, so it imports nothing from the package. So a change to the manifest, the comments, those rules, or the tables is a change in both places. `scripts/verify-attach.test.mjs` has its tests. - A remote session runs in `.github/workflows/verify-remote.yml`. `src/platform/session-device.ts` holds the steps that the session runs on its device, and `src/core/remote/` is the code on both ends of it. The runner installs the package with `npm ci`. A session keeps the code under `src/core/` from the commit it started on, so after a change to a file that `src/core/MANIFEST` lists, commit, push, `down`, then `up`. diff --git a/.github/workflows/verify-e2e.yml b/.github/workflows/verify-e2e.yml index 79abdf9d707..4ff977bdd09 100644 --- a/.github/workflows/verify-e2e.yml +++ b/.github/workflows/verify-e2e.yml @@ -239,7 +239,7 @@ jobs: CLERK_PLATFORM_API_KEY: ${{ secrets.MOBILE_VERIFICATION_PLATFORM_API_KEY }} GITHUB_TOKEN: ${{ github.token }} run: | - "$CLI" run --all --platform "$PLATFORM" --backend local --retries 1 --github-report + "$CLI" run --all --platform "$PLATFORM" --backend local --retries 1 --max-failures 5 --github-report - name: Release the device and delete the Clerk application if: always() && steps.install.outcome == 'success' diff --git a/integration/expo-native/src/core/MANIFEST b/integration/expo-native/src/core/MANIFEST index b78165fc7a5..eb888eab3cc 100644 --- a/integration/expo-native/src/core/MANIFEST +++ b/integration/expo-native/src/core/MANIFEST @@ -17,10 +17,10 @@ df955a42a2bd4ca1780f56837ebb9c832c68e97f74a35764d42a32e44b28286b specs/support/ d56c29efc8061adfd65b89f0f02465af3234ecd52289ef6d9f338483afeadeb5 src/core/broker.ts 7c741449c755524bba80f1ce314554293003a34f7f7c30696015ba1c0c3dc1ab src/core/claims.ts 1fc59d13e7197e6c7099c0467744b978d498d1fe409c88254d44e2f99913a7b5 src/core/clerk.ts -6eef5f3989f8e29771fb47ec1b3721699249ea272e5009ccf8e66ece9981c4e6 src/core/cli.ts +8d0fb398aec4187b15b82ef88142b0a548f0ff4a0d26902c3e01ea0659283236 src/core/cli.ts 4aeacea985f5ef80001e5695feec53182abb9695eb07130898568743755fc8af src/core/devices.ts 3ae461d6aa75a2cefac0392a9ad124aec4105ac25b9320371743e2ea90fe2202 src/core/driver.ts -efc4cdabcd1b00e903bfcfeb077016f1654c07ef4926b6389d248227c12c0af5 src/core/e2e.ts +14e74b156592a645ae579e3a8f900c5bb0a8be487adfacd4ff94ca268ce0dbdf src/core/e2e.ts c9a29e27eb835d9c3fc9b0bd394ff55b5f1fcc2b2fc0d96da51197e5e7bca003 src/core/evidence.ts 27bfd4ce59937aa8c97d9fd6849cf52f3da05626fbc9388385f4b5e684577d36 src/core/exec.ts e2075f17f52ded8dea64d8a2473820396ca5ac1a22db59f21a092da28e55911f src/core/github-report.ts @@ -47,6 +47,6 @@ d614ee5c98a85d15037b744a67404496273f62985fd7d6a049cacfd5ab335503 src/core/remot e734336ea3b345057b69193790e2efce9a662c67ec5b4a426c811f999b45ccdb src/core/remote/tunnel.ts 685eabf010c7f63d076cfd3da1a681b5cc8024720538c1bf3a99792b563d8b60 src/core/slot.ts a8838aaf1fa9e4a6bc4e350ea674079201da76c5962656039b96db2505591aed src/core/state.ts -25501a3117a814fb9e0cd4a00a4ecaf33a28e589b0a0e22679ab660736846b15 src/core/types.ts +b242eff9cb23ed78af02190a7decd47d8299089996648b6f0c2540ca2fe4c0d1 src/core/types.ts 79b822b663c1ac1748cf3c8ec390ef9204378f4c97531f4da9b0bf5736a20d36 src/core/verbs.ts 9be1816773216373548ec9933978b53ace3d3bfdba17b72c1e5ed6d8aef80f62 src/core/workspace.ts diff --git a/integration/expo-native/src/core/cli.ts b/integration/expo-native/src/core/cli.ts index 3a03155f903..a288fe92ba7 100644 --- a/integration/expo-native/src/core/cli.ts +++ b/integration/expo-native/src/core/cli.ts @@ -31,7 +31,7 @@ const VERBS: readonly Verb[] = ['doctor', 'up', 'run', 'screen', 'attach', 'down const USAGE_FIX = [ '{cli} doctor [--platform p] [--backend auto|local|remote] [--live [--runner