Skip to content

fix(testing): fix concurrent Playwright workers timing out on signIn()#8173

Open
jacekradko wants to merge 12 commits intomainfrom
jacek/fix-testing-concurrent-workers
Open

fix(testing): fix concurrent Playwright workers timing out on signIn()#8173
jacekradko wants to merge 12 commits intomainfrom
jacek/fix-testing-concurrent-workers

Conversation

@jacekradko
Copy link
Copy Markdown
Member

@jacekradko jacekradko commented Mar 27, 2026

Summary

Fixes setupClerkTestingToken() causing signIn() timeouts when running Playwright with concurrent workers (--workers=2+).

Root cause: setupClerkTestingToken() is called from signIn(), goToAppHome(), and goToRelative(). Each call registered a new browserContext.route() handler on the same context. With N stacked handlers, each FAPI request triggered N real HTTP requests via route.fetch(), amplifying rate-limit pressure. When FAPI returned 429/5xx, the catch block silently called route.continue(), swallowing the error. ClerkJS never received a valid response, never reached loaded state, and waitForFunction() timed out.

Changes:

  • De-duplicate route handler registration -- a module-level WeakSet<BrowserContext> tracks contexts that already have a handler registered and returns early on subsequent calls. The WeakSet is checked after parameter validation so an invalid call can't poison the set.
  • Retry with exponential backoff -- the route handler retries up to 3 times on transient FAPI errors (429, 502, 503, 504) and network errors (ECONNREFUSED, ECONNRESET, etc.) with exponential backoff (500ms base, 2x multiplier) plus random jitter (0-250ms) to avoid thundering herd across workers.
  • Surface errors instead of swallowing -- on exhausted retries, logs a console.warn with the request URL and status/error. For retryable HTTP statuses, fulfills with the raw error response so ClerkJS can handle it. For network errors, falls back to route.continue().
  • Debug mode -- set CLERK_TESTING_DEBUG=1 to log retry attempts and duplicate setup skips.
  • Unit tests -- added comprehensive tests covering de-duplication (same context, different contexts, page-derived context), request mutation (testing token, captcha_bypass overrides), retry logic (each retryable status code, network errors, recovery after transient failure), and exhausted-retry behavior.

Test plan

  • pnpm build passes
  • Unit tests pass (vitest run in packages/testing)
  • Run E2E staging tests with --workers=2+ to confirm concurrent sign-in no longer times out

@changeset-bot
Copy link
Copy Markdown

changeset-bot bot commented Mar 27, 2026

🦋 Changeset detected

Latest commit: 12e7473

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 Patch

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

@vercel
Copy link
Copy Markdown

vercel bot commented Mar 27, 2026

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

Project Deployment Actions Updated (UTC)
clerk-js-sandbox Ready Ready Preview, Comment Mar 31, 2026 2:53pm

Request Review

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Mar 27, 2026

Open in StackBlitz

@clerk/agent-toolkit

npm i https://pkg.pr.new/@clerk/agent-toolkit@8173

@clerk/astro

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

@clerk/backend

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

@clerk/chrome-extension

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

@clerk/clerk-js

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

@clerk/dev-cli

npm i https://pkg.pr.new/@clerk/dev-cli@8173

@clerk/expo

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

@clerk/expo-passkeys

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

@clerk/express

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

@clerk/fastify

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

@clerk/hono

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

@clerk/localizations

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

@clerk/nextjs

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

@clerk/nuxt

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

@clerk/react

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

@clerk/react-router

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

@clerk/shared

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

@clerk/tanstack-react-start

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

@clerk/testing

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

@clerk/ui

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

@clerk/upgrade

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

@clerk/vue

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

commit: 12e7473

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 27, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

A changeset was added for @clerk/testing. setupClerkTestingToken was made idempotent per Playwright BrowserContext via a module-level WeakSet, and its single route.fetch try/catch was replaced with a bounded retry loop using exponential backoff and jitter that retries on thrown errors and retryable FAPI statuses (429, 502, 503, 504). After exhausting retries the handler logs a warning and falls back to continuing the route. New tests, a Vitest config, a test tsconfig, and npm test scripts were added; exports and signatures are unchanged.

🚥 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
Title check ✅ Passed The title accurately summarizes the main fix: addressing concurrent Playwright workers timing out on signIn() through de-duplication and retry logic.
Linked Issues check ✅ Passed The PR fully addresses all coding objectives from issue #7891: de-duplicates route handlers via WeakSet, adds retry logic with exponential backoff for transient errors (429, 502, 503, 504), and surfaces failures via console warnings instead of silently failing.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing the concurrent workers issue: route handler de-duplication, retry logic, test suite, and configuration files for testing setup. No unrelated modifications found.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

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


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

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/testing/src/playwright/setupClerkTestingToken.ts`:
- Around line 72-91: The retry logic currently lets a final retryable response
fall through and be parsed/fulfilled as an error; modify the branch around
RETRYABLE_STATUS_CODES so that when RETRYABLE_STATUS_CODES.has(status) is true
you check attempt < MAX_ROUTE_RETRIES to back off and continue, but if attempt
>= MAX_ROUTE_RETRIES you immediately fulfill the route with the raw response and
return (e.g., await route.fulfill({ response }); return) instead of calling
response.json() and running the captcha_bypass overrides; adjust the if block in
setupClerkTestingToken.ts to explicitly handle the exhausted-retries case before
parsing JSON.
🪄 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), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: f02acff7-c751-4d3a-a5a5-d73817f707d2

📥 Commits

Reviewing files that changed from the base of the PR and between 5ddb747 and 3874338.

📒 Files selected for processing (2)
  • .changeset/fix-testing-concurrent-workers.md
  • packages/testing/src/playwright/setupClerkTestingToken.ts

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/testing/src/playwright/__tests__/setupClerkTestingToken.test.ts`:
- Around line 17-18: The test declares fulfilled with json typed as unknown
which prevents property access in assertions; define a concrete shape or cast
before use: create a type alias (e.g., MockFulfilled = { response?: { client?:
unknown }; json?: { response?: { client?: unknown }; client?: unknown } }) and
change the fulfilled declaration to const fulfilled: MockFulfilled[] = []; or,
if you prefer minimal change, add narrow/cast at assertion sites when accessing
fulfilled[i].json (e.g., const j = fulfilled[i].json as { response?: { client?:
unknown } } before using j.response or j.response.client) so TypeScript accepts
the property accesses in the tests.
🪄 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), Organization UI (inherited)

Review profile: ASSERTIVE

Plan: Pro

Run ID: 1daf02a9-4752-4341-b0bf-64dad5d06d27

📥 Commits

Reviewing files that changed from the base of the PR and between 8dc633e and 604ec43.

📒 Files selected for processing (4)
  • packages/testing/package.json
  • packages/testing/src/playwright/__tests__/setupClerkTestingToken.test.ts
  • packages/testing/tsconfig.test.json
  • packages/testing/vitest.config.mts

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
packages/testing/src/playwright/__tests__/setupClerkTestingToken.test.ts (1)

18-19: ⚠️ Potential issue | 🔴 Critical

Typecheck blocker: fulfilled[].json is unknown but dereferenced in assertions

Line 18 defines json as unknown, then Lines 170/183/195 access nested properties directly. This will fail TS typecheck in strict mode.

Suggested fix
+interface MockClerkClientPayload {
+  captcha_bypass?: boolean;
+}
+
+interface MockClerkResponsePayload {
+  response?: MockClerkClientPayload;
+  client?: MockClerkClientPayload;
+}
+
+interface MockFulfillOptions {
+  response?: unknown;
+  json?: MockClerkResponsePayload;
+}
+
 function createMockRoute(overrides: { url?: string; fetchStatus?: number; fetchJson?: unknown; fetchError?: Error } = {}) {
@@
-  const fulfilled: { response?: unknown; json?: unknown }[] = [];
+  const fulfilled: MockFulfillOptions[] = [];
@@
-    fulfill: vi.fn((opts: any) => {
+    fulfill: vi.fn((opts: MockFulfillOptions) => {
       fulfilled.push(opts);
       return Promise.resolve();
     }),
#!/bin/bash
# Verify the type mismatch locations (read-only)
rg -n "const fulfilled:|fulfilled\\[0\\]\\.json\\.(response|client)" packages/testing/src/playwright/__tests__/setupClerkTestingToken.test.ts -n -C2

Also applies to: 170-170, 183-183, 195-195

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/testing/src/playwright/__tests__/setupClerkTestingToken.test.ts`
around lines 18 - 19, The test declares fulfilled: { response?: unknown; json?:
unknown }[] but later directly accesses nested properties on fulfilled[i].json
(e.g., fulfilled[0].json.response), causing TS errors; change the json type to a
concrete indexable shape (e.g., json?: Record<string, unknown> or a specific
interface for the expected response structure) or add narrowing/casts before
dereferencing (e.g., type guard or cast to any) so assertions compile; update
the declaration of fulfilled in setupClerkTestingToken.test.ts (the fulfilled
variable) and/or add local narrowing where fulfilled[].json is used.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@packages/testing/src/playwright/__tests__/setupClerkTestingToken.test.ts`:
- Around line 18-19: The test declares fulfilled: { response?: unknown; json?:
unknown }[] but later directly accesses nested properties on fulfilled[i].json
(e.g., fulfilled[0].json.response), causing TS errors; change the json type to a
concrete indexable shape (e.g., json?: Record<string, unknown> or a specific
interface for the expected response structure) or add narrowing/casts before
dereferencing (e.g., type guard or cast to any) so assertions compile; update
the declaration of fulfilled in setupClerkTestingToken.test.ts (the fulfilled
variable) and/or add local narrowing where fulfilled[].json is used.

ℹ️ Review info
⚙️ Run configuration

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

Review profile: ASSERTIVE

Plan: Pro

Run ID: d460eca5-05f3-49be-b785-aca88a7bcaa6

📥 Commits

Reviewing files that changed from the base of the PR and between 604ec43 and 6c202cb.

📒 Files selected for processing (1)
  • packages/testing/src/playwright/__tests__/setupClerkTestingToken.test.ts

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
packages/testing/src/playwright/__tests__/setupClerkTestingToken.test.ts (1)

20-20: ⚠️ Potential issue | 🔴 Critical

fulfilled[].json is typed as unknown, so these assertions are not type-safe and will fail typecheck.

fulfilled[0].json.response... / .client... dereference unknown. Give fulfilled a concrete payload type (or narrow before assertions) so this test compiles reliably.

Proposed fix
+type FulfilledJson =
+  | { response?: { captcha_bypass?: boolean }; client?: { captcha_bypass?: boolean } }
+  | undefined;
+
+interface FulfilledEntry {
+  response?: unknown;
+  json?: FulfilledJson;
+}
+
-const fulfilled: { response?: unknown; json?: unknown }[] = [];
+const fulfilled: FulfilledEntry[] = [];
#!/bin/bash
# Verify this file typechecks under the package's Vitest typecheck setup
pnpm -C packages/testing vitest typecheck src/playwright/__tests__/setupClerkTestingToken.test.ts

Also applies to: 172-172, 185-185, 197-197

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/testing/src/playwright/__tests__/setupClerkTestingToken.test.ts` at
line 20, The test declares fulfilled: { response?: unknown; json?: unknown }[]
so later dereferences like fulfilled[0].json.response and .client are untyped
and fail typecheck; update the type of fulfilled (or assert/narrow before use)
to a concrete interface matching the expected payload (e.g., shape containing
json.response and json.response.client) and use that type for the fulfilled
array used in the assertions inside the setupClerkTestingToken tests
(references: the fulfilled variable in setupClerkTestingToken.test.ts and any
assertions that access .json.response or .json.response.client); alternatively,
narrow fulfilled[0].json with a type guard or cast right before the assertions
to satisfy the compiler.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@packages/testing/src/playwright/__tests__/setupClerkTestingToken.test.ts`:
- Line 20: The test declares fulfilled: { response?: unknown; json?: unknown }[]
so later dereferences like fulfilled[0].json.response and .client are untyped
and fail typecheck; update the type of fulfilled (or assert/narrow before use)
to a concrete interface matching the expected payload (e.g., shape containing
json.response and json.response.client) and use that type for the fulfilled
array used in the assertions inside the setupClerkTestingToken tests
(references: the fulfilled variable in setupClerkTestingToken.test.ts and any
assertions that access .json.response or .json.response.client); alternatively,
narrow fulfilled[0].json with a type guard or cast right before the assertions
to satisfy the compiler.

ℹ️ Review info
⚙️ Run configuration

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

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4bb1f252-ad1e-483d-abbc-7b9cf956b3ff

📥 Commits

Reviewing files that changed from the base of the PR and between 6c202cb and 51d7a70.

📒 Files selected for processing (1)
  • packages/testing/src/playwright/__tests__/setupClerkTestingToken.test.ts

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