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..853ca906ccb 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 test again, up to `n` more times, when an assertion failed, the test threw an error, or the test ran out of time. A test that e2e reports as a machine failure, such as an `ENGINE_FAILURE` when an `open` or a screenshot times out in agent-device, gets one attempt whatever `n` is. A driver error e2e reports as a test error, such as a stale node or an action that timed out, is retried like any other. 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`. @@ -139,7 +139,7 @@ test('the home UserButton opens the profile of the signed-in user', async ({ hos - `host.seedUser({ phone?, password? })` creates a `+clerk_test` user in the worktree's application. With `password: true` the user also has a password that the run generated for that user, and `user.password` holds it. Pass it to `host.fill`, as `host.fill(field, user.password!)`, and to nothing else. It prints as ``, `run` redacts its value from the logs it writes, and `attach` refuses a run whose files hold it. For a sign-up through the form, `host.newEmail()` returns a new address of this run, and `host.newPhone()` returns a 555-01xx number that no user holds. `host.newEmail()` calls nothing and records nothing. After the tests, `run` finds the user by the run's prefix in the address. - `host.launch({ signedInAs?, authMode?, keepStorage?, landsOn? })` relaunches the test app and waits up to 60 seconds for its home. Every launch opens on the home, and a spec taps from there to the screen it needs, as a user does. `authMode` is the mode of every AuthView the home opens: `signIn`, `signUp`, or `signInOrUp`. A launch starts with fresh storage unless `keepStorage` is true, so a launch with no `signedInAs` is signed out and lands on the home's `Sign in` button. `host.launch()` with no argument is that launch. A launch with `signedInAs` lands on the home, which must show that user's email and user ID. When the home should show anything else, pass its locator as `landsOn`. A launch that keeps storage and has no `signedInAs` needs it. `host.launch` fails at once when the test app shows its error screen, and the error has the message on that screen. - The home lists a link to each of the test app's other screens. Signed out, it has `Sign in` (`host.app.signIn`, AuthView in a modal), `Sign in with a logo` (the same modal with a React Native logo), `Sign in full screen` (`host.app.signInFullScreen`, AuthView with no close button), `Sign in full screen with a close button`, `Custom sign-in`, `Custom sign-up`, and `Native modules`. Signed in, it has `Embedded profile` (UserProfileView inline). `Token cache` shows in both states. `homeLinks(screen)` in `specs/native.ts` has the locator of each link that `host.app` does not hold. A new screen goes in `screens/destinations.ts` and `verify/VerifyHost.tsx` of the test app and in `homeLinks`. -- `host.expectSignedInAs(user, timeoutMs?)` waits for the home to show `Signed in as `, the user's ID, and a session ID. Pass the email alone for a user that the sign-up form created. `host.expectSignedOut(timeoutMs?)` waits for the home to show `Signed out` with no user ID and no session ID. Use them for the outcome of a flow that returns to the home. When one of these waits or the wait of `host.launch` runs out, its message says what the home showed at the last reading, how many passes and screen reads the wait made, how long the slowest read took, and the error of a read that failed. +- `host.expectSignedInAs(user, timeoutMs?)` waits for the home to show `Signed in as `, the user's ID, and a session ID. Pass the email alone for a user that the sign-up form created. `host.expectSignedOut(timeoutMs?)` waits for the home to show `Signed out` with no user ID and no session ID. Use them for the outcome of a flow that returns to the home. `host.expectVisible(locator, what, timeoutMs?)` is the same wait for an element of an SDK view, and its message names the element with `what`. On iOS, use it for the first element a spec waits for after it submits a password, because iOS may cover the app with its `Save Password?` sheet then. When one of these waits or the wait of `host.launch` runs out, its message says what the home showed at the last reading, how many passes and screen reads the wait made, how long the slowest read took, and the error of a read that failed. - `host.app` has the locators of the home: `signIn`, `signInFullScreen`, `signedOut`, `signedIn`, `userId`, `sessionId`, `signOut`, and `error` for the error screen. `host.runId` is the id of the run, for a password that must be new in each run. - `host.tap(locator)` is `locator.tap()` with the assertion timeout, and `host.fill(locator, text)` taps a field and types into it. When the first tap left no field focused, it taps the field once more before it types. Fill the text fields of the native views with `host.fill`. On iOS a native text field shows no text input until it has focus, so a plain `locator.fill()` on it fails with "no text input found at the provided coordinates to clear". After it types, `host.fill` reads the focused text input back. When the screen tree marks no text input as focused, as it does on iOS for a while after a slow snapshot, it reads the value that the node the locator names shows. It sends a long text sixteen characters to a command, because the iOS runner abandons a command after 30 seconds. If the input is still empty after two seconds and the screen shows no text that it did not show before the typing, `host.fill` taps it and types once more, and it fails with "the text never reached the field" if the input is empty again. A screen that shows new text has taken the typed text and moved on, as a code screen does when its code is complete, so `host.fill` types nothing more there. For this it compares the letters of the screen's text only, so the seconds that a code screen counts down beside `Resend` are not new text. If the input holds something other than the text, `host.fill` replaces the input's contents with the text once, and it fails with "the field does not hold the typed text" if the input is still wrong. It compares letters and digits only, so the formatting a field adds does not count. A password field withholds its value, so a password is typed once and is not confirmed. When the iOS runner refuses a tap, a typing command, or a screenshot because it is still finishing an earlier command, `host.tap`, `host.fill`, and `host.screenshot` wait five seconds and try the step again, up to six times, and then fail with the refusal. After a refused typing command, `host.fill` replaces the input's contents with the text and does not type that command again, so characters that did land are not typed twice. On iOS an empty React Native field of the test app reads as its placeholder, which `host.fill` treats as any other value that is not the text. - `host.screenshot(label)` writes `screenshots/