Repository navigation
test(expo): add a verify skill that drives the expo-native fixture on a local or borrowed device - #10087
Draft
mikepitre wants to merge 22 commits into
Draft
test(expo): add a verify skill that drives the expo-native fixture on a local or borrowed device#10087mikepitre wants to merge 22 commits into
mikepitre wants to merge 22 commits into
Conversation
🦋 Changeset detectedLatest commit: 8e4f27c The changes in this PR will be included in the next version bump. This PR includes changesets to release 0 packagesWhen changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: true
Comment |
mikepitre
force-pushed
the
mike/expo-verify-skill
branch
from
October 6, 2026 06:03
482deff to
435e872
Compare
mikepitre
force-pushed
the
mike/expo-verify-remote
branch
from
October 6, 2026 06:03
160b44a to
729ecd1
Compare
The skill is outside the pnpm workspace and installs its pinned e2e with npm from its own lockfile. .cursor/skills/verify-clerk-expo is a symlink to the skill, and .prettierignore keeps the pre-commit hook off the files that are copied from other repositories. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
src/core, src/platform/ios, specs/fixtures.ts, testing, e2e.config.ts, and these tests are byte copies of clerk/clerk-ios. src/platform/android and test/android.test.ts are byte copies of clerk/clerk-android. src/core/MANIFEST pins the core. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… check, and the golden specs src/host.ts builds the expo-native fixture as a Debug dev client, starts the watch build and one Metro per lane, and waits until Metro serves current JS before specs start. specs/golden holds ten spec files for six features. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…them Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mikepitre
force-pushed
the
mike/expo-verify-skill
branch
from
October 6, 2026 06:41
435e872 to
45a50d0
Compare
mikepitre
force-pushed
the
mike/expo-verify-remote
branch
from
October 6, 2026 06:41
729ecd1 to
2d05137
Compare
3 of 9 tasks
mikepitre
changed the base branch from
mike/expo-verify-skill
to
mike/expo-verify-host
October 6, 2026 16:30
3 of 9 tasks
…dev/mobile 0.10.0 The new mobile engine brings agent-device 0.21.22. e2e 0.18.0 refuses Node 24.0 to 24.7, so `engines` and the `node` check of `doctor` ask for 24.8.0 or newer on 24. The lockfile is regenerated with npm. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
agent-device 0.21.22 no longer refuses a tap on a control of a native view, so `host.tap` is `locator.tap()` with the assertion timeout and the helper that tapped the middle of a node is gone. `host.fill` still taps the field and types through agent-device, because a native text field on iOS shows no text input until it has focus. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…pt of a flaky test `run --retries <n>` runs a failed test again, up to n more times. The default is 0. A test that passes on a retry is printed and recorded as flaky with the error, failure page, and screenshot of its failed attempt, and it does not fail the run. The e2e config no longer sets retries. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…gh @e2e-dev/github After the run is sealed, the reports of every settings group are merged and handed to the reporter once, with the platform as its key, so one job gives one job summary and one pull request comment per platform. The switch is off by default. A GitHub token in the environment is treated as a secret of every run: output is redacted, a file that holds it is marked tainted, and a tainted run is never reported. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
After typing, `host.fill` reads the focused text input back. If it is still empty after two seconds, the helper taps it and types once more, and it fails with "the text never reached the field" if it is empty again. It confirms nothing when it cannot tell: a password field withholds its value, and on iOS an empty React Native field of the fixture reads as its placeholder. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…repository A test that runs `git init` under a git hook or a rebase exec inherited `GIT_DIR` and wrote to the real checkout. `testing/git-env.ts` clears the variables that name a repository, every test file imports it first, and `test/git-env.test.ts` fails when a test file does not. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mikepitre
force-pushed
the
mike/expo-verify-remote
branch
from
October 6, 2026 16:55
2d05137 to
43ca8cc
Compare
3 of 9 tasks
…id lane emulator A lane now boots with the window, transition, and animator animation scales at 0, so a spec sees each screen change at once. It also sets `hide_error_dialogs`: on a slow CI runner the launcher can stop responding while the emulator boots, and agent-device refuses every tap while that dialog covers the app. The local backend reads the settings back and fails `up` when one did not take. A borrowed emulator boots its lane through the same code. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…k-android src/core/remote, the launcher, and these tests are byte copies of clerk/clerk-ios at its remote device layer. src/platform/android is a byte copy of clerk/clerk-android at the same layer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ify skill verify-remote.yml runs a session for either platform. A session builds the pushed commit as a standalone Release app with the JS embedded, because a runner cannot reach Metro on the developer's machine. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…n creates The session created its simulator on the runtime that matched the default Xcode of the runner image, so an image update could change the iOS version under the specs without a change in this repository. The workflow now names the runtime. On a runner without it, the step fails and lists the runtimes the runner has. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A remote session now starts on `macos-26` for iOS and `ubuntu-24.04` for Android, which GitHub hosts at no cost for a public repository. `--runner <label>` and `VERIFY_REMOTE_RUNNER` still choose a Blacksmith label, which is faster and billed by the minute. The free Mac is slow, so the workflow waits longer: five minutes for the cloudflared download, ten for the tunnel, and as long as the step allows for the first boot of the simulator. On a GitHub-hosted label the session writes no Gradle cache and no pnpm cache for macOS, because the repository's Actions cache is already at its limit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
mikepitre
force-pushed
the
mike/expo-verify-remote
branch
from
October 6, 2026 17:50
43ca8cc to
e31b477
Compare
The shared files are byte copies of clerk/clerk-ios after eight cuts there. What changes for this skill: - A spec declares other instance settings in a JSON file beside it, `<name>.settings.json`. An exported `instanceSettings` is refused, and so is any other JSON file under `specs/`. No golden spec declares any. - A worktree has one Clerk application, which serves one run at a time. A run that starts while a run on the other platform is driving fails with `DEVICE_BUSY`. - The settings check compares the 61 leaves a spec cannot run without and the leaves a settings file declares. - The Backend API is called on api.clerk.com only. A 401 to an instance's own key fails with its cause. - The switch for e2e's AI judge and the doctor check for a daemon of a deleted install are gone. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The short job that reads a borrowed-device request ran on a Blacksmith label when the session's own label was a Blacksmith one. It now runs on `ubuntu-latest` for every session. `VERIFY_REMOTE_PLAN_RUNNER` still names another label. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`up` ended a session that was not ready 25 minutes after it asked for the build. The Expo iOS build on the free GitHub-hosted Mac took 1,432 and 1,441 s in the two sessions measured, about a minute short of that, so a slightly slower runner would have failed `up` with NOT_READY and thrown the session away. A session that ends or fails its build is still reported at once, so the longer limit only applies to one that is alive and still building. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch was successfully deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Adds
verify-clerk-expo, a skill and CLI that an agent uses to prove a@clerk/expochange on an iOS simulator or Android emulator, with video, screenshots, and app state as evidence. It drives theexpo-nativefixture through the launch inputs from #10052, against a Clerk application that it creates for the worktree and deletes afterwards. The device runs on the developer's Mac. On a machine that cannot run one, such as a Linux machine or a cloud agent's sandbox, the CLI borrows a simulator or emulator on a CI runner and drives it the same way.The skill is at
.claude/skills/verify-clerk-expo/, the CLI isbin/control-clerk-expoinside it, and.cursor/skills/verify-clerk-expois a symlink so Cursor reads the same files.packages/expo/AGENTS.mdhas the short version for agents, and the rootAGENTS.mdpoints to it. This pull request holds what #10053 held, and #10053 is closed.Where to start reading:
SKILL.md, thensrc/host.tsandsrc/fixture.ts, then.github/workflows/verify-remote.yml. About 3,700 of the 20,000 added lines are written for this repo. The rest are the lockfile and byte copies of the verification skills in clerk/clerk-ios (clerk/clerk-ios#629) and clerk/clerk-android (clerk/clerk-android#1046), where they are reviewed:src/core/,src/platform/ios/,src/platform/android/,specs/fixtures.ts,testing/, and every test file buttest/host.test.ts,test/freshness.test.ts, andtest/remote-host.test.ts.src/core/MANIFESTlists the hashes of the core files, and a unit test andcontrol-clerk-expo doctorfail when one drifts.The commits are in reading order, the local device first and the borrowed device after it:
src/host.ts,src/fixture.ts,src/freshness.ts,specs/native.ts, ten spec files for six features underspecs/golden/), the docs, and a CI job for the unit tests.e2e0.18.0 and@e2e-dev/mobile0.10.0 and take what came with that in the copies: a plain tap inhost.tap,run --retries <n>,run --github-report,junit.xmlbeside each report, ahost.fillthat checks the text reached the field, unit tests that cannot write to the repository they run in, and an Android emulator with animations and system error dialogs off.src/core/remote/and its tests), then this repo's own code (.github/workflows/verify-remote.yml,src/platform/session-device.ts, the second build product insrc/fixture.ts, the two remote backends insrc/host.ts), then the docs.What the CLI does:
up --platform ios|androidbuilds the fixture as a Debug dev client, creates the worktree's Clerk application, takes a device the skill owns (a cloned simulator or a read-only emulator), installs the app, and startstsdown --watchinpackages/expoandexpo starton a port fixed per device.run <feature>runs that feature's specs and writesvideo.mp4, screenshots, and everyverify.statethe app reported under.claude/skills/verify-clerk-expo/.verify/runs/<run-id>/. The features arenative-auth-view,user-button-and-profile,custom-flow-sign-in,custom-flow-sign-up,token-cache-persistence, andnative-js-sync.downreleases the devices, stops Metro and the watch build, and deletes the application with every user in it. It keeps the evidence.doctoronly reads. It creates no file, device, or application.packages/expo/srcreaches a local device on the nextrunwith no native build. Before the specs start,runwaits until the watch build has caught up and Metro serves a bundle built from the currentdist, and fails withNOT_READYif it never does. A change to a native input (the list isnativeInputsinsrc/fixture.ts) rebuilds the dev client.--backend autois the default: a local device when the machine can run it, a runner when it cannot.--backend local|remoteforces one.How a borrowed device behaves:
HEADthat GitHub does not have, fails with the fixgit commitorgit push.expo-dev-clientleft out and the JS embedded, because it cannot reach Metro on the developer's machine. A JS edit reaches the app by commit, push,run, and the held session rebuilds..verify/runs/<run-id>/as for a local device.doctorwith the remote backend does git and REST reads only.doctor --livestarts one probe run and one short session to prove the rest.Things a reviewer may ask:
org_3KHungJxbvIscuSvy8oos5MHAli), configured fromsrc/core/instances/base.json. A spec that needs other settings declares them in a<name>.settings.jsonfile beside it. No golden spec does. The credential is a Platform API key from the environment or from a 1Password reference kept on the machine. A machine with none failsdoctorwith one fix line.integration/tests/expo-native(ci(e2e): Replace maestro with e2e in the expo native integration tests #10032) stay the regression gate. TheVerify Skill Testsjob inci.ymlruns the skill's unit tests and typecheck on a Linux runner, with no device and no secret, when a pull request changes the skill or a path its tests read.e2e0.18.0,@e2e-dev/mobile0.10.0, and@e2e-dev/github0.4.0 withnpm cifrom its own lockfile, and needs Node 24.8 or newer. It is outside the pnpm workspace, and the copied files are tested against those versions. Nothing is installed globally, on a developer's machine or on a runner.macos-26for iOS andubuntu-24.04for Android, GitHub-hosted labels that cost nothing for a public repository. They are slow.upwas ready after 26 and 27 minutes on iOS in two sessions and after 11 to 14 minutes on Android in three, nearly all of it the build.--runner blacksmith-6vcpu-macos-26or--runner blacksmith-8vcpu-ubuntu-2204leases one of the two Blacksmith labels this repo already uses, which is ready in 5 to 7 minutes and is billed by the minute. A session stops itself after 15 minutes without a call from the CLI and always after 60, anddownends it.macos-26took 24 minutes, which was one minute inside the limit the CLI gave a session to be ready. The last commit raises that limit from 25 to 40 minutes. No session has run on the free Mac since that commit. In one of the two sessions the firstrunalso lost its first test to a launch that took more than 30 seconds, and the secondrunpassed.workflow_dispatchand on a push toverify-remote/**, so only someone with write access. It has nopull_requesttrigger. A machine that may not dispatch pushes averify-remote/...branch whose commit message is the request, and thecleanupjob deletes that branch, which is the one use ofcontents: write.iPhone 17 Proon iOS 26.5. The workflow names that runtime, so a new runner image cannot change the iOS version, and a runner without it fails with the list of runtimes it has.integration/tests/expo-native/boot-ios-simulators.sh, the script the e2e tests from ci(e2e): Replace maestro with e2e in the expo native integration tests #10032 use, still sets the simulator's keyboard preferences.host.fillreads the focused field back after it types and types once more if the field is still empty. It cannot do that for a password field, which withholds its value, or on iOS for the fixture's own React Native fields, where an empty field reads as its placeholder. Those are typed once.+clerk_testusers. Four specs type a password or the test code424242and are taggedform-entry:custom-flow-sign-in/complete,custom-flow-sign-up/request-code,custom-flow-sign-up/complete, andnative-auth-view/complete. They ran in CI before the move toe2e0.18.0 (ci(repo): run the verify-clerk-expo device specs on pull requests #10090 has the runs). All four pass on iOS, and on Android the two that type an email code failed because the typed code did not reach the field. They have not run since, so thehost.fillthat reads the field back is unproven on them. Everything else runs with--skip form-entryand passes on local and borrowed devices, 7 tests on iOS and 6 on Android. Each sign-in flow is covered up to its code screen that way, and sign-up has no spec that runs without typing.known-bugand skipped unless--include known-bugis passed: the close button of an inlineAuthViewdoes not fireonDismiss.clerk.*accessibility identifiers.runthat starts while a run on the other platform is driving fails withDEVICE_BUSYand the fix to let that run finish. Before that was enforced, a ticket sign-in failed withresource_not_foundin two of four tries with both platforms running specs at once, and never with one at a time. The cause is not known.:app:createBundleReleaseJsAndAssets --rerunbeforeassembleRelease. Gradle treats that task as up to date when the only change is inside a linked workspace package, and without the rerun a rebuilt app kept the old JS.--backend autopicks the local emulator and the build then fails withUNSUPPORTEDand the fixrerun with --backend remote..prettierignoreskips the copied.tsfiles so the pre-commit hook does not reformat them.Checklist
pnpm testruns as expected.pnpm buildruns as expected.Type of change
🤖 Generated with Claude Code