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 .changeset/expo-native-update-required-spec.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
6 changes: 4 additions & 2 deletions .claude/skills/verify-clerk-expo/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 `<secret:password>`, `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()` claims a first name for the run on this machine, as `maya.7c1e+clerk_test@example.com`, and calls nothing. After the tests, `run` finds the user by the run tag, the last four characters of the run id, 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, user ID, and a session 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.
- `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, unless a minimum app version blocks it, 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` and no `keepStorage` is signed out and lands on the home's `Sign in` button, and one with `keepStorage: true` keeps the session of the launch before it. `host.launch()` with no argument is that launch. A launch with `signedInAs` lands on the home, which must show that user's email, user ID, and a session ID. When the home should show anything else, pass its locator as `landsOn`. A launch that keeps storage and has no `signedInAs` needs it, and so does one that a minimum app version blocks, which shows the update screen over the home. `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 <email>`, 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 sign-up password, fill the field with `runPassword(host.runId)` from `@clerk/internal-mobile-e2e/specs/support/clerk`, two words and four digits such as `Otter-Lamp-3917`, the same in every test of the run. For an organization name that a spec types into a form, use `host.newOrganizationName()`, a word and the run tag that no other test of the run gets, as `Maple 7c1e`.
Expand All @@ -158,6 +158,8 @@ On iOS, locate nodes of the native views with the SDK's identifiers, such as `sc

Every test starts on the standard settings. A test that needs other settings calls `host.useSettings({ config, environment? })`, and the CLI puts every leaf it changed back when the test ends. [Test instances](references/instances.md) has the call, the restore, and the leaves that tests often change. To see which settings are on or off, run `integration/expo-native/bin/control-clerk-expo settings <prefix>`, such as `settings auth_multi_factor`.

A test of the update screen calls `host.useMinimumAppVersion({ minimumVersion, updateUrl })`, which registers the app under test with the application and sets its minimum. The CLI removes the minimum when the test ends, pass or fail. [Test instances](references/instances.md) has the details.

### Check your work

The specs under `specs/features/<feature>/` are committed and cover the feature map in `features/`, which starts at `features/README.md`. Prove your own change, on both platforms when the change is not specific to one. CI runs every committed spec only on the Version packages pull request (`changeset-release/main`), before each release, and never on your pull request (`.github/workflows/verify-e2e.yml`). When your change is to behavior a committed spec already covers, that spec is your proof: run it. A spec covers a behavior only when it asserts on it. A spec that passes through the screen you changed without checking what you changed is not your proof. Run another feature's specs too when you changed code that feature shares. For new work:
Expand All @@ -184,7 +186,7 @@ Every `run` writes `.verify/runs/<run-id>/` and prints its path.

| File | What it holds |
| ------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `run.json` | The record: `results` per spec, `platform`, `backend`, `gitHead`, `dirty`, `build`, `device`, `identities`, `settings` (one entry per `host.useSettings` call), and `tainted` |
| `run.json` | The record: `results` per spec, `platform`, `backend`, `gitHead`, `dirty`, `build`, `device`, `identities`, `settings` (one entry per `host.useSettings` or `host.useMinimumAppVersion` call), and `tainted` |
| `video.mp4` | The run, from the first `host.launch` to the end, recorded by `simctl io recordVideo` on iOS or `adb shell screenrecord` on Android. Just before that launch, `run` closes the app and starts the recorder, so the video opens on the device's home screen for a moment and then on the app being launched. A run in which no test calls `host.launch` has no video, and `run` prints a `video` line that says so |
| `evidence-marks.jsonl` | When each `host.evidence` block started and ended, and when and where each `host.tap` and `host.fill` tapped, in a run that records evidence. `run.json` keeps them as `evidenceSpans` and `evidenceTaps`, in seconds of the video |
| `video-cut.mp4` | What `attach --video` last posted for this run: the marked part of `video.mp4`. `video.mp4` stays as recorded |
Expand Down
7 changes: 4 additions & 3 deletions .claude/skills/verify-clerk-expo/features/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ This directory is the maintained source for verifying the user-facing behavior o

`SKILL.md` under Launch has the setup and the commands. These facts hold for every recipe here, on both platforms.

- The standard settings have every auth method and signed-in feature that these recipes use. The `settings` check of `doctor` compares the live instance with the standard file once the worktree holds an application.
- The standard settings have every auth method and signed-in feature that these recipes use, except a minimum app version, which a spec sets with `host.useMinimumAppVersion`. The `settings` check of `doctor` compares the live instance with the standard file once the worktree holds an application.
- Every launch gets a new `verifyStorageScope`, so no spec inherits a session from another spec. A launch with `keepStorage: true` reuses the scope of the launch before it in the same test.
- `down` deletes this worktree's application, and with it every user and organization a run created, including users created through the sign-up form. `down --platform <p>` keeps the application while the other platform still holds a lease in this worktree. It deletes the application when the other platform holds no lease.

Expand Down Expand Up @@ -47,7 +47,7 @@ The iOS identifiers come from `Sources/ClerkKitUI/Components/Auth/ClerkAccessibi

## Feature entry contract

Each feature file starts with an H1 title and one paragraph describing the user-visible behavior, then exactly four H2 sections in this order: `Sub-features`, `How to get to it (user POV)`, `Driving it with verify`, and `Gotchas`. `Driving it with verify` starts with `Preconditions:` and names the specs that prove each sub-feature. A feature file lists only entry points that a spec drives.
Each feature file starts with an H1 title and one paragraph describing the user-visible behavior, then these four required H2 sections in this order: `Sub-features`, `How to get to it (user POV)`, `Driving it with verify`, and `Gotchas`. `Driving it with verify` starts with `Preconditions:` and names the specs that prove each sub-feature. A feature file lists only entry points that a spec drives. A feature with states that no spec can reach adds an optional fifth H2, `States a spec cannot reach`, before `Gotchas`, which names each state, where the app decides it, and what it takes.

## Features

Expand All @@ -58,7 +58,8 @@ Each feature file starts with an H1 title and one paragraph describing the user-
- [Token cache persistence](./token-cache-persistence.md) covers restoring the same session after a relaunch.
- [Native and JS session sync](./native-js-sync.md) covers a native sign-out and a native password sign-in reaching `useAuth`, `useUser`, and `useSession`, that sign-in surviving a relaunch, a second native sign-in in one process, a ticket sign-in reaching the native profile, and a JS sign-out clearing the hooks.
- [Native modules](./native-modules.md) covers `useSignInWithGoogle` opening the native Google sign-in, a cancel of it on iOS, and on Android when back closes Google's page, and `useBiometricCredentials` reporting what its native module found on the device.
- [Update required](./update-required.md) covers the update screen for a signed-in and a signed-out user on a build below the minimum, the same session returning once the minimum is lowered, and a build at the minimum, on both platforms, and it lists the states no spec reaches.

Not mapped yet: SSO through `useSSO` (no real OAuth on simulators), sign-in and sign-up in Account Portal through `useHostedAuth` from `@clerk/expo/hosted-auth`, which opens a browser that no spec drives and the test app has no button for (`packages/expo/src/hooks/useHostedAuth.ts`), a Google or Apple native sign-in with a real account, passkeys, enrolling or using a biometric (no associated domains or Secure Enclave on the simulator), session tasks in the native views, and the update screen that `<ClerkProvider>` shows over the app on iOS and Android when the instance requires a newer version of it (`packages/expo/src/update-required/`, or the native clerk-ios or clerk-android screen when the app does not install `expo-application`; it needs a minimum version for `com.clerk.exponativebuildfixture` in the instance's native app settings, which the standard settings do not set and a settings file cannot declare).
Not mapped yet: SSO through `useSSO` (no real OAuth on simulators), sign-in and sign-up in Account Portal through `useHostedAuth` from `@clerk/expo/hosted-auth`, which opens a browser that no spec drives and the test app has no button for (`packages/expo/src/hooks/useHostedAuth.ts`), a Google or Apple native sign-in with a real account, passkeys, enrolling or using a biometric (no associated domains or Secure Enclave on the simulator), session tasks in the native views.

Organizations have no page, because a user of the test app has no way to reach organization UI. This holds for the native components only. `@clerk/expo/web` exports the clerk-js OrganizationProfile, OrganizationSwitcher, OrganizationList, and CreateOrganization for Expo web, but the test app runs native only. `@clerk/expo-native-components` exports AuthView, UserButton, and UserProfileView and no OrganizationSwitcher or OrganizationProfileView. The profile that the UserButton opens lists `Manage account`, `Security`, `Add account`, and `Sign out`, with `Switch account` once a second session exists, and no organization row, in the pinned clerk-ios and clerk-android releases, though the standard settings turn organizations on. The one organization screen that a native view shows is a session task in AuthView, choose or create an organization, which needs `force_organization_selection` on. The harness seeds users, sign-in tickets, and, through `host.seedOrganization`, organizations whose members hold one of the harness roles. It cannot seed invitations, so no spec can reach a choose-organization task that lists an invitation. Today such a state can be shown only with a local patch that is not committed, and the description of the pull request that shows it says so.
Loading
Loading