Skip to content

feat(testing): support per-instance testing tokens in setupClerkTestingToken#8833

Open
jacekradko wants to merge 3 commits into
mainfrom
jacek/per-instance-testing-tokens
Open

feat(testing): support per-instance testing tokens in setupClerkTestingToken#8833
jacekradko wants to merge 3 commits into
mainfrom
jacek/per-instance-testing-tokens

Conversation

@jacekradko

@jacekradko jacekradko commented Jun 11, 2026

Copy link
Copy Markdown
Member

The testing-token captcha bypass has been single-instance: clerkSetup last-writer-wins on CLERK_FAPI/CLERK_TESTING_TOKEN, the route guard was a per-context WeakSet so a second registration for a different host silently no-oped, and the page objects always registered against the global host. Any test running against an app other than the last-initialized one got no bypass at all, which is how legal-consent spent a week red against the captcha-enabled staging instance (run 27344100979).

setupClerkTestingToken now accepts options.testingToken (string or lazy provider) and registers one route per (context, frontendApiUrl) pair, and the integration harness passes per-app options derived from each app's own publishable key, minting one token per instance through the existing retry-wrapped clerkClient. Tests need no changes; the auto-setup on navigation picks it up.

Verified locally with the two-app scenario that reproduces the bug (E2E_APP_ID=react.vite.withLegalConsent,react.vite.withEmailCodes, global env pointing at the second app): on main the legal-consent traffic shows 0 token-carrying requests on its own FAPI host; on this branch every FAPI request is intercepted and reissued with the instance's token.

Summary by CodeRabbit

  • New Features

    • Pass testing tokens via options (takes precedence over env var) and support lazy/async token providers.
    • Support multiple Clerk instances in the same browser context with distinct testing tokens.
    • Page-object helpers and test utilities accept testing-token configuration and forward it to setup.
  • Behavior Changes

    • Playwright setup now errors when frontend API URL cannot be resolved on repeated calls (no longer silently ignored).
  • Cypress

    • Cypress setup also accepts testingToken via options.

@vercel

vercel Bot commented Jun 11, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
clerk-js-sandbox Ready Ready Preview, Comment Jun 12, 2026 3:06am
swingset Ready Ready Preview, Comment Jun 12, 2026 3:06am

Request Review

@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This PR extends Clerk's testing helpers to support instance-specific bot-protection bypass tokens for Playwright and integration tests. The core enhancement allows setupClerkTestingToken to accept lazy-resolved testing tokens (string or async provider) with per-frontend-API-URL deduplication, enabling multiple Clerk instances to coexist in the same browser context. The feature is wired through page object helpers and test utilities, with comprehensive test coverage and Cypress parity.

Changes

Per-instance testing token support

Layer / File(s) Summary
Type contracts and core Playwright implementation
.changeset/eighty-husky-fly.md, packages/testing/src/common/types.ts, packages/testing/src/playwright/setupClerkTestingToken.ts, packages/testing/src/playwright/index.ts, packages/testing/src/playwright/unstable/index.ts
Adds TestingTokenProvider and allows testingToken as `string
Playwright tests and mocks
packages/testing/src/playwright/__tests__/setupClerkTestingToken.test.ts
Refactors mock BrowserContext to support multiple route registrations and adds tests for per-URL deduplication, rollback/retry semantics, testingToken precedence, async provider behavior, env fallback, and per-registration token attachment.
Page object helpers threaded with testingTokenOptions
packages/testing/src/playwright/unstable/page-objects/index.ts, packages/testing/src/playwright/unstable/page-objects/app.ts, packages/testing/src/playwright/unstable/page-objects/testingToken.ts
Adds testingTokenOptions parameter and forwards it through createPageObjects, createAppPageObject, and createTestingTokenPageObject, so page objects pass options into setupClerkTestingToken.
Test utilities with instance-specific token minting
integration/testUtils/index.ts
Adds a per-publishable-key testingTokenCache and getTestingToken lazy minter that caches in-flight/created promises, warns and evicts on mint failure, and returns undefined to allow env fallback. createTestingTokenOptions builds PlaywrightSetupClerkTestingTokenOptions for development keys and createTestUtils wires testingTokenOptions into page-object creation flows.
Cypress testing token option support
packages/testing/src/cypress/setupClerkTestingToken.ts
Updates JSDoc and logic to prefer params.options.testingToken over Cypress.env('CLERK_TESTING_TOKEN'), enabling per-instance string testing tokens in Cypress setups.
Playwright helpers type updates
packages/testing/src/playwright/helpers.ts
Retypes setupClerkTestingTokenOptions fields in Playwright sign-in param types from SetupClerkTestingTokenOptions to PlaywrightSetupClerkTestingTokenOptions and adjusts imports.

Sequence Diagram(s)

sequenceDiagram
  participant TestRunner
  participant setupClerkTestingToken
  participant BrowserContext
  participant TestingTokenProvider
  participant ClerkFrontendAPI

  TestRunner->>setupClerkTestingToken: setup({ page, options })
  setupClerkTestingToken->>BrowserContext: resolve frontendApiUrl and check (context, frontendApiUrl)
  alt first registration for frontendApiUrl
    setupClerkTestingToken->>TestingTokenProvider: resolve testingToken once
    TestingTokenProvider-->>setupClerkTestingToken: token or undefined
    setupClerkTestingToken->>BrowserContext: context.route(frontendApiUrl pattern)
    BrowserContext->>ClerkFrontendAPI: forward request with _clerk_testing_token
  else already registered for frontendApiUrl
    setupClerkTestingToken-->>TestRunner: no-op
  end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • wobsoriano
  • mauricioabreu
  • prestonwebdev

Poem

🐰 I nibble tokens, one per frontend name,
Minted lazy, cached, never twice the same.
Page objects carry them through contexts wide,
Multiple Clerks now safely coincide.
Hop, mint, route — the tests dance in frame.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: adding support for per-instance testing tokens in setupClerkTestingToken, which is the core objective of this PR.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (3)
packages/testing/src/cypress/setupClerkTestingToken.ts (2)

33-33: 💤 Low value

Consider using nullish coalescing for stricter precedence semantics.

Using || will fall through to the environment variable if testingToken is any falsy value (including empty string). For stricter "explicit parameter takes precedence" semantics, ?? (nullish coalescing) would only fall through for null or undefined.

♻️ Proposed refinement
-    const testingToken = params?.options?.testingToken || Cypress.env('CLERK_TESTING_TOKEN');
+    const testingToken = params?.options?.testingToken ?? Cypress.env('CLERK_TESTING_TOKEN');
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/testing/src/cypress/setupClerkTestingToken.ts` at line 33, Replace
the loose OR fallback with nullish coalescing so an explicit falsy testingToken
parameter (e.g., empty string) is honored: change the assignment to use
testingToken = params?.options?.testingToken ??
Cypress.env('CLERK_TESTING_TOKEN') in the setupClerkTestingToken logic so only
null or undefined trigger reading the CLERK_TESTING_TOKEN env var.

25-25: ⚡ Quick win

Add explicit return type annotation.

This exported public API function lacks an explicit return type. As per coding guidelines, public APIs should always define explicit return types for clarity and type safety.

♻️ Proposed fix
-export const setupClerkTestingToken = (params?: SetupClerkTestingTokenParams) => {
+export const setupClerkTestingToken = (params?: SetupClerkTestingTokenParams): void => {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/testing/src/cypress/setupClerkTestingToken.ts` at line 25, The
exported public API function setupClerkTestingToken lacks an explicit return
type; update its signature to include a concrete return type that matches the
implementation (for example : void or : Promise<void> if async) by changing
"export const setupClerkTestingToken = (params?: SetupClerkTestingTokenParams)"
to include the appropriate return annotation (e.g., "export const
setupClerkTestingToken = (params?: SetupClerkTestingTokenParams): void" or ":
Promise<void>") so the public API has an explicit, type-safe return.

Source: Coding guidelines

packages/testing/src/playwright/unstable/page-objects/index.ts (1)

26-36: ⚡ Quick win

Document the new testingTokenOptions parameter on this public API.

createPageObjects now exposes new behavior via testingTokenOptions, but the public API contract in this file is still undocumented. Please add/update JSDoc for the new option and expected precedence behavior to avoid reference-doc drift.

As per coding guidelines, public APIs under packages/**/src/**/*.{ts,tsx} should maintain comprehensive JSDoc, and public/reference-facing API changes should keep generated docs aligned (with Docs-team awareness when needed).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/testing/src/playwright/unstable/page-objects/index.ts` around lines
26 - 36, Add/update JSDoc on the exported createPageObjects function to document
the new testingTokenOptions parameter and its behavior: describe the type
(PlaywrightSetupClerkTestingTokenOptions), defaulting behavior when
testingTokenOptions is omitted, and the precedence rules vs useTestingToken and
baseURL (e.g., when testingTokenOptions is provided it takes precedence for
token setup, otherwise useTestingToken controls token injection). Include a
short summary of the function purpose, annotate each parameter (page,
useTestingToken, baseURL, testingTokenOptions) and the return value, and ensure
the docblock follows the project's public API JSDoc conventions used elsewhere
in packages/*/src so generated docs remain accurate.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@integration/testUtils/index.ts`:
- Around line 39-53: The current logic caches a failed minting result
(undefined) causing permanent fallback; change it so failures are not
permanently cached by either (a) only inserting into testingTokenCache after a
successful createTestingToken() resolves (reference testingTokenCache,
publishableKey, clerkClient.testingTokens.createTestingToken), or (b) if you
must cache the promise, attach a .catch handler that removes
testingTokenCache.delete(publishableKey) before rethrowing/returning undefined
so transient errors don’t remain cached; ensure the returned promise behavior is
preserved and logs continue to run on errors.

---

Nitpick comments:
In `@packages/testing/src/cypress/setupClerkTestingToken.ts`:
- Line 33: Replace the loose OR fallback with nullish coalescing so an explicit
falsy testingToken parameter (e.g., empty string) is honored: change the
assignment to use testingToken = params?.options?.testingToken ??
Cypress.env('CLERK_TESTING_TOKEN') in the setupClerkTestingToken logic so only
null or undefined trigger reading the CLERK_TESTING_TOKEN env var.
- Line 25: The exported public API function setupClerkTestingToken lacks an
explicit return type; update its signature to include a concrete return type
that matches the implementation (for example : void or : Promise<void> if async)
by changing "export const setupClerkTestingToken = (params?:
SetupClerkTestingTokenParams)" to include the appropriate return annotation
(e.g., "export const setupClerkTestingToken = (params?:
SetupClerkTestingTokenParams): void" or ": Promise<void>") so the public API has
an explicit, type-safe return.

In `@packages/testing/src/playwright/unstable/page-objects/index.ts`:
- Around line 26-36: Add/update JSDoc on the exported createPageObjects function
to document the new testingTokenOptions parameter and its behavior: describe the
type (PlaywrightSetupClerkTestingTokenOptions), defaulting behavior when
testingTokenOptions is omitted, and the precedence rules vs useTestingToken and
baseURL (e.g., when testingTokenOptions is provided it takes precedence for
token setup, otherwise useTestingToken controls token injection). Include a
short summary of the function purpose, annotate each parameter (page,
useTestingToken, baseURL, testingTokenOptions) and the return value, and ensure
the docblock follows the project's public API JSDoc conventions used elsewhere
in packages/*/src so generated docs remain accurate.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Repository UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: a6997c84-afdd-438a-bc9a-dffde0a53f76

📥 Commits

Reviewing files that changed from the base of the PR and between d0ed42f and 705d472.

📒 Files selected for processing (11)
  • .changeset/eighty-husky-fly.md
  • integration/testUtils/index.ts
  • packages/testing/src/common/types.ts
  • packages/testing/src/cypress/setupClerkTestingToken.ts
  • packages/testing/src/playwright/__tests__/setupClerkTestingToken.test.ts
  • packages/testing/src/playwright/index.ts
  • packages/testing/src/playwright/setupClerkTestingToken.ts
  • packages/testing/src/playwright/unstable/index.ts
  • packages/testing/src/playwright/unstable/page-objects/app.ts
  • packages/testing/src/playwright/unstable/page-objects/index.ts
  • packages/testing/src/playwright/unstable/page-objects/testingToken.ts

Comment thread integration/testUtils/index.ts
@github-actions

github-actions Bot commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

API Changes Report

Generated by Break Check on 2026-06-12T03:07:34.331Z

Summary

Metric Count
Packages analyzed 19
Packages with changes 1
🔴 Breaking changes 0
🟡 Non-breaking changes 2
🟢 Additions 4

🤖 This report was reviewed by claude-sonnet-4-6.

Note
Break Check could not snapshot 3 subpaths; the diff below excludes them.

  • @clerk/astro ./env: Internal Error: Unable to determine module for: /home/runner/_work/javascript/javascript/packages/astro/env.d.ts You have encountered a software defect. Please consider reporting the issue to the maintainers of this application.
  • @clerk/shared ./cookie: Internal Error: Unable to follow symbol for "Cookies" You have encountered a software defect. Please consider reporting the issue to the maintainers of this application.
  • @clerk/testing ./cypress: Symbol not found for identifier: Cypress

@clerk/testing

Current version: 2.1.0
Recommended bump: MINOR → 2.2.0

Subpath ./playwright

🟢 Additions (2)

Added: PlaywrightSetupClerkTestingTokenOptions
+ export type PlaywrightSetupClerkTestingTokenOptions = Omit<SetupClerkTestingTokenOptions, 'testingToken'> & {
+     testingToken?: string | TestingTokenProvider;
+ };

Added type alias PlaywrightSetupClerkTestingTokenOptions

Added: TestingTokenProvider
+ export type TestingTokenProvider = () => string | undefined | Promise<string | undefined>;

Added type alias TestingTokenProvider

Subpath ./playwright/unstable

🟡 Non-breaking Changes (2)

Modified: createAppPageObject
  createAppPageObject: (testArgs: {
      page: Page;
      useTestingToken?: boolean;
+     testingTokenOptions?: PlaywrightSetupClerkTestingTokenOptions;
  }, app: {
      baseURL?: string;
  }) => Page & {
// ... 14 unchanged lines elided ...

Static analyzer: Breaking change in function createAppPageObject: Parameter testArgs type changed: {page:import("playwright-core").Page;useTestingToken?:boolean;}{page:import("playwright-core").Page;useTestingToken?:boolean;testingTokenOptions?:import("@clerk/testing").PlaywrightSetupClerkTestingTokenOptions;}

🤖 AI review (reclassified as non-breaking) (97%): The change only adds a new optional property testingTokenOptions? to the testArgs parameter object; existing callers omitting this property continue to compile and run correctly, and widening an input type with an optional field is non-breaking.

Modified: createPageObjects
// ... 1 unchanged line elided ...
      page: Page;
      useTestingToken?: boolean;
      baseURL?: string;
+     testingTokenOptions?: PlaywrightSetupClerkTestingTokenOptions;
  }) => {
      page: Page & {
          goToAppHome: () => Promise<void>;
// ... 656 unchanged lines elided ...

Static analyzer: Breaking change in function createPageObjects: Parameter input type changed: {page:import("playwright-core").Page;useTestingToken?:boolean;baseURL?:string;}{page:import("playwright-core").Page;useTestingToken?:boolean;baseURL?:string;testingTokenOptions?:import("@clerk/testing").PlaywrightSetupClerkTestingTokenOptions;}

🤖 AI review (reclassified as non-breaking) (97%): The change only adds a new optional property testingTokenOptions? to the input parameter object; existing callers that do not pass this property are unaffected, making this a safe input-type widening.

🟢 Additions (2)

Added: PlaywrightSetupClerkTestingTokenOptions
+ export type PlaywrightSetupClerkTestingTokenOptions = Omit<SetupClerkTestingTokenOptions, 'testingToken'> & {
+     testingToken?: string | TestingTokenProvider;
+ };

Added type alias PlaywrightSetupClerkTestingTokenOptions

Added: TestingTokenProvider
+ export type TestingTokenProvider = () => string | undefined | Promise<string | undefined>;

Added type alias TestingTokenProvider


Report generated by Break Check

Last ran on 874068f.

@changeset-bot

changeset-bot Bot commented Jun 11, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 874068f

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@clerk/testing Minor

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

@pkg-pr-new

pkg-pr-new Bot commented Jun 11, 2026

Copy link
Copy Markdown

Open in StackBlitz

@clerk/astro

npm i https://pkg.pr.new/@clerk/astro@8833

@clerk/backend

npm i https://pkg.pr.new/@clerk/backend@8833

@clerk/chrome-extension

npm i https://pkg.pr.new/@clerk/chrome-extension@8833

@clerk/clerk-js

npm i https://pkg.pr.new/@clerk/clerk-js@8833

@clerk/expo

npm i https://pkg.pr.new/@clerk/expo@8833

@clerk/expo-passkeys

npm i https://pkg.pr.new/@clerk/expo-passkeys@8833

@clerk/express

npm i https://pkg.pr.new/@clerk/express@8833

@clerk/fastify

npm i https://pkg.pr.new/@clerk/fastify@8833

@clerk/hono

npm i https://pkg.pr.new/@clerk/hono@8833

@clerk/localizations

npm i https://pkg.pr.new/@clerk/localizations@8833

@clerk/nextjs

npm i https://pkg.pr.new/@clerk/nextjs@8833

@clerk/nuxt

npm i https://pkg.pr.new/@clerk/nuxt@8833

@clerk/react

npm i https://pkg.pr.new/@clerk/react@8833

@clerk/react-router

npm i https://pkg.pr.new/@clerk/react-router@8833

@clerk/shared

npm i https://pkg.pr.new/@clerk/shared@8833

@clerk/tanstack-react-start

npm i https://pkg.pr.new/@clerk/tanstack-react-start@8833

@clerk/testing

npm i https://pkg.pr.new/@clerk/testing@8833

@clerk/ui

npm i https://pkg.pr.new/@clerk/ui@8833

@clerk/upgrade

npm i https://pkg.pr.new/@clerk/upgrade@8833

@clerk/vue

npm i https://pkg.pr.new/@clerk/vue@8833

commit: 874068f

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/testing/src/playwright/helpers.ts (1)

26-64: ⚡ Quick win

Consider documenting the setupClerkTestingTokenOptions parameter.

The JSDoc for clerk.signIn does not document the setupClerkTestingTokenOptions parameter. Since this PR introduces lazy testing token providers (a significant new capability for multi-instance scenarios), consider adding a @param entry explaining when and how to use this option, especially the new lazy provider support.

Example:

 * `@param` opts.setupClerkTestingTokenOptions - Optional token setup configuration.
 *   Pass `{ testingToken: string | () => Promise<string> }` to override the default
 *   CLERK_TESTING_TOKEN for per-instance test scenarios.

As per coding guidelines, public API JSDoc is used to generate customer-facing reference documentation, so documenting this parameter will help users understand the new multi-instance testing capabilities.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/testing/src/playwright/helpers.ts` around lines 26 - 64, Add a
`@param` entry to the JSDoc for clerk.signIn documenting the
setupClerkTestingTokenOptions parameter: describe that
opts.setupClerkTestingTokenOptions is optional, accepts { testingToken: string |
() => Promise<string> } (or similar) to override the default
CLERK_TESTING_TOKEN, and explain when to use it (per-instance test scenarios /
lazy token providers) and that it works with the helper’s internal
setupClerkTestingToken call to enable multi-instance testing; reference the
symbol name setupClerkTestingTokenOptions so readers can find usage in the
signIn helper.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@packages/testing/src/playwright/helpers.ts`:
- Around line 26-64: Add a `@param` entry to the JSDoc for clerk.signIn
documenting the setupClerkTestingTokenOptions parameter: describe that
opts.setupClerkTestingTokenOptions is optional, accepts { testingToken: string |
() => Promise<string> } (or similar) to override the default
CLERK_TESTING_TOKEN, and explain when to use it (per-instance test scenarios /
lazy token providers) and that it works with the helper’s internal
setupClerkTestingToken call to enable multi-instance testing; reference the
symbol name setupClerkTestingTokenOptions so readers can find usage in the
signIn helper.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository YAML (base), Repository UI (inherited)

Review profile: CHILL

Plan: Pro

Run ID: 86bc96d8-be47-4477-be60-3d11b40d9dd7

📥 Commits

Reviewing files that changed from the base of the PR and between 3f3d199 and 874068f.

📒 Files selected for processing (2)
  • .changeset/eighty-husky-fly.md
  • packages/testing/src/playwright/helpers.ts
✅ Files skipped from review due to trivial changes (1)
  • .changeset/eighty-husky-fly.md

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant