All examples

Testing Clerk's iOS SDK with e2e

Sign-in, sign-up, MFA, and organization flows of Clerk's iOS SDK in a SwiftUI host app, on an iOS simulator.

Community examples aren't actively verified by the e2e team so exercise extra caution when running.

The tests in specs/ are an ordinary e2e project. e2e.config.ts and the files under specs/ run with npx e2e run when the environment names three things: a device, a build of E2EHost, and a Clerk development instance.

The CLI in bin/ and src/ sits on top of that project. It makes those three things for a run, and it adds what a pull request needs. The tests never import the CLI. test/seam.test.ts fails when a file under specs/ or e2e.config.ts names a path outside specs/.

To prove a change, use the CLI. .claude/skills/verify-clerk-ios/SKILL.md has the steps. Run a test by hand when you want e2e alone, for example to work on a test with e2e's own flags.

Run a test by hand

You need Node 24.8 or newer on 24, Xcode and a booted iOS simulator, and the publishable key and secret key of a Clerk development instance.

Terminal
cd e2e-tests
npm ci
xcodebuild build -quiet -workspace ../Clerk.xcworkspace -scheme E2EHost -configuration Debug \
  -destination 'generic/platform=iOS Simulator' -derivedDataPath /tmp/e2ehost
xcrun simctl list devices booted
export CLERK_E2E_DEVICE=<the id of a booted simulator, from the list above>
export CLERK_E2E_APP_PATH=/tmp/e2ehost/Build/Products/Debug-iphonesimulator/E2EHost.app
export CLERK_PUBLISHABLE_KEY=pk_test_...
export CLERK_SECRET_KEY=sk_test_...
npx e2e run specs/features/sign-in-email-code/complete.e2e.ts

--video on, --retries, --grep, and --output are e2e's own flags. e2e writes its report and its screenshots to .e2e/, which git ignores.

SettingNeededWhat it is
CLERK_E2E_DEVICEyesThe id of a booted simulator, as xcrun simctl list devices booted prints it. A name is not accepted.
CLERK_E2E_APP_PATHnoA build of E2EHost for the simulator. The first launch of a run installs it. Leave it unset when the app is already on the simulator.
CLERK_PUBLISHABLE_KEYyesThe pk_test_ key of a development instance.
CLERK_SECRET_KEYyesThe sk_test_ key of the same instance. The tests create users and sign-in tickets with it. A pk_live_ or sk_live_ key is refused.

The instance

The tests expect the settings in src/core/instances/base.json of @clerk/internal-mobile-e2e, which the CLI puts on the application it creates. On an instance with those settings, every test file that has no settings file beside it passes by hand.

An instance left at Clerk's defaults passes only part of the suite. The tests that sign in with a ticket, or sign up with an email address and a password, pass. The tests that need a phone number, an email code at sign-in, organizations, or a second account on the device fail, because a new instance has none of those turned on.

Every run creates users with addresses like maya.7c1e+clerk_test@example.com and never deletes them. The address is a first name and the last four characters of the run id, here r20261002-141210-7c1e. No two tests of a run on one machine get the same name. A development instance holds 100 users. Use an instance that you can empty.

Test files that need the CLI

These test files have a .settings.json file beside them, and pass only on an instance with those settings:

  • specs/features/session-tasks/choose-organization.e2e.ts
  • specs/features/session-tasks/complete-setup-mfa.e2e.ts
  • specs/features/session-tasks/setup-mfa.e2e.ts
  • specs/features/session-tasks/sign-up-setup-mfa.e2e.ts

The CLI's run puts the instance on the declared settings before the file starts, and e2e alone does not. Run them through the CLI, for example e2e-tests/bin/control-clerk-ios run session-tasks from the repo root.

What a run by hand does not do

  • It creates no Clerk application, changes no instance settings, and replaces no instance that is filling up.
  • It leases and locks no device. Run one test process per device at a time.
  • It does not rebuild the app when the sources change.
  • It records no video of the whole run. --video on records one per attempt.
  • It keeps no sealed run directory, scans nothing for secrets, redacts no log, and posts nothing to a pull request.
  • It leaves the agent-device daemon that e2e started running, and with it the iOS runner process that the daemon keeps. npx agent-device daemon stop --clean stops both. Without --clean the runner stays.

Layout

  • e2e.config.ts and specs/ are the e2e project. specs/features/ has the tests, specs/fixtures.ts configures the shared host fixture they use, and specs/app.ts names the test app. The fixture and its support code come from @clerk/internal-mobile-e2e, whose specs/support/inputs.ts is the only file that reads the settings above.
  • bin/ and src/ are the iOS side of the CLI. The shared CLI comes from @clerk/internal-mobile-e2e, pinned to a commit in package.json. Under the CLI the test process gets the same settings, with one difference: in place of the secret key it gets the address and token of a stand-in that the CLI runs on this machine. The stand-in forwards the three Backend API calls the tests make, for this run's test users only, so the test process never holds the key.
  • test/ has the unit tests of the iOS side. npm test runs them, with no device, key, or network.
  • To change the shared core, follow the README of @clerk/internal-mobile-e2e, then bump the commit pin here.