diff --git a/.changeset/eighty-husky-fly.md b/.changeset/eighty-husky-fly.md new file mode 100644 index 00000000000..74afba00e22 --- /dev/null +++ b/.changeset/eighty-husky-fly.md @@ -0,0 +1,7 @@ +--- +'@clerk/testing': minor +--- + +`setupClerkTestingToken` for Playwright now accepts `options.testingToken`, either as a string or as a function resolving one lazily, taking precedence over the `CLERK_TESTING_TOKEN` environment variable; the `clerk.signIn` helper accepts the same options via `setupClerkTestingTokenOptions`. It can also be called multiple times on the same browser context with different `frontendApiUrl` values, registering one bot-protection bypass per Clerk instance, so test suites spanning multiple instances no longer depend on a single process-wide `CLERK_FAPI`/`CLERK_TESTING_TOKEN` pair. The unstable `createPageObjects` and `createAppPageObject` helpers accept a new `testingTokenOptions` parameter forwarded to their automatic `setupClerkTestingToken` calls, and the Cypress `setupClerkTestingToken` accepts `options.testingToken` as a string. + +Note: `setupClerkTestingToken` for Playwright now resolves the frontend API URL before its duplicate-call check, so calling it without a resolvable frontend API URL (no `options.frontendApiUrl` and no `CLERK_FAPI`) always throws; previously such repeat calls on an already-set-up context were silently ignored. diff --git a/integration/testUtils/index.ts b/integration/testUtils/index.ts index 5c757e51b00..6161fc48113 100644 --- a/integration/testUtils/index.ts +++ b/integration/testUtils/index.ts @@ -1,5 +1,11 @@ import { createClerkClient as backendCreateClerkClient } from '@clerk/backend'; -import { createAppPageObject, createPageObjects, type EnhancedPage } from '@clerk/testing/playwright/unstable'; +import { parsePublishableKey } from '@clerk/shared/keys'; +import { + createAppPageObject, + createPageObjects, + type EnhancedPage, + type PlaywrightSetupClerkTestingTokenOptions, +} from '@clerk/testing/playwright/unstable'; import type { Browser, BrowserContext, Page } from '@playwright/test'; import type { Application } from '../models/application'; @@ -21,6 +27,53 @@ const createClerkClient = (app: Application) => { }); }; +// One testing token per instance (keyed by publishable key), minted lazily on the first +// FAPI request a test intercepts and shared across tests in this worker process. +const testingTokenCache = new Map>(); + +const getTestingToken = ( + app: Application, + clerkClient: ReturnType, + publishableKey: string, +): Promise => { + let promise = testingTokenCache.get(publishableKey); + if (!promise) { + promise = clerkClient.testingTokens + .createTestingToken() + .then(({ token }) => token) + .catch(err => { + console.warn( + `Failed to mint a testing token for app "${app.name}". Falling back to the CLERK_TESTING_TOKEN env var.`, + err, + ); + // Evict so the next test re-attempts the mint; otherwise one transient + // failure would pin the env-var fallback for the whole worker process. + testingTokenCache.delete(publishableKey); + return undefined; + }); + testingTokenCache.set(publishableKey, promise); + } + return promise; +}; + +// Builds per-app options so the testing-token bypass targets the instance THIS app talks +// to, instead of the process-global CLERK_FAPI (which holds whichever app ran clerkSetup +// last and silently misses every other instance, e.g. captcha-enabled ones). +const createTestingTokenOptions = ( + app: Application, + clerkClient: ReturnType, +): PlaywrightSetupClerkTestingTokenOptions | undefined => { + const publishableKey = app.env.publicVariables.get('CLERK_PUBLISHABLE_KEY'); + const parsedKey = publishableKey ? parsePublishableKey(publishableKey) : null; + if (!publishableKey || parsedKey?.instanceType !== 'development') { + return undefined; + } + return { + frontendApiUrl: parsedKey.frontendApi, + testingToken: () => getTestingToken(app, clerkClient, publishableKey), + }; +}; + export type CreateAppPageObjectArgs = { page: Page; context: BrowserContext; browser: Browser }; export const createTestUtils = < @@ -49,7 +102,13 @@ export const createTestUtils = < return { services } as any; } - const pageObjects = createPageObjects({ page: params.page, useTestingToken, baseURL: app.serverUrl }); + const testingTokenOptions = createTestingTokenOptions(app, clerkClient); + const pageObjects = createPageObjects({ + page: params.page, + useTestingToken, + baseURL: app.serverUrl, + testingTokenOptions, + }); const browserHelpers = { runInNewTab: async ( @@ -57,7 +116,10 @@ export const createTestUtils = < ) => { const u = createTestUtils({ app, - page: createAppPageObject({ page: await context.newPage(), useTestingToken }, { baseURL: app.serverUrl }), + page: createAppPageObject( + { page: await context.newPage(), useTestingToken, testingTokenOptions }, + { baseURL: app.serverUrl }, + ), }); await cb(u as any, context); return u; @@ -71,7 +133,10 @@ export const createTestUtils = < const context = await browser.newContext(); const u = createTestUtils({ app, - page: createAppPageObject({ page: await context.newPage(), useTestingToken }, { baseURL: app.serverUrl }), + page: createAppPageObject( + { page: await context.newPage(), useTestingToken, testingTokenOptions }, + { baseURL: app.serverUrl }, + ), }); await cb(u as any, context); return u; diff --git a/packages/testing/src/common/types.ts b/packages/testing/src/common/types.ts index 6a7c0a2e265..0deef19935b 100644 --- a/packages/testing/src/common/types.ts +++ b/packages/testing/src/common/types.ts @@ -48,6 +48,13 @@ export type SetupClerkTestingTokenOptions = { * Example: 'relieved-chamois-66.clerk.accounts.dev' */ frontendApiUrl?: string; + + /* + * The testing token to append to Frontend API requests. + * If provided, it takes precedence over the CLERK_TESTING_TOKEN environment variable. + * Useful when a test suite spans multiple Clerk instances, each needing its own token. + */ + testingToken?: string; }; export type ClerkSignInParams = diff --git a/packages/testing/src/cypress/setupClerkTestingToken.ts b/packages/testing/src/cypress/setupClerkTestingToken.ts index 71522be346d..3b742872fdd 100644 --- a/packages/testing/src/cypress/setupClerkTestingToken.ts +++ b/packages/testing/src/cypress/setupClerkTestingToken.ts @@ -10,6 +10,7 @@ type SetupClerkTestingTokenParams = { * Bypasses bot protection by appending the testing token in the Frontend API requests. * * @param params.options.frontendApiUrl - The frontend API URL for your Clerk dev instance, without the protocol. + * @param params.options.testingToken - The testing token to append. Takes precedence over the `CLERK_TESTING_TOKEN` Cypress env variable. * @returns A promise that resolves when the bot protection bypass is set up. * @throws An error if the Frontend API URL is not provided. * @example @@ -29,7 +30,7 @@ export const setupClerkTestingToken = (params?: SetupClerkTestingTokenParams) => const apiUrl = `https://${fapiUrl}/v1/**`; cy.intercept(apiUrl, req => { - const testingToken = Cypress.env('CLERK_TESTING_TOKEN'); + const testingToken = params?.options?.testingToken || Cypress.env('CLERK_TESTING_TOKEN'); if (testingToken) { req.query[TESTING_TOKEN_PARAM] = testingToken; } diff --git a/packages/testing/src/playwright/__tests__/setupClerkTestingToken.test.ts b/packages/testing/src/playwright/__tests__/setupClerkTestingToken.test.ts index 2758987257c..33d9a098a6f 100644 --- a/packages/testing/src/playwright/__tests__/setupClerkTestingToken.test.ts +++ b/packages/testing/src/playwright/__tests__/setupClerkTestingToken.test.ts @@ -47,18 +47,19 @@ function createMockRoute( } function createMockContext() { - let routeHandler: ((route: Route) => Promise) | undefined; + const registrations: { pattern: RegExp; handler: (route: Route) => Promise }[] = []; const context = { - route: vi.fn((_pattern: RegExp, handler: (route: Route) => Promise) => { - routeHandler = handler; + route: vi.fn((pattern: RegExp, handler: (route: Route) => Promise) => { + registrations.push({ pattern, handler }); return Promise.resolve(); }), } as unknown as BrowserContext; return { context, - getRouteHandler: () => routeHandler, + getRouteHandler: () => registrations.at(-1)?.handler, + getRegistrations: () => registrations, getRouteCallCount: () => (context.route as ReturnType).mock.calls.length, }; } @@ -148,6 +149,25 @@ describe('setupClerkTestingToken', () => { expect(routeFn).toHaveBeenCalledTimes(2); }); + it('rolls back only the failed host, keeping other hosts registered', async () => { + const routeFn = vi.fn(); + routeFn.mockResolvedValueOnce(undefined); + routeFn.mockRejectedValueOnce(new Error('context closed')); + routeFn.mockResolvedValueOnce(undefined); + + const context = { route: routeFn } as unknown as BrowserContext; + + await setupClerkTestingToken({ context, options: { frontendApiUrl: 'a.clerk.com' } }); + await expect(setupClerkTestingToken({ context, options: { frontendApiUrl: 'b.clerk.com' } })).rejects.toThrow( + 'context closed', + ); + // The failed host can retry; the successful host stays deduped. + await setupClerkTestingToken({ context, options: { frontendApiUrl: 'b.clerk.com' } }); + await setupClerkTestingToken({ context, options: { frontendApiUrl: 'a.clerk.com' } }); + + expect(routeFn).toHaveBeenCalledTimes(3); + }); + it('resolves context from page when context is not provided', async () => { const { context, getRouteCallCount } = createMockContext(); const page = { context: () => context } as any; @@ -157,6 +177,129 @@ describe('setupClerkTestingToken', () => { expect(getRouteCallCount()).toBe(1); }); + + it('no-ops on a second call with the same explicit frontendApiUrl', async () => { + const { context, getRouteCallCount } = createMockContext(); + + await setupClerkTestingToken({ context, options: { frontendApiUrl: 'a.clerk.com' } }); + await setupClerkTestingToken({ context, options: { frontendApiUrl: 'a.clerk.com' } }); + + expect(getRouteCallCount()).toBe(1); + }); + + it('registers an additional route for a different frontendApiUrl on the same context', async () => { + const { context, getRegistrations } = createMockContext(); + + await setupClerkTestingToken({ context, options: { frontendApiUrl: 'a.clerk.com' } }); + await setupClerkTestingToken({ context, options: { frontendApiUrl: 'b.clerk.com' } }); + + const registrations = getRegistrations(); + expect(registrations).toHaveLength(2); + expect(registrations[0].pattern.test('https://a.clerk.com/v1/client')).toBe(true); + expect(registrations[0].pattern.test('https://b.clerk.com/v1/client')).toBe(false); + expect(registrations[1].pattern.test('https://b.clerk.com/v1/client')).toBe(true); + expect(registrations[1].pattern.test('https://a.clerk.com/v1/client')).toBe(false); + }); + }); + + describe('per-instance testing tokens', () => { + it('uses options.testingToken string over the env var', async () => { + const { context, getRouteHandler } = createMockContext(); + await setupClerkTestingToken({ context, options: { testingToken: 'option_token' } }); + + const { route } = createMockRoute(); + await getRouteHandler()!(route); + + expect(route.fetch).toHaveBeenCalledWith({ + url: expect.stringContaining('__clerk_testing_token=option_token'), + }); + }); + + it('resolves an async testing token provider', async () => { + const { context, getRouteHandler } = createMockContext(); + await setupClerkTestingToken({ context, options: { testingToken: () => Promise.resolve('provider_token') } }); + + const { route } = createMockRoute(); + await getRouteHandler()!(route); + + expect(route.fetch).toHaveBeenCalledWith({ + url: expect.stringContaining('__clerk_testing_token=provider_token'), + }); + }); + + it('invokes the provider once per registration across multiple requests', async () => { + const { context, getRouteHandler } = createMockContext(); + const provider = vi.fn(() => Promise.resolve('provider_token')); + await setupClerkTestingToken({ context, options: { testingToken: provider } }); + + const handler = getRouteHandler()!; + await handler(createMockRoute().route); + await handler(createMockRoute().route); + + expect(provider).toHaveBeenCalledTimes(1); + }); + + it('falls back to the env var and warns when the provider rejects', async () => { + const warnSpy = vi.spyOn(console, 'warn').mockImplementation(() => {}); + const { context, getRouteHandler } = createMockContext(); + await setupClerkTestingToken({ + context, + options: { testingToken: () => Promise.reject(new Error('mint failed')) }, + }); + + const { route, fulfilled } = createMockRoute(); + await getRouteHandler()!(route); + + expect(warnSpy).toHaveBeenCalledWith( + expect.stringContaining('Failed to resolve the testing token'), + expect.any(Error), + ); + expect(route.fetch).toHaveBeenCalledWith({ + url: expect.stringContaining(`__clerk_testing_token=${TESTING_TOKEN}`), + }); + expect(fulfilled[0].json.response.captcha_bypass).toBe(true); + + warnSpy.mockRestore(); + }); + + it('falls back to the env var when the provider resolves undefined', async () => { + const { context, getRouteHandler } = createMockContext(); + await setupClerkTestingToken({ context, options: { testingToken: () => undefined } }); + + const { route } = createMockRoute(); + await getRouteHandler()!(route); + + expect(route.fetch).toHaveBeenCalledWith({ + url: expect.stringContaining(`__clerk_testing_token=${TESTING_TOKEN}`), + }); + }); + + it('appends each registration its own token when two instances share a context', async () => { + const { context, getRegistrations } = createMockContext(); + + await setupClerkTestingToken({ + context, + options: { frontendApiUrl: 'a.clerk.com', testingToken: 'token_a' }, + }); + await setupClerkTestingToken({ + context, + options: { frontendApiUrl: 'b.clerk.com', testingToken: () => Promise.resolve('token_b') }, + }); + + const [first, second] = getRegistrations(); + + const routeA = createMockRoute({ url: 'https://a.clerk.com/v1/client' }); + await first.handler(routeA.route); + expect(routeA.route.fetch).toHaveBeenCalledWith({ + url: expect.stringContaining('__clerk_testing_token=token_a'), + }); + + const routeB = createMockRoute({ url: 'https://b.clerk.com/v1/client' }); + await second.handler(routeB.route); + expect(routeB.route.fetch).toHaveBeenCalledWith({ + url: expect.stringContaining('__clerk_testing_token=token_b'), + }); + }); }); describe('route handler', () => { diff --git a/packages/testing/src/playwright/helpers.ts b/packages/testing/src/playwright/helpers.ts index 5c377375dfd..611f676c4cf 100644 --- a/packages/testing/src/playwright/helpers.ts +++ b/packages/testing/src/playwright/helpers.ts @@ -2,8 +2,9 @@ import { createClerkClient } from '@clerk/backend'; import type { Clerk, SignOutOptions } from '@clerk/shared/types'; import type { Page } from '@playwright/test'; -import type { ClerkSignInParams, SetupClerkTestingTokenOptions } from '../common'; +import type { ClerkSignInParams } from '../common'; import { signInHelper } from '../common'; +import type { PlaywrightSetupClerkTestingTokenOptions } from './setupClerkTestingToken'; import { setupClerkTestingToken } from './setupClerkTestingToken'; declare global { @@ -19,7 +20,7 @@ type PlaywrightClerkLoadedParams = { type PlaywrightClerkSignInParamsWithEmail = { page: Page; emailAddress: string; - setupClerkTestingTokenOptions?: SetupClerkTestingTokenOptions; + setupClerkTestingTokenOptions?: PlaywrightSetupClerkTestingTokenOptions; }; type ClerkHelperParams = { @@ -104,7 +105,7 @@ const loaded = async ({ page }: PlaywrightClerkLoadedParams) => { type PlaywrightClerkSignInParams = { page: Page; signInParams: ClerkSignInParams; - setupClerkTestingTokenOptions?: SetupClerkTestingTokenOptions; + setupClerkTestingTokenOptions?: PlaywrightSetupClerkTestingTokenOptions; }; const signIn = async (opts: PlaywrightClerkSignInParams | PlaywrightClerkSignInParamsWithEmail) => { diff --git a/packages/testing/src/playwright/index.ts b/packages/testing/src/playwright/index.ts index 59a95a4b8d2..cf6bef29a91 100644 --- a/packages/testing/src/playwright/index.ts +++ b/packages/testing/src/playwright/index.ts @@ -1,4 +1,5 @@ export { clerkSetup } from './setup'; export { createAgentTestingTask } from './agent-task'; export { setupClerkTestingToken } from './setupClerkTestingToken'; +export type { PlaywrightSetupClerkTestingTokenOptions, TestingTokenProvider } from './setupClerkTestingToken'; export { clerk } from './helpers'; diff --git a/packages/testing/src/playwright/setupClerkTestingToken.ts b/packages/testing/src/playwright/setupClerkTestingToken.ts index 2f40827c7ba..ae0a0af7d20 100644 --- a/packages/testing/src/playwright/setupClerkTestingToken.ts +++ b/packages/testing/src/playwright/setupClerkTestingToken.ts @@ -3,13 +3,28 @@ import type { BrowserContext, Page } from '@playwright/test'; import type { SetupClerkTestingTokenOptions } from '../common'; import { ERROR_MISSING_FRONTEND_API_URL, TESTING_TOKEN_PARAM } from '../common'; +/** + * A function that lazily resolves a testing token. Returning `undefined` falls back to the + * `CLERK_TESTING_TOKEN` environment variable. + */ +export type TestingTokenProvider = () => string | undefined | Promise; + +export type PlaywrightSetupClerkTestingTokenOptions = Omit & { + /* + * The testing token to append to Frontend API requests, or a function that resolves it lazily. + * If provided, it takes precedence over the CLERK_TESTING_TOKEN environment variable. + * Useful when a test suite spans multiple Clerk instances, each needing its own token. + */ + testingToken?: string | TestingTokenProvider; +}; + type SetupClerkTestingTokenParams = { context?: BrowserContext; page?: Page; - options?: SetupClerkTestingTokenOptions; + options?: PlaywrightSetupClerkTestingTokenOptions; }; -const setupContexts = new WeakSet(); +const setupContexts = new WeakMap>(); const RETRYABLE_STATUS_CODES = new Set([429, 502, 503, 504]); const MAX_ROUTE_RETRIES = 3; @@ -22,9 +37,10 @@ const JITTER_MAX_MS = 250; * @param params.context - The Playwright browser context object. * @param params.page - The Playwright page object. * @param params.options.frontendApiUrl - The frontend API URL for your Clerk dev instance, without the protocol. + * @param params.options.testingToken - The testing token to append, or a function that resolves it lazily. Takes precedence over the `CLERK_TESTING_TOKEN` environment variable. * @returns A promise that resolves when the bot protection bypass is set up. * @throws An error if the Frontend API URL is not provided. - * @remarks Set the `CLERK_TESTING_DEBUG` environment variable to enable verbose logging of retry attempts and route handler registration. + * @remarks Set the `CLERK_TESTING_DEBUG` environment variable to enable verbose logging of retry attempts and route handler registration. Calling this again with the same frontend API URL on the same context is a no-op and its options (including `testingToken`) are ignored; the first registration wins. Calling it with a different frontend API URL registers an additional, independent bypass, so suites spanning multiple Clerk instances can set up one per instance. * @example * import { setupClerkTestingToken } from '@clerk/testing/playwright'; * @@ -42,26 +58,48 @@ export const setupClerkTestingToken = async ({ context, options, page }: SetupCl throw new Error('Either context or page must be provided to setup testing token'); } - if (setupContexts.has(browserContext)) { - if (process.env.CLERK_TESTING_DEBUG) { - console.log('[Clerk Testing] Route handler already registered for this context, skipping duplicate setup'); - } - return; - } - const fapiUrl = options?.frontendApiUrl || process.env.CLERK_FAPI; if (!fapiUrl) { throw new Error(ERROR_MISSING_FRONTEND_API_URL); } + const setupHosts = setupContexts.get(browserContext) ?? new Set(); + if (setupHosts.has(fapiUrl)) { + if (process.env.CLERK_TESTING_DEBUG) { + console.log( + `[Clerk Testing] Route handler already registered for ${fapiUrl} on this context, skipping duplicate setup`, + ); + } + return; + } + const escapedFapiUrl = fapiUrl.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); const apiUrl = new RegExp(`^https://${escapedFapiUrl}/v1/.*?(\\?.*)?$`); - setupContexts.add(browserContext); + // Resolve the token once per registration, shared by every intercepted request. + let resolvedTokenPromise: Promise | undefined; + const resolveTestingToken = () => { + if (!resolvedTokenPromise) { + const provided = options?.testingToken; + resolvedTokenPromise = Promise.resolve() + .then(() => (typeof provided === 'function' ? provided() : provided)) + .catch(error => { + console.warn( + `[Clerk Testing] Failed to resolve the testing token for ${fapiUrl}. Falling back to the CLERK_TESTING_TOKEN environment variable.`, + error, + ); + return undefined; + }); + } + return resolvedTokenPromise; + }; + + setupHosts.add(fapiUrl); + setupContexts.set(browserContext, setupHosts); try { await browserContext.route(apiUrl, async route => { const originalUrl = new URL(route.request().url()); - const testingToken = process.env.CLERK_TESTING_TOKEN; + const testingToken = (await resolveTestingToken()) ?? process.env.CLERK_TESTING_TOKEN; if (testingToken) { originalUrl.searchParams.set(TESTING_TOKEN_PARAM, testingToken); @@ -130,7 +168,7 @@ export const setupClerkTestingToken = async ({ context, options, page }: SetupCl } }); } catch (e) { - setupContexts.delete(browserContext); + setupHosts.delete(fapiUrl); throw e; } }; diff --git a/packages/testing/src/playwright/unstable/index.ts b/packages/testing/src/playwright/unstable/index.ts index 45c1df6d301..5219dc64d8b 100644 --- a/packages/testing/src/playwright/unstable/index.ts +++ b/packages/testing/src/playwright/unstable/index.ts @@ -2,4 +2,5 @@ import { createPageObjects } from './page-objects'; import { createAppPageObject } from './page-objects/app'; export type { EnhancedPage } from './page-objects/app'; +export type { PlaywrightSetupClerkTestingTokenOptions, TestingTokenProvider } from '../setupClerkTestingToken'; export { createPageObjects, createAppPageObject }; diff --git a/packages/testing/src/playwright/unstable/page-objects/app.ts b/packages/testing/src/playwright/unstable/page-objects/app.ts index b578844b054..0ca573714ec 100644 --- a/packages/testing/src/playwright/unstable/page-objects/app.ts +++ b/packages/testing/src/playwright/unstable/page-objects/app.ts @@ -1,10 +1,14 @@ import type { Page } from '@playwright/test'; +import type { PlaywrightSetupClerkTestingTokenOptions } from '../../setupClerkTestingToken'; import { setupClerkTestingToken } from '../../setupClerkTestingToken'; export type EnhancedPage = ReturnType; -export const createAppPageObject = (testArgs: { page: Page; useTestingToken?: boolean }, app: { baseURL?: string }) => { - const { page, useTestingToken = true } = testArgs; +export const createAppPageObject = ( + testArgs: { page: Page; useTestingToken?: boolean; testingTokenOptions?: PlaywrightSetupClerkTestingTokenOptions }, + app: { baseURL?: string }, +) => { + const { page, useTestingToken = true, testingTokenOptions } = testArgs; const appPage = Object.create(page) as Page; const helpers = { goToAppHome: async () => { @@ -16,7 +20,7 @@ export const createAppPageObject = (testArgs: { page: Page; useTestingToken?: bo try { if (useTestingToken) { - await setupClerkTestingToken({ page }); + await setupClerkTestingToken({ page, options: testingTokenOptions }); } await page.goto(app.baseURL); @@ -56,7 +60,7 @@ export const createAppPageObject = (testArgs: { page: Page; useTestingToken?: bo } if (useTestingToken) { - await setupClerkTestingToken({ page }); + await setupClerkTestingToken({ page, options: testingTokenOptions }); } return page.goto(url.toString(), { timeout: opts.timeout ?? 20000, waitUntil: opts.waitUntil }); diff --git a/packages/testing/src/playwright/unstable/page-objects/index.ts b/packages/testing/src/playwright/unstable/page-objects/index.ts index 67c6bb73ec3..3a5177e771b 100644 --- a/packages/testing/src/playwright/unstable/page-objects/index.ts +++ b/packages/testing/src/playwright/unstable/page-objects/index.ts @@ -1,5 +1,6 @@ import type { Page } from '@playwright/test'; +import type { PlaywrightSetupClerkTestingTokenOptions } from '../../setupClerkTestingToken'; import { createAPIKeysComponentPageObject } from './apiKeys'; import { createAppPageObject } from './app'; import { createCheckoutPageObject } from './checkout'; @@ -26,13 +27,15 @@ export const createPageObjects = ({ page, useTestingToken = true, baseURL, + testingTokenOptions, }: { page: Page; useTestingToken?: boolean; baseURL?: string; + testingTokenOptions?: PlaywrightSetupClerkTestingTokenOptions; }) => { - const app = createAppPageObject({ page, useTestingToken }, { baseURL }); - const testArgs = { page: app }; + const app = createAppPageObject({ page, useTestingToken, testingTokenOptions }, { baseURL }); + const testArgs = { page: app, testingTokenOptions }; return { page: app, diff --git a/packages/testing/src/playwright/unstable/page-objects/testingToken.ts b/packages/testing/src/playwright/unstable/page-objects/testingToken.ts index d24e7efcd86..4c8639beceb 100644 --- a/packages/testing/src/playwright/unstable/page-objects/testingToken.ts +++ b/packages/testing/src/playwright/unstable/page-objects/testingToken.ts @@ -1,8 +1,15 @@ +import type { PlaywrightSetupClerkTestingTokenOptions } from '../../setupClerkTestingToken'; import { setupClerkTestingToken } from '../../setupClerkTestingToken'; import type { EnhancedPage } from './app'; -export const createTestingTokenPageObject = ({ page }: { page: EnhancedPage }) => { +export const createTestingTokenPageObject = ({ + page, + testingTokenOptions, +}: { + page: EnhancedPage; + testingTokenOptions?: PlaywrightSetupClerkTestingTokenOptions; +}) => { return { - setup: async () => setupClerkTestingToken({ page }), + setup: async () => setupClerkTestingToken({ page, options: testingTokenOptions }), }; };