@clerk/testing
Version:
Utilities to help you create E2E test suites for apps using Clerk
1 lines • 9.43 kB
Source Map (JSON)
{"version":3,"sources":["../../src/playwright/setup.ts","../../src/playwright/helpers.ts"],"sourcesContent":["import type { ClerkSetupOptions } from '../common';\nimport { fetchEnvVars } from '../common';\n\n/**\n * Sets up Clerk for testing by fetching the testing token from the Clerk Backend API.\n *\n * @param options.publishableKey - The publishable key for your Clerk dev instance.\n * @param options.frontendApiUrl - The frontend API URL for your Clerk dev instance, without the protocol. It overrides the Frontend API URL parsed from the publishable key.\n * @param options.debug - Enable debug logs.\n * @returns A promise that resolves when Clerk is set up.\n *\n * @throws An error if the publishable key or the secret key is not provided.\n * @throws An error if the secret key is from a production instance.\n * @throws An error if the testing token cannot be fetched from the Clerk Backend API.\n */\nexport const clerkSetup = async (options?: ClerkSetupOptions) => {\n const { CLERK_FAPI, CLERK_TESTING_TOKEN } = await fetchEnvVars(options);\n process.env.CLERK_FAPI = CLERK_FAPI;\n process.env.CLERK_TESTING_TOKEN = CLERK_TESTING_TOKEN;\n};\n","import { createClerkClient } from '@clerk/backend';\nimport type { Clerk, SignOutOptions } from '@clerk/types';\nimport type { Page } from '@playwright/test';\n\nimport type { ClerkSignInParams, SetupClerkTestingTokenOptions } from '../common';\nimport { signInHelper } from '../common';\nimport { setupClerkTestingToken } from './setupClerkTestingToken';\n\ndeclare global {\n interface Window {\n Clerk: Clerk;\n }\n}\n\ntype PlaywrightClerkLoadedParams = {\n page: Page;\n};\n\ntype PlaywrightClerkSignInParamsWithEmail = {\n page: Page;\n emailAddress: string;\n setupClerkTestingTokenOptions?: SetupClerkTestingTokenOptions;\n};\n\ntype ClerkHelperParams = {\n /**\n * Signs in a user using Clerk. This helper supports multiple sign-in strategies:\n * 1. Using signInParams object (password, phone_code, email_code strategies)\n * 2. Using emailAddress for automatic ticket-based sign-in\n *\n * Multi-factor is not supported.\n * This helper is using the `setupClerkTestingToken` internally.\n * It is required to call `page.goto` before calling this helper, and navigate to a not protected page that loads Clerk.\n *\n * For strategy-based sign-in:\n * If the strategy is password, the helper will sign in the user using the provided password and identifier.\n * If the strategy is phone_code, you are required to have a user with a test phone number as an identifier (e.g. +15555550100).\n * If the strategy is email_code, you are required to have a user with a test email as an identifier (e.g. your_email+clerk_test@example.com).\n *\n * For email-based sign-in:\n * The helper finds the user by email, creates a sign-in token using Clerk's backend API, and uses the ticket strategy.\n *\n * @example Strategy-based sign-in\n * import { clerk } from \"@clerk/testing/playwright\";\n *\n * test(\"sign in with strategy\", async ({ page }) => {\n * await page.goto(\"/\");\n * await clerk.signIn({\n * page,\n * signInParams: { strategy: 'phone_code', identifier: '+15555550100' },\n * });\n * await page.goto(\"/protected\");\n * });\n *\n * @example Email-based sign-in\n * import { clerk } from \"@clerk/testing/playwright\";\n *\n * test(\"sign in with email\", async ({ page }) => {\n * await page.goto(\"/\");\n * await clerk.signIn({ emailAddress: \"bryce@clerk.dev\", page });\n * await page.goto(\"/protected\");\n * });\n */\n signIn: {\n (opts: PlaywrightClerkSignInParams): Promise<void>;\n (opts: PlaywrightClerkSignInParamsWithEmail): Promise<void>;\n };\n /**\n * Signs out the current user using Clerk.\n * It is required to call `page.goto` before calling this helper, and navigate to a page that loads Clerk.\n * @param opts.signOutOptions - A SignOutOptions object.\n * @param opts.page - The Playwright page object.\n *\n * @example\n * import { clerk } from \"@clerk/testing/playwright\";\n *\n * test(\"sign out\", async ({ page }) => {\n * await page.goto(\"/\");\n * await clerk.signIn({\n * page,\n * signInParams: { strategy: 'phone_code', identifier: '+15555550100' },\n * });\n * await page.goto(\"/protected\");\n * await clerk.signOut({ page });\n * await page.goto(\"/protected\");\n * // should redirect to sign in page\n * });\n */\n signOut: (opts: PlaywrightClerkSignOutParams) => Promise<void>;\n /**\n * Asserts that Clerk has been loaded.\n * It is required to call `page.goto` before calling this helper, and navigate to a page that loads Clerk.\n *\n * @param opts.page - The Playwright page object.\n */\n loaded: (opts: PlaywrightClerkLoadedParams) => Promise<void>;\n};\n\nconst loaded = async ({ page }: PlaywrightClerkLoadedParams) => {\n await page.waitForFunction(() => window.Clerk !== undefined);\n await page.waitForFunction(() => window.Clerk.loaded);\n};\n\ntype PlaywrightClerkSignInParams = {\n page: Page;\n signInParams: ClerkSignInParams;\n setupClerkTestingTokenOptions?: SetupClerkTestingTokenOptions;\n};\n\nconst signIn = async (opts: PlaywrightClerkSignInParams | PlaywrightClerkSignInParamsWithEmail) => {\n const context = opts.page.context();\n if (!context) {\n throw new Error('Page context is not available. Make sure the page is properly initialized.');\n }\n\n await setupClerkTestingToken({\n context,\n options: 'setupClerkTestingTokenOptions' in opts ? opts.setupClerkTestingTokenOptions : undefined,\n });\n await loaded({ page: opts.page });\n\n if ('emailAddress' in opts) {\n // Email-based sign-in using ticket strategy\n const { emailAddress, page } = opts;\n\n const secretKey = process.env.CLERK_SECRET_KEY;\n if (!secretKey) {\n throw new Error('CLERK_SECRET_KEY environment variable is required for email-based sign-in');\n }\n\n const clerkClient = createClerkClient({ secretKey });\n\n try {\n // Find user by email\n const userList = await clerkClient.users.getUserList({ emailAddress: [emailAddress] });\n if (!userList.data || userList.data.length === 0) {\n throw new Error(`No user found with email: ${emailAddress}`);\n }\n\n const user = userList.data[0];\n\n const signInToken = await clerkClient.signInTokens.createSignInToken({\n userId: user.id,\n expiresInSeconds: 300, // 5 minutes\n });\n\n await page.evaluate(signInHelper, {\n signInParams: { strategy: 'ticket' as const, ticket: signInToken.token },\n });\n\n await page.waitForFunction(() => window.Clerk?.user !== null);\n } catch (err: any) {\n throw new Error(`Failed to sign in with email ${emailAddress}: ${err?.message}`);\n }\n } else {\n // Strategy-based sign-in: signIn(opts)\n const { page, signInParams } = opts;\n await page.evaluate(signInHelper, { signInParams });\n }\n};\n\ntype PlaywrightClerkSignOutParams = {\n page: Page;\n signOutOptions?: SignOutOptions;\n};\n\nconst signOut = async ({ page, signOutOptions }: PlaywrightClerkSignOutParams) => {\n await loaded({ page });\n\n await page.evaluate(async options => {\n await window.Clerk.signOut(options);\n }, signOutOptions);\n};\n\nexport const clerk: ClerkHelperParams = {\n signIn: signIn as ClerkHelperParams['signIn'],\n signOut,\n loaded,\n};\n"],"mappings":"yHAeO,IAAMA,EAAa,MAAOC,GAAgC,CAC/D,GAAM,CAAE,WAAAC,EAAY,oBAAAC,CAAoB,EAAI,MAAMC,EAAaH,CAAO,EACtE,QAAQ,IAAI,WAAaC,EACzB,QAAQ,IAAI,oBAAsBC,CACpC,ECnBA,OAAS,qBAAAE,MAAyB,iBAkGlC,IAAMC,EAAS,MAAO,CAAE,KAAAC,CAAK,IAAmC,CAC9D,MAAMA,EAAK,gBAAgB,IAAM,OAAO,QAAU,MAAS,EAC3D,MAAMA,EAAK,gBAAgB,IAAM,OAAO,MAAM,MAAM,CACtD,EAQMC,EAAS,MAAOC,GAA6E,CACjG,IAAMC,EAAUD,EAAK,KAAK,QAAQ,EAClC,GAAI,CAACC,EACH,MAAM,IAAI,MAAM,4EAA4E,EAS9F,GANA,MAAMC,EAAuB,CAC3B,QAAAD,EACA,QAAS,kCAAmCD,EAAOA,EAAK,8BAAgC,MAC1F,CAAC,EACD,MAAMH,EAAO,CAAE,KAAMG,EAAK,IAAK,CAAC,EAE5B,iBAAkBA,EAAM,CAE1B,GAAM,CAAE,aAAAG,EAAc,KAAAL,CAAK,EAAIE,EAEzBI,EAAY,QAAQ,IAAI,iBAC9B,GAAI,CAACA,EACH,MAAM,IAAI,MAAM,2EAA2E,EAG7F,IAAMC,EAAcC,EAAkB,CAAE,UAAAF,CAAU,CAAC,EAEnD,GAAI,CAEF,IAAMG,EAAW,MAAMF,EAAY,MAAM,YAAY,CAAE,aAAc,CAACF,CAAY,CAAE,CAAC,EACrF,GAAI,CAACI,EAAS,MAAQA,EAAS,KAAK,SAAW,EAC7C,MAAM,IAAI,MAAM,6BAA6BJ,CAAY,EAAE,EAG7D,IAAMK,EAAOD,EAAS,KAAK,CAAC,EAEtBE,EAAc,MAAMJ,EAAY,aAAa,kBAAkB,CACnE,OAAQG,EAAK,GACb,iBAAkB,GACpB,CAAC,EAED,MAAMV,EAAK,SAASY,EAAc,CAChC,aAAc,CAAE,SAAU,SAAmB,OAAQD,EAAY,KAAM,CACzE,CAAC,EAED,MAAMX,EAAK,gBAAgB,IAAM,OAAO,OAAO,OAAS,IAAI,CAC9D,OAASa,EAAU,CACjB,MAAM,IAAI,MAAM,gCAAgCR,CAAY,KAAKQ,GAAK,OAAO,EAAE,CACjF,CACF,KAAO,CAEL,GAAM,CAAE,KAAAb,EAAM,aAAAc,CAAa,EAAIZ,EAC/B,MAAMF,EAAK,SAASY,EAAc,CAAE,aAAAE,CAAa,CAAC,CACpD,CACF,EAOMC,EAAU,MAAO,CAAE,KAAAf,EAAM,eAAAgB,CAAe,IAAoC,CAChF,MAAMjB,EAAO,CAAE,KAAAC,CAAK,CAAC,EAErB,MAAMA,EAAK,SAAS,MAAMiB,GAAW,CACnC,MAAM,OAAO,MAAM,QAAQA,CAAO,CACpC,EAAGD,CAAc,CACnB,EAEaE,EAA2B,CACtC,OAAQjB,EACR,QAAAc,EACA,OAAAhB,CACF","names":["clerkSetup","options","CLERK_FAPI","CLERK_TESTING_TOKEN","fetchEnvVars","createClerkClient","loaded","page","signIn","opts","context","setupClerkTestingToken","emailAddress","secretKey","clerkClient","createClerkClient","userList","user","signInToken","signInHelper","err","signInParams","signOut","signOutOptions","options","clerk"]}