From 288ad98b0e2bf1468df86e90c29910d4d2678458 Mon Sep 17 00:00:00 2001 From: Dylan Staley <88163+dstaley@users.noreply.github.com> Date: Thu, 17 Apr 2025 12:17:13 -0700 Subject: [PATCH 1/6] feat(testing): Add pageObjects support --- .changeset/huge-plums-invent.md | 5 + integration/testUtils/index.ts | 95 ++----------------- packages/testing/package.json | 10 ++ .../testing/src/playwright/unstable/index.ts | 5 + .../playwright/unstable/page-objects/app.ts | 10 +- .../playwright/unstable/page-objects/clerk.ts | 16 ++++ .../unstable/page-objects/common.ts | 4 +- .../unstable/page-objects/expect.ts | 34 +++++++ .../playwright/unstable/page-objects/index.ts | 39 ++++++++ .../unstable/page-objects/keylessPopover.ts | 9 +- .../page-objects/organizationSwitcher.ts | 6 +- .../unstable/page-objects/sessionTask.ts | 9 +- .../unstable/page-objects/signIn.ts | 10 +- .../unstable/page-objects/signUp.ts | 10 +- .../unstable/page-objects/testingToken.ts | 8 ++ .../unstable/page-objects/userButton.ts | 5 +- .../unstable/page-objects/userProfile.ts | 11 +-- .../unstable/page-objects/userVerification.ts | 11 +-- .../unstable/page-objects/waitlist.ts | 6 +- packages/testing/tsconfig.json | 8 +- packages/testing/tsup.config.ts | 2 +- 21 files changed, 169 insertions(+), 144 deletions(-) create mode 100644 .changeset/huge-plums-invent.md create mode 100644 packages/testing/src/playwright/unstable/index.ts rename integration/testUtils/appPageObject.ts => packages/testing/src/playwright/unstable/page-objects/app.ts (92%) create mode 100644 packages/testing/src/playwright/unstable/page-objects/clerk.ts rename integration/testUtils/commonPageObject.ts => packages/testing/src/playwright/unstable/page-objects/common.ts (94%) create mode 100644 packages/testing/src/playwright/unstable/page-objects/expect.ts create mode 100644 packages/testing/src/playwright/unstable/page-objects/index.ts rename integration/testUtils/keylessPopoverPageObject.ts => packages/testing/src/playwright/unstable/page-objects/keylessPopover.ts (70%) rename integration/testUtils/organizationSwitcherPageObject.ts => packages/testing/src/playwright/unstable/page-objects/organizationSwitcher.ts (89%) rename integration/testUtils/sessionTaskPageObject.ts => packages/testing/src/playwright/unstable/page-objects/sessionTask.ts (70%) rename integration/testUtils/signInPageObject.ts => packages/testing/src/playwright/unstable/page-objects/signIn.ts (86%) rename integration/testUtils/signUpPageObject.ts => packages/testing/src/playwright/unstable/page-objects/signUp.ts (87%) create mode 100644 packages/testing/src/playwright/unstable/page-objects/testingToken.ts rename integration/testUtils/userButtonPageObject.ts => packages/testing/src/playwright/unstable/page-objects/userButton.ts (90%) rename integration/testUtils/userProfilePageObject.ts => packages/testing/src/playwright/unstable/page-objects/userProfile.ts (84%) rename integration/testUtils/userVerificationPageObject.ts => packages/testing/src/playwright/unstable/page-objects/userVerification.ts (71%) rename integration/testUtils/waitlistPageObject.ts => packages/testing/src/playwright/unstable/page-objects/waitlist.ts (84%) diff --git a/.changeset/huge-plums-invent.md b/.changeset/huge-plums-invent.md new file mode 100644 index 00000000000..98361be6513 --- /dev/null +++ b/.changeset/huge-plums-invent.md @@ -0,0 +1,5 @@ +--- +'@clerk/testing': minor +--- + +Add pageObjects diff --git a/integration/testUtils/index.ts b/integration/testUtils/index.ts index 82d3128269a..9de1ed961be 100644 --- a/integration/testUtils/index.ts +++ b/integration/testUtils/index.ts @@ -1,25 +1,14 @@ import { createClerkClient as backendCreateClerkClient } from '@clerk/backend'; -import { setupClerkTestingToken } from '@clerk/testing/playwright'; -import type { Browser, BrowserContext, Page, Response } from '@playwright/test'; -import { expect } from '@playwright/test'; +import { createPageObjects, createAppPageObject, type EnhancedPage } from '@clerk/testing/playwright/unstable'; +import type { Browser, BrowserContext, Page } from '@playwright/test'; import type { Application } from '../models/application'; -import { createAppPageObject } from './appPageObject'; import { createEmailService } from './emailService'; import { createInvitationService } from './invitationsService'; -import { createKeylessPopoverPageObject } from './keylessPopoverPageObject'; import { createOrganizationsService } from './organizationsService'; -import { createOrganizationSwitcherComponentPageObject } from './organizationSwitcherPageObject'; -import { createSessionTaskComponentPageObject } from './sessionTaskPageObject'; -import type { EnchancedPage, TestArgs } from './signInPageObject'; -import { createSignInComponentPageObject } from './signInPageObject'; -import { createSignUpComponentPageObject } from './signUpPageObject'; -import { createUserButtonPageObject } from './userButtonPageObject'; -import { createUserProfileComponentPageObject } from './userProfilePageObject'; + import type { FakeOrganization, FakeUser } from './usersService'; import { createUserService } from './usersService'; -import { createUserVerificationComponentPageObject } from './userVerificationPageObject'; -import { createWaitlistComponentPageObject } from './waitlistPageObject'; export type { FakeUser, FakeOrganization }; const createClerkClient = (app: Application) => { @@ -30,58 +19,6 @@ const createClerkClient = (app: Application) => { }); }; -const createExpectPageObject = ({ page }: TestArgs) => { - return { - toBeHandshake: async (res: Response) => { - // Travel the redirect chain until we find the handshake header - // TODO: Loop through the redirects until we find a handshake header, or timeout trying - const redirect = await res.request().redirectedFrom().redirectedFrom().response(); - expect(redirect.status()).toBe(307); - expect(redirect.headers()['x-clerk-auth-status']).toContain('handshake'); - }, - toBeSignedOut: (args?: { timeOut: number }) => { - return page.waitForFunction( - () => { - return !window.Clerk?.user; - }, - null, - { timeout: args?.timeOut }, - ); - }, - toBeSignedIn: async () => { - return page.waitForFunction(() => { - return !!window.Clerk?.user; - }); - }, - toHaveResolvedTask: async () => { - return page.waitForFunction(() => { - return !window.Clerk?.session?.currentTask; - }); - }, - }; -}; - -const createClerkUtils = ({ page }: TestArgs) => { - return { - toBeLoaded: async () => { - return page.waitForFunction(() => { - return !!window.Clerk?.loaded; - }); - }, - getClientSideUser: () => { - return page.evaluate(() => { - return window.Clerk?.user; - }); - }, - }; -}; - -const createTestingTokenUtils = ({ page }: TestArgs) => { - return { - setup: async () => setupClerkTestingToken({ page }), - }; -}; - export type CreateAppPageObjectArgs = { page: Page; context: BrowserContext; browser: Browser }; export const createTestUtils = < @@ -89,7 +26,7 @@ export const createTestUtils = < Services = typeof services, PO = typeof pageObjects, BH = typeof browserHelpers, - FullReturn = { services: Services; po: PO; tabs: BH; page: EnchancedPage; nextJsVersion: string }, + FullReturn = { services: Services; po: PO; tabs: BH; page: EnhancedPage; nextJsVersion: string }, OnlyAppReturn = { services: Services }, >( params: Params, @@ -109,27 +46,11 @@ export const createTestUtils = < return { services } as any; } - const page = createAppPageObject({ page: params.page, useTestingToken }, app); - const testArgs = { page, context, browser }; - - const pageObjects = { - clerk: createClerkUtils(testArgs), - expect: createExpectPageObject(testArgs), - keylessPopover: createKeylessPopoverPageObject(testArgs), - organizationSwitcher: createOrganizationSwitcherComponentPageObject(testArgs), - sessionTask: createSessionTaskComponentPageObject(testArgs), - signIn: createSignInComponentPageObject(testArgs), - signUp: createSignUpComponentPageObject(testArgs), - testingToken: createTestingTokenUtils(testArgs), - userButton: createUserButtonPageObject(testArgs), - userProfile: createUserProfileComponentPageObject(testArgs), - userVerification: createUserVerificationComponentPageObject(testArgs), - waitlist: createWaitlistComponentPageObject(testArgs), - }; + const pageObjects = createPageObjects(params.page, { useTestingToken, serverUrl: app.serverUrl }); const browserHelpers = { runInNewTab: async ( - cb: (u: { services: Services; po: PO; page: EnchancedPage }, context: BrowserContext) => Promise, + cb: (u: { services: Services; po: PO; page: EnhancedPage }, context: BrowserContext) => Promise, ) => { const u = createTestUtils({ app, @@ -139,7 +60,7 @@ export const createTestUtils = < return u; }, runInNewBrowser: async ( - cb: (u: { services: Services; po: PO; page: EnchancedPage }, context: BrowserContext) => Promise, + cb: (u: { services: Services; po: PO; page: EnhancedPage }, context: BrowserContext) => Promise, ) => { if (!browser) { throw new Error('Browser is not defined. Did you forget to pass it to createPageObjects?'); @@ -155,7 +76,7 @@ export const createTestUtils = < }; return { - page, + page: pageObjects.app, services, po: pageObjects, tabs: browserHelpers, diff --git a/packages/testing/package.json b/packages/testing/package.json index a7dd16e7cd3..be18bed437b 100644 --- a/packages/testing/package.json +++ b/packages/testing/package.json @@ -36,6 +36,16 @@ "default": "./dist/playwright/index.js" } }, + "./playwright/unstable": { + "import": { + "types": "./dist/types/playwright/unstable/index.d.ts", + "default": "./dist/playwright/unstable/index.mjs" + }, + "require": { + "types": "./dist/types/playwright/unstable/index.d.ts", + "default": "./dist/playwright/unstable/index.js" + } + }, "./cypress": { "import": { "types": "./dist/types/cypress/index.d.ts", diff --git a/packages/testing/src/playwright/unstable/index.ts b/packages/testing/src/playwright/unstable/index.ts new file mode 100644 index 00000000000..45c1df6d301 --- /dev/null +++ b/packages/testing/src/playwright/unstable/index.ts @@ -0,0 +1,5 @@ +import { createPageObjects } from './page-objects'; +import { createAppPageObject } from './page-objects/app'; + +export type { EnhancedPage } from './page-objects/app'; +export { createPageObjects, createAppPageObject }; diff --git a/integration/testUtils/appPageObject.ts b/packages/testing/src/playwright/unstable/page-objects/app.ts similarity index 92% rename from integration/testUtils/appPageObject.ts rename to packages/testing/src/playwright/unstable/page-objects/app.ts index f27e1e1c342..831a882f290 100644 --- a/integration/testUtils/appPageObject.ts +++ b/packages/testing/src/playwright/unstable/page-objects/app.ts @@ -1,9 +1,11 @@ -import { setupClerkTestingToken } from '@clerk/testing/playwright'; +import { setupClerkTestingToken } from '../../setupClerkTestingToken'; import type { Page } from '@playwright/test'; -import type { Application } from '../models/application'; - -export const createAppPageObject = (testArgs: { page: Page; useTestingToken?: boolean }, app: Application) => { +export type EnhancedPage = ReturnType; +export const createAppPageObject = ( + testArgs: { page: Page; useTestingToken?: boolean }, + app: { serverUrl: string }, +) => { const { page, useTestingToken = true } = testArgs; const appPage = Object.create(page) as Page; const helpers = { diff --git a/packages/testing/src/playwright/unstable/page-objects/clerk.ts b/packages/testing/src/playwright/unstable/page-objects/clerk.ts new file mode 100644 index 00000000000..ea414cbc180 --- /dev/null +++ b/packages/testing/src/playwright/unstable/page-objects/clerk.ts @@ -0,0 +1,16 @@ +import { EnhancedPage } from './app'; + +export const createClerkPageObject = ({ page }: { page: EnhancedPage }) => { + return { + toBeLoaded: async () => { + return page.waitForFunction(() => { + return !!window.Clerk?.loaded; + }); + }, + getClientSideUser: () => { + return page.evaluate(() => { + return window.Clerk?.user; + }); + }, + }; +}; diff --git a/integration/testUtils/commonPageObject.ts b/packages/testing/src/playwright/unstable/page-objects/common.ts similarity index 94% rename from integration/testUtils/commonPageObject.ts rename to packages/testing/src/playwright/unstable/page-objects/common.ts index b0fe8463606..618a0957db4 100644 --- a/integration/testUtils/commonPageObject.ts +++ b/packages/testing/src/playwright/unstable/page-objects/common.ts @@ -1,6 +1,6 @@ -import type { TestArgs } from './signInPageObject'; +import type { EnhancedPage } from './app'; -export const common = ({ page }: TestArgs) => { +export const common = ({ page }: { page: EnhancedPage }) => { const self = { continue: () => { return page.getByRole('button', { name: 'Continue', exact: true }).click(); diff --git a/packages/testing/src/playwright/unstable/page-objects/expect.ts b/packages/testing/src/playwright/unstable/page-objects/expect.ts new file mode 100644 index 00000000000..be5214c6009 --- /dev/null +++ b/packages/testing/src/playwright/unstable/page-objects/expect.ts @@ -0,0 +1,34 @@ +import { expect } from '@playwright/test'; +import type { EnhancedPage } from './app'; +import type { Response } from '@playwright/test'; + +export const createExpectPageObject = ({ page }: { page: EnhancedPage }) => { + return { + toBeHandshake: async (res: Response) => { + // Travel the redirect chain until we find the handshake header + // TODO: Loop through the redirects until we find a handshake header, or timeout trying + const redirect = await res.request().redirectedFrom()?.redirectedFrom()?.response(); + expect(redirect?.status()).toBe(307); + expect(redirect?.headers()['x-clerk-auth-status']).toContain('handshake'); + }, + toBeSignedOut: (args?: { timeOut: number }) => { + return page.waitForFunction( + () => { + return !window.Clerk?.user; + }, + null, + { timeout: args?.timeOut }, + ); + }, + toBeSignedIn: async () => { + return page.waitForFunction(() => { + return !!window.Clerk?.user; + }); + }, + toHaveResolvedTask: async () => { + return page.waitForFunction(() => { + return !window.Clerk?.session?.currentTask; + }); + }, + }; +}; diff --git a/packages/testing/src/playwright/unstable/page-objects/index.ts b/packages/testing/src/playwright/unstable/page-objects/index.ts new file mode 100644 index 00000000000..0a02962ab1f --- /dev/null +++ b/packages/testing/src/playwright/unstable/page-objects/index.ts @@ -0,0 +1,39 @@ +import { createAppPageObject } from './app'; +import type { Page } from '@playwright/test'; + +import { createKeylessPopoverPageObject } from './keylessPopover'; +import { createOrganizationSwitcherComponentPageObject } from './organizationSwitcher'; +import { createSessionTaskComponentPageObject } from './sessionTask'; +import { createSignInComponentPageObject } from './signIn'; +import { createSignUpComponentPageObject } from './signUp'; +import { createUserButtonPageObject } from './userButton'; +import { createUserProfileComponentPageObject } from './userProfile'; +import { createUserVerificationComponentPageObject } from './userVerification'; +import { createWaitlistComponentPageObject } from './waitlist'; +import { createExpectPageObject } from './expect'; +import { createClerkPageObject } from './clerk'; +import { createTestingTokenPageObject } from './testingToken'; + +export const createPageObjects = ( + page: Page, + { useTestingToken, serverUrl }: { useTestingToken?: boolean; serverUrl: string }, +) => { + const app = createAppPageObject({ page, useTestingToken }, { serverUrl }); + const testArgs = { page: app }; + + return { + app, + clerk: createClerkPageObject(testArgs), + expect: createExpectPageObject(testArgs), + keylessPopover: createKeylessPopoverPageObject(testArgs), + organizationSwitcher: createOrganizationSwitcherComponentPageObject(testArgs), + sessionTask: createSessionTaskComponentPageObject(testArgs), + signIn: createSignInComponentPageObject(testArgs), + signUp: createSignUpComponentPageObject(testArgs), + testingToken: createTestingTokenPageObject(testArgs), + userButton: createUserButtonPageObject(testArgs), + userProfile: createUserProfileComponentPageObject(testArgs), + userVerification: createUserVerificationComponentPageObject(testArgs), + waitlist: createWaitlistComponentPageObject(testArgs), + }; +}; diff --git a/integration/testUtils/keylessPopoverPageObject.ts b/packages/testing/src/playwright/unstable/page-objects/keylessPopover.ts similarity index 70% rename from integration/testUtils/keylessPopoverPageObject.ts rename to packages/testing/src/playwright/unstable/page-objects/keylessPopover.ts index 41f9917831f..0dc1e255f72 100644 --- a/integration/testUtils/keylessPopoverPageObject.ts +++ b/packages/testing/src/playwright/unstable/page-objects/keylessPopover.ts @@ -1,11 +1,6 @@ -import type { Browser, BrowserContext } from '@playwright/test'; +import { EnhancedPage } from './app'; -import type { createAppPageObject } from './appPageObject'; - -export type EnchancedPage = ReturnType; -export type TestArgs = { page: EnchancedPage; context: BrowserContext; browser: Browser }; - -export const createKeylessPopoverPageObject = (testArgs: TestArgs) => { +export const createKeylessPopoverPageObject = (testArgs: { page: EnhancedPage }) => { const { page } = testArgs; // TODO: Is this the ID we really want ? const elementId = '#--clerk-keyless-prompt-button'; diff --git a/integration/testUtils/organizationSwitcherPageObject.ts b/packages/testing/src/playwright/unstable/page-objects/organizationSwitcher.ts similarity index 89% rename from integration/testUtils/organizationSwitcherPageObject.ts rename to packages/testing/src/playwright/unstable/page-objects/organizationSwitcher.ts index faa2d86209b..3eaf8be8489 100644 --- a/integration/testUtils/organizationSwitcherPageObject.ts +++ b/packages/testing/src/playwright/unstable/page-objects/organizationSwitcher.ts @@ -1,9 +1,9 @@ import { expect } from '@playwright/test'; -import { common } from './commonPageObject'; -import type { TestArgs } from './signInPageObject'; +import { common } from './common'; +import { EnhancedPage } from './app'; -export const createOrganizationSwitcherComponentPageObject = (testArgs: TestArgs) => { +export const createOrganizationSwitcherComponentPageObject = (testArgs: { page: EnhancedPage }) => { const { page } = testArgs; const self = { diff --git a/integration/testUtils/sessionTaskPageObject.ts b/packages/testing/src/playwright/unstable/page-objects/sessionTask.ts similarity index 70% rename from integration/testUtils/sessionTaskPageObject.ts rename to packages/testing/src/playwright/unstable/page-objects/sessionTask.ts index a9a3d83a857..9aa8f23e4c8 100644 --- a/integration/testUtils/sessionTaskPageObject.ts +++ b/packages/testing/src/playwright/unstable/page-objects/sessionTask.ts @@ -1,15 +1,14 @@ import { expect } from '@playwright/test'; -import { common } from './commonPageObject'; -import type { FakeOrganization } from './organizationsService'; -import type { TestArgs } from './signInPageObject'; +import { common } from './common'; +import type { EnhancedPage } from './app'; -export const createSessionTaskComponentPageObject = (testArgs: TestArgs) => { +export const createSessionTaskComponentPageObject = (testArgs: { page: EnhancedPage }) => { const { page } = testArgs; const self = { ...common(testArgs), - resolveForceOrganizationSelectionTask: async (fakeOrganization: FakeOrganization) => { + resolveForceOrganizationSelectionTask: async (fakeOrganization: { name: string; slug: string }) => { const createOrganizationButton = page.getByRole('button', { name: /create organization/i }); await expect(createOrganizationButton).toBeVisible(); diff --git a/integration/testUtils/signInPageObject.ts b/packages/testing/src/playwright/unstable/page-objects/signIn.ts similarity index 86% rename from integration/testUtils/signInPageObject.ts rename to packages/testing/src/playwright/unstable/page-objects/signIn.ts index 5c000c539e4..e195c331048 100644 --- a/integration/testUtils/signInPageObject.ts +++ b/packages/testing/src/playwright/unstable/page-objects/signIn.ts @@ -1,13 +1,9 @@ -import type { Browser, BrowserContext } from '@playwright/test'; import { expect } from '@playwright/test'; -import type { createAppPageObject } from './appPageObject'; -import { common } from './commonPageObject'; +import { common } from './common'; +import type { EnhancedPage } from './app'; -export type EnchancedPage = ReturnType; -export type TestArgs = { page: EnchancedPage; context: BrowserContext; browser: Browser }; - -export const createSignInComponentPageObject = (testArgs: TestArgs) => { +export const createSignInComponentPageObject = (testArgs: { page: EnhancedPage }) => { const { page } = testArgs; const self = { ...common(testArgs), diff --git a/integration/testUtils/signUpPageObject.ts b/packages/testing/src/playwright/unstable/page-objects/signUp.ts similarity index 87% rename from integration/testUtils/signUpPageObject.ts rename to packages/testing/src/playwright/unstable/page-objects/signUp.ts index d1038c5c9bc..14decf3cba4 100644 --- a/integration/testUtils/signUpPageObject.ts +++ b/packages/testing/src/playwright/unstable/page-objects/signUp.ts @@ -1,5 +1,5 @@ -import { common } from './commonPageObject'; -import type { TestArgs } from './signInPageObject'; +import type { EnhancedPage } from './app'; +import { common } from './common'; type SignUpFormInputs = { email?: string; @@ -11,7 +11,7 @@ type SignUpFormInputs = { legalAccepted?: boolean; }; -export const createSignUpComponentPageObject = (testArgs: TestArgs) => { +export const createSignUpComponentPageObject = (testArgs: { page: EnhancedPage }) => { const { page } = testArgs; const self = { @@ -37,11 +37,11 @@ export const createSignUpComponentPageObject = (testArgs: TestArgs) => { }, signUp: async (opts: SignUpFormInputs) => { if (opts.firstName) { - await self.getFirstNameInput().fill(opts.lastName); + await self.getFirstNameInput().fill(opts.firstName); } if (opts.lastName) { - await self.getLastNameInput().fill(opts.firstName); + await self.getLastNameInput().fill(opts.lastName); } if (opts.email) { diff --git a/packages/testing/src/playwright/unstable/page-objects/testingToken.ts b/packages/testing/src/playwright/unstable/page-objects/testingToken.ts new file mode 100644 index 00000000000..d24e7efcd86 --- /dev/null +++ b/packages/testing/src/playwright/unstable/page-objects/testingToken.ts @@ -0,0 +1,8 @@ +import { setupClerkTestingToken } from '../../setupClerkTestingToken'; +import type { EnhancedPage } from './app'; + +export const createTestingTokenPageObject = ({ page }: { page: EnhancedPage }) => { + return { + setup: async () => setupClerkTestingToken({ page }), + }; +}; diff --git a/integration/testUtils/userButtonPageObject.ts b/packages/testing/src/playwright/unstable/page-objects/userButton.ts similarity index 90% rename from integration/testUtils/userButtonPageObject.ts rename to packages/testing/src/playwright/unstable/page-objects/userButton.ts index 896e203f684..a76733a70c0 100644 --- a/integration/testUtils/userButtonPageObject.ts +++ b/packages/testing/src/playwright/unstable/page-objects/userButton.ts @@ -1,8 +1,7 @@ import { expect } from '@playwright/test'; +import type { EnhancedPage } from './app'; -import type { TestArgs } from './signInPageObject'; - -export const createUserButtonPageObject = (testArgs: TestArgs) => { +export const createUserButtonPageObject = (testArgs: { page: EnhancedPage }) => { const { page } = testArgs; const self = { diff --git a/integration/testUtils/userProfilePageObject.ts b/packages/testing/src/playwright/unstable/page-objects/userProfile.ts similarity index 84% rename from integration/testUtils/userProfilePageObject.ts rename to packages/testing/src/playwright/unstable/page-objects/userProfile.ts index ea0e1f4dea3..d15c9ac8397 100644 --- a/integration/testUtils/userProfilePageObject.ts +++ b/packages/testing/src/playwright/unstable/page-objects/userProfile.ts @@ -1,14 +1,9 @@ -import type { Browser, BrowserContext } from '@playwright/test'; - -import type { createAppPageObject } from './appPageObject'; -import { common } from './commonPageObject'; - -export type EnchancedPage = ReturnType; -export type TestArgs = { page: EnchancedPage; context: BrowserContext; browser: Browser }; +import { common } from './common'; +import type { EnhancedPage } from './app'; export type Sections = 'profile' | 'emailAddresses' | 'username' | 'phoneNumbers' | 'danger'; -export const createUserProfileComponentPageObject = (testArgs: TestArgs) => { +export const createUserProfileComponentPageObject = (testArgs: { page: EnhancedPage }) => { const { page } = testArgs; const self = { ...common(testArgs), diff --git a/integration/testUtils/userVerificationPageObject.ts b/packages/testing/src/playwright/unstable/page-objects/userVerification.ts similarity index 71% rename from integration/testUtils/userVerificationPageObject.ts rename to packages/testing/src/playwright/unstable/page-objects/userVerification.ts index 917b165a9fb..0129563bd82 100644 --- a/integration/testUtils/userVerificationPageObject.ts +++ b/packages/testing/src/playwright/unstable/page-objects/userVerification.ts @@ -1,12 +1,7 @@ -import type { Browser, BrowserContext } from '@playwright/test'; +import { common } from './common'; +import type { EnhancedPage } from './app'; -import type { createAppPageObject } from './appPageObject'; -import { common } from './commonPageObject'; - -export type EnchancedPage = ReturnType; -export type TestArgs = { page: EnchancedPage; context: BrowserContext; browser: Browser }; - -export const createUserVerificationComponentPageObject = (testArgs: TestArgs) => { +export const createUserVerificationComponentPageObject = (testArgs: { page: EnhancedPage }) => { const { page } = testArgs; const self = { ...common(testArgs), diff --git a/integration/testUtils/waitlistPageObject.ts b/packages/testing/src/playwright/unstable/page-objects/waitlist.ts similarity index 84% rename from integration/testUtils/waitlistPageObject.ts rename to packages/testing/src/playwright/unstable/page-objects/waitlist.ts index ef5d106be60..7f75febf20e 100644 --- a/integration/testUtils/waitlistPageObject.ts +++ b/packages/testing/src/playwright/unstable/page-objects/waitlist.ts @@ -1,11 +1,11 @@ -import { common } from './commonPageObject'; -import type { TestArgs } from './signInPageObject'; +import { common } from './common'; +import type { EnhancedPage } from './app'; type WaitlistFormInputs = { email: string; }; -export const createWaitlistComponentPageObject = (testArgs: TestArgs) => { +export const createWaitlistComponentPageObject = (testArgs: { page: EnhancedPage }) => { const { page } = testArgs; const self = { diff --git a/packages/testing/tsconfig.json b/packages/testing/tsconfig.json index 9cb1c600baa..96eec6d4f02 100644 --- a/packages/testing/tsconfig.json +++ b/packages/testing/tsconfig.json @@ -15,6 +15,12 @@ "resolveJsonModule": true, "declarationDir": "dist/types" }, - "include": ["src/index.ts", "src/playwright/index.ts", "src/cypress/index.ts", "src/global.d.ts"], + "include": [ + "src/index.ts", + "src/playwright/index.ts", + "src/playwright/unstable/index.ts", + "src/cypress/index.ts", + "src/global.d.ts" + ], "exclude": ["node_modules"] } diff --git a/packages/testing/tsup.config.ts b/packages/testing/tsup.config.ts index c11058b56c2..b315e083c48 100644 --- a/packages/testing/tsup.config.ts +++ b/packages/testing/tsup.config.ts @@ -7,7 +7,7 @@ export default defineConfig(overrideOptions => { const isProd = overrideOptions.env?.NODE_ENV === 'production'; return { - entry: ['src/playwright/index.ts', 'src/cypress/index.ts', 'src/index.ts'], + entry: ['src/playwright/index.ts', 'src/playwright/unstable/index.ts', 'src/cypress/index.ts', 'src/index.ts'], onSuccess: 'tsc', minify: isProd, clean: true, From f61f008328f4a64f163e791ea9ec288c298cf0f4 Mon Sep 17 00:00:00 2001 From: Dylan Staley <88163+dstaley@users.noreply.github.com> Date: Thu, 17 Apr 2025 12:22:29 -0700 Subject: [PATCH 2/6] chore(testing): lint --- .../testing/src/playwright/unstable/page-objects/app.ts | 3 ++- .../testing/src/playwright/unstable/page-objects/clerk.ts | 2 +- .../src/playwright/unstable/page-objects/expect.ts | 3 ++- .../testing/src/playwright/unstable/page-objects/index.ts | 8 ++++---- .../playwright/unstable/page-objects/keylessPopover.ts | 2 +- .../unstable/page-objects/organizationSwitcher.ts | 2 +- .../src/playwright/unstable/page-objects/sessionTask.ts | 2 +- .../src/playwright/unstable/page-objects/signIn.ts | 2 +- .../src/playwright/unstable/page-objects/userButton.ts | 1 + .../src/playwright/unstable/page-objects/userProfile.ts | 2 +- .../playwright/unstable/page-objects/userVerification.ts | 2 +- .../src/playwright/unstable/page-objects/waitlist.ts | 2 +- 12 files changed, 17 insertions(+), 14 deletions(-) diff --git a/packages/testing/src/playwright/unstable/page-objects/app.ts b/packages/testing/src/playwright/unstable/page-objects/app.ts index 831a882f290..9b32d4ef872 100644 --- a/packages/testing/src/playwright/unstable/page-objects/app.ts +++ b/packages/testing/src/playwright/unstable/page-objects/app.ts @@ -1,6 +1,7 @@ -import { setupClerkTestingToken } from '../../setupClerkTestingToken'; import type { Page } from '@playwright/test'; +import { setupClerkTestingToken } from '../../setupClerkTestingToken'; + export type EnhancedPage = ReturnType; export const createAppPageObject = ( testArgs: { page: Page; useTestingToken?: boolean }, diff --git a/packages/testing/src/playwright/unstable/page-objects/clerk.ts b/packages/testing/src/playwright/unstable/page-objects/clerk.ts index ea414cbc180..a8ebb0772c3 100644 --- a/packages/testing/src/playwright/unstable/page-objects/clerk.ts +++ b/packages/testing/src/playwright/unstable/page-objects/clerk.ts @@ -1,4 +1,4 @@ -import { EnhancedPage } from './app'; +import type { EnhancedPage } from './app'; export const createClerkPageObject = ({ page }: { page: EnhancedPage }) => { return { diff --git a/packages/testing/src/playwright/unstable/page-objects/expect.ts b/packages/testing/src/playwright/unstable/page-objects/expect.ts index be5214c6009..5e46ed4d9ac 100644 --- a/packages/testing/src/playwright/unstable/page-objects/expect.ts +++ b/packages/testing/src/playwright/unstable/page-objects/expect.ts @@ -1,6 +1,7 @@ +import type { Response } from '@playwright/test'; import { expect } from '@playwright/test'; + import type { EnhancedPage } from './app'; -import type { Response } from '@playwright/test'; export const createExpectPageObject = ({ page }: { page: EnhancedPage }) => { return { diff --git a/packages/testing/src/playwright/unstable/page-objects/index.ts b/packages/testing/src/playwright/unstable/page-objects/index.ts index 0a02962ab1f..3b6ff8ac613 100644 --- a/packages/testing/src/playwright/unstable/page-objects/index.ts +++ b/packages/testing/src/playwright/unstable/page-objects/index.ts @@ -1,18 +1,18 @@ -import { createAppPageObject } from './app'; import type { Page } from '@playwright/test'; +import { createAppPageObject } from './app'; +import { createClerkPageObject } from './clerk'; +import { createExpectPageObject } from './expect'; import { createKeylessPopoverPageObject } from './keylessPopover'; import { createOrganizationSwitcherComponentPageObject } from './organizationSwitcher'; import { createSessionTaskComponentPageObject } from './sessionTask'; import { createSignInComponentPageObject } from './signIn'; import { createSignUpComponentPageObject } from './signUp'; +import { createTestingTokenPageObject } from './testingToken'; import { createUserButtonPageObject } from './userButton'; import { createUserProfileComponentPageObject } from './userProfile'; import { createUserVerificationComponentPageObject } from './userVerification'; import { createWaitlistComponentPageObject } from './waitlist'; -import { createExpectPageObject } from './expect'; -import { createClerkPageObject } from './clerk'; -import { createTestingTokenPageObject } from './testingToken'; export const createPageObjects = ( page: Page, diff --git a/packages/testing/src/playwright/unstable/page-objects/keylessPopover.ts b/packages/testing/src/playwright/unstable/page-objects/keylessPopover.ts index 0dc1e255f72..69b5bbd4728 100644 --- a/packages/testing/src/playwright/unstable/page-objects/keylessPopover.ts +++ b/packages/testing/src/playwright/unstable/page-objects/keylessPopover.ts @@ -1,4 +1,4 @@ -import { EnhancedPage } from './app'; +import type { EnhancedPage } from './app'; export const createKeylessPopoverPageObject = (testArgs: { page: EnhancedPage }) => { const { page } = testArgs; diff --git a/packages/testing/src/playwright/unstable/page-objects/organizationSwitcher.ts b/packages/testing/src/playwright/unstable/page-objects/organizationSwitcher.ts index 3eaf8be8489..781cb3930c1 100644 --- a/packages/testing/src/playwright/unstable/page-objects/organizationSwitcher.ts +++ b/packages/testing/src/playwright/unstable/page-objects/organizationSwitcher.ts @@ -1,7 +1,7 @@ import { expect } from '@playwright/test'; +import type { EnhancedPage } from './app'; import { common } from './common'; -import { EnhancedPage } from './app'; export const createOrganizationSwitcherComponentPageObject = (testArgs: { page: EnhancedPage }) => { const { page } = testArgs; diff --git a/packages/testing/src/playwright/unstable/page-objects/sessionTask.ts b/packages/testing/src/playwright/unstable/page-objects/sessionTask.ts index 9aa8f23e4c8..c5721982326 100644 --- a/packages/testing/src/playwright/unstable/page-objects/sessionTask.ts +++ b/packages/testing/src/playwright/unstable/page-objects/sessionTask.ts @@ -1,7 +1,7 @@ import { expect } from '@playwright/test'; -import { common } from './common'; import type { EnhancedPage } from './app'; +import { common } from './common'; export const createSessionTaskComponentPageObject = (testArgs: { page: EnhancedPage }) => { const { page } = testArgs; diff --git a/packages/testing/src/playwright/unstable/page-objects/signIn.ts b/packages/testing/src/playwright/unstable/page-objects/signIn.ts index e195c331048..3196aab6785 100644 --- a/packages/testing/src/playwright/unstable/page-objects/signIn.ts +++ b/packages/testing/src/playwright/unstable/page-objects/signIn.ts @@ -1,7 +1,7 @@ import { expect } from '@playwright/test'; -import { common } from './common'; import type { EnhancedPage } from './app'; +import { common } from './common'; export const createSignInComponentPageObject = (testArgs: { page: EnhancedPage }) => { const { page } = testArgs; diff --git a/packages/testing/src/playwright/unstable/page-objects/userButton.ts b/packages/testing/src/playwright/unstable/page-objects/userButton.ts index a76733a70c0..044f8de5d27 100644 --- a/packages/testing/src/playwright/unstable/page-objects/userButton.ts +++ b/packages/testing/src/playwright/unstable/page-objects/userButton.ts @@ -1,4 +1,5 @@ import { expect } from '@playwright/test'; + import type { EnhancedPage } from './app'; export const createUserButtonPageObject = (testArgs: { page: EnhancedPage }) => { diff --git a/packages/testing/src/playwright/unstable/page-objects/userProfile.ts b/packages/testing/src/playwright/unstable/page-objects/userProfile.ts index d15c9ac8397..82b10938075 100644 --- a/packages/testing/src/playwright/unstable/page-objects/userProfile.ts +++ b/packages/testing/src/playwright/unstable/page-objects/userProfile.ts @@ -1,5 +1,5 @@ -import { common } from './common'; import type { EnhancedPage } from './app'; +import { common } from './common'; export type Sections = 'profile' | 'emailAddresses' | 'username' | 'phoneNumbers' | 'danger'; diff --git a/packages/testing/src/playwright/unstable/page-objects/userVerification.ts b/packages/testing/src/playwright/unstable/page-objects/userVerification.ts index 0129563bd82..1b9679f0552 100644 --- a/packages/testing/src/playwright/unstable/page-objects/userVerification.ts +++ b/packages/testing/src/playwright/unstable/page-objects/userVerification.ts @@ -1,5 +1,5 @@ -import { common } from './common'; import type { EnhancedPage } from './app'; +import { common } from './common'; export const createUserVerificationComponentPageObject = (testArgs: { page: EnhancedPage }) => { const { page } = testArgs; diff --git a/packages/testing/src/playwright/unstable/page-objects/waitlist.ts b/packages/testing/src/playwright/unstable/page-objects/waitlist.ts index 7f75febf20e..ee3a730f076 100644 --- a/packages/testing/src/playwright/unstable/page-objects/waitlist.ts +++ b/packages/testing/src/playwright/unstable/page-objects/waitlist.ts @@ -1,5 +1,5 @@ -import { common } from './common'; import type { EnhancedPage } from './app'; +import { common } from './common'; type WaitlistFormInputs = { email: string; From 2cf4a7a5c1951f86c7cd558a612055f4c5e8a74d Mon Sep 17 00:00:00 2001 From: Dylan Staley <88163+dstaley@users.noreply.github.com> Date: Thu, 17 Apr 2025 15:59:49 -0700 Subject: [PATCH 3/6] fix(testing): Use one arg for createPageObjects --- integration/testUtils/index.ts | 8 ++--- .../playwright/unstable/page-objects/app.ts | 29 ++++++++++++++----- .../playwright/unstable/page-objects/index.ts | 17 +++++++---- 3 files changed, 36 insertions(+), 18 deletions(-) diff --git a/integration/testUtils/index.ts b/integration/testUtils/index.ts index 9de1ed961be..a02fec69200 100644 --- a/integration/testUtils/index.ts +++ b/integration/testUtils/index.ts @@ -46,7 +46,7 @@ export const createTestUtils = < return { services } as any; } - const pageObjects = createPageObjects(params.page, { useTestingToken, serverUrl: app.serverUrl }); + const pageObjects = createPageObjects({ page: params.page, useTestingToken, baseURL: app.serverUrl }); const browserHelpers = { runInNewTab: async ( @@ -54,7 +54,7 @@ export const createTestUtils = < ) => { const u = createTestUtils({ app, - page: createAppPageObject({ page: await context.newPage(), useTestingToken }, app), + page: createAppPageObject({ page: await context.newPage(), useTestingToken }, { baseURL: app.serverUrl }), }); await cb(u as any, context); return u; @@ -68,7 +68,7 @@ export const createTestUtils = < const context = await browser.newContext(); const u = createTestUtils({ app, - page: createAppPageObject({ page: await context.newPage(), useTestingToken }, app), + page: createAppPageObject({ page: await context.newPage(), useTestingToken }, { baseURL: app.serverUrl }), }); await cb(u as any, context); return u; @@ -76,7 +76,7 @@ export const createTestUtils = < }; return { - page: pageObjects.app, + page: pageObjects.page, services, po: pageObjects, tabs: browserHelpers, diff --git a/packages/testing/src/playwright/unstable/page-objects/app.ts b/packages/testing/src/playwright/unstable/page-objects/app.ts index 9b32d4ef872..b578844b054 100644 --- a/packages/testing/src/playwright/unstable/page-objects/app.ts +++ b/packages/testing/src/playwright/unstable/page-objects/app.ts @@ -3,20 +3,23 @@ import type { Page } from '@playwright/test'; import { setupClerkTestingToken } from '../../setupClerkTestingToken'; export type EnhancedPage = ReturnType; -export const createAppPageObject = ( - testArgs: { page: Page; useTestingToken?: boolean }, - app: { serverUrl: string }, -) => { +export const createAppPageObject = (testArgs: { page: Page; useTestingToken?: boolean }, app: { baseURL?: string }) => { const { page, useTestingToken = true } = testArgs; const appPage = Object.create(page) as Page; const helpers = { goToAppHome: async () => { + if (!app.baseURL) { + throw new Error( + 'Attempted to call method requiring baseURL, but baseURL was not provided to createPageObjects.', + ); + } + try { if (useTestingToken) { await setupClerkTestingToken({ page }); } - await page.goto(app.serverUrl); + await page.goto(app.baseURL); } catch { // do not fail the test if interstitial is returned (401) } @@ -25,13 +28,18 @@ export const createAppPageObject = ( path: string, opts: { waitUntil?: any; searchParams?: URLSearchParams; timeout?: number } = {}, ) => { + if (!app.baseURL) { + throw new Error( + 'Attempted to call method requiring baseURL, but baseURL was not provided to createPageObjects.', + ); + } let url: URL; try { // When testing applications using real domains we want to manually navigate to the domain first // and not follow serverUrl (localhost) by default, as this is usually proxied if (page.url().includes('about:blank')) { - url = new URL(path, app.serverUrl); + url = new URL(path, app.baseURL); } else { url = new URL(path, page.url()); } @@ -40,7 +48,7 @@ export const createAppPageObject = ( // as the test is using a localhost app directly // This handles the case where the page is at about:blank // and instead it uses the serverUrl - url = new URL(path, app.serverUrl); + url = new URL(path, app.baseURL); } if (opts.searchParams) { @@ -67,7 +75,12 @@ export const createAppPageObject = ( return page.waitForSelector('.cl-rootBox', { state: 'attached' }); }, waitForAppUrl: async (relativePath: string) => { - return page.waitForURL(new URL(relativePath, app.serverUrl).toString()); + if (!app.baseURL) { + throw new Error( + 'Attempted to call method requiring baseURL, but baseURL was not provided to createPageObjects.', + ); + } + return page.waitForURL(new URL(relativePath, app.baseURL).toString()); }, /** * Get the cookies for the URL the page is currently at. diff --git a/packages/testing/src/playwright/unstable/page-objects/index.ts b/packages/testing/src/playwright/unstable/page-objects/index.ts index 3b6ff8ac613..be7387c4d8e 100644 --- a/packages/testing/src/playwright/unstable/page-objects/index.ts +++ b/packages/testing/src/playwright/unstable/page-objects/index.ts @@ -14,15 +14,20 @@ import { createUserProfileComponentPageObject } from './userProfile'; import { createUserVerificationComponentPageObject } from './userVerification'; import { createWaitlistComponentPageObject } from './waitlist'; -export const createPageObjects = ( - page: Page, - { useTestingToken, serverUrl }: { useTestingToken?: boolean; serverUrl: string }, -) => { - const app = createAppPageObject({ page, useTestingToken }, { serverUrl }); +export const createPageObjects = ({ + page, + useTestingToken = true, + baseURL, +}: { + page: Page; + useTestingToken?: boolean; + baseURL?: string; +}) => { + const app = createAppPageObject({ page, useTestingToken }, { baseURL }); const testArgs = { page: app }; return { - app, + page: app, clerk: createClerkPageObject(testArgs), expect: createExpectPageObject(testArgs), keylessPopover: createKeylessPopoverPageObject(testArgs), From 0a7a45de25942208267ba5c192ea144997ff4566 Mon Sep 17 00:00:00 2001 From: Dylan Staley <88163+dstaley@users.noreply.github.com> Date: Thu, 17 Apr 2025 16:37:15 -0700 Subject: [PATCH 4/6] feat(testing): Add impersonation pageObject --- .../src/playwright/unstable/page-objects/clerk.ts | 5 +++++ .../src/playwright/unstable/page-objects/expect.ts | 5 +++++ .../unstable/page-objects/impersonation.ts | 14 ++++++++++++++ .../src/playwright/unstable/page-objects/index.ts | 2 ++ 4 files changed, 26 insertions(+) create mode 100644 packages/testing/src/playwright/unstable/page-objects/impersonation.ts diff --git a/packages/testing/src/playwright/unstable/page-objects/clerk.ts b/packages/testing/src/playwright/unstable/page-objects/clerk.ts index a8ebb0772c3..065e9a4e87a 100644 --- a/packages/testing/src/playwright/unstable/page-objects/clerk.ts +++ b/packages/testing/src/playwright/unstable/page-objects/clerk.ts @@ -7,6 +7,11 @@ export const createClerkPageObject = ({ page }: { page: EnhancedPage }) => { return !!window.Clerk?.loaded; }); }, + getClientSideActor: () => { + return page.evaluate(() => { + return window.Clerk?.session?.actor; + }); + }, getClientSideUser: () => { return page.evaluate(() => { return window.Clerk?.user; diff --git a/packages/testing/src/playwright/unstable/page-objects/expect.ts b/packages/testing/src/playwright/unstable/page-objects/expect.ts index 5e46ed4d9ac..82ab7834e98 100644 --- a/packages/testing/src/playwright/unstable/page-objects/expect.ts +++ b/packages/testing/src/playwright/unstable/page-objects/expect.ts @@ -26,6 +26,11 @@ export const createExpectPageObject = ({ page }: { page: EnhancedPage }) => { return !!window.Clerk?.user; }); }, + toBeSignedInAsActor: async () => { + return page.waitForFunction(() => { + return !!window.Clerk?.session?.actor; + }); + }, toHaveResolvedTask: async () => { return page.waitForFunction(() => { return !window.Clerk?.session?.currentTask; diff --git a/packages/testing/src/playwright/unstable/page-objects/impersonation.ts b/packages/testing/src/playwright/unstable/page-objects/impersonation.ts new file mode 100644 index 00000000000..10eeff5b9be --- /dev/null +++ b/packages/testing/src/playwright/unstable/page-objects/impersonation.ts @@ -0,0 +1,14 @@ +import type { EnhancedPage } from './app'; + +export const createImpersonationPageObject = (testArgs: { page: EnhancedPage }) => { + const { page } = testArgs; + const self = { + waitForMounted: (selector = '.cl-impersonationFab') => { + return page.waitForSelector(selector, { state: 'attached' }); + }, + getSignOutLink: () => { + return page.locator('.cl-impersonationFab').getByText('Sign out'); + }, + }; + return self; +}; diff --git a/packages/testing/src/playwright/unstable/page-objects/index.ts b/packages/testing/src/playwright/unstable/page-objects/index.ts index be7387c4d8e..484b3075db0 100644 --- a/packages/testing/src/playwright/unstable/page-objects/index.ts +++ b/packages/testing/src/playwright/unstable/page-objects/index.ts @@ -3,6 +3,7 @@ import type { Page } from '@playwright/test'; import { createAppPageObject } from './app'; import { createClerkPageObject } from './clerk'; import { createExpectPageObject } from './expect'; +import { createImpersonationPageObject } from './impersonation'; import { createKeylessPopoverPageObject } from './keylessPopover'; import { createOrganizationSwitcherComponentPageObject } from './organizationSwitcher'; import { createSessionTaskComponentPageObject } from './sessionTask'; @@ -30,6 +31,7 @@ export const createPageObjects = ({ page: app, clerk: createClerkPageObject(testArgs), expect: createExpectPageObject(testArgs), + impersonation: createImpersonationPageObject(testArgs), keylessPopover: createKeylessPopoverPageObject(testArgs), organizationSwitcher: createOrganizationSwitcherComponentPageObject(testArgs), sessionTask: createSessionTaskComponentPageObject(testArgs), From 4ef7ba9f2c55272bc641550e1659a88535b00195 Mon Sep 17 00:00:00 2001 From: Dylan Staley <88163+dstaley@users.noreply.github.com> Date: Mon, 21 Apr 2025 09:59:56 -0700 Subject: [PATCH 5/6] chore(testing): Add changeset --- .changeset/huge-plums-invent.md | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/.changeset/huge-plums-invent.md b/.changeset/huge-plums-invent.md index 98361be6513..bb4bf31c918 100644 --- a/.changeset/huge-plums-invent.md +++ b/.changeset/huge-plums-invent.md @@ -2,4 +2,28 @@ '@clerk/testing': minor --- -Add pageObjects +Add [Playwright page objects](https://playwright.dev/docs/pom) for Clerk functionality. This functionality is directly extraced from the end-to-end integration test suite that Clerk uses to develop Clerk components. While the API is being refined for public consumption, it will be available under the `@clerk/testing/playwright/unstable` import, and is not subject to [SemVer](https://semver.org) compatibility guidelines. + +```ts +import { test } from "@playwright/test"; +import { createPageObjects } from "@clerk/testing/playwright/unstable"; + +test("can sign up with email and password", async (context) => { + const po = createPageObjects(context); + + // Go to sign up page + await po.signUp.goTo(); + + // Fill in sign up form + await po.signUp.signUpWithEmailAndPassword({ + email: 'e2e+clerk_test@example.com', + password: Math.random().toString(36), + }); + + // Verify email + await po.signUp.enterTestOtpCode(); + + // Check if user is signed in + await po.expect.toBeSignedIn(); +}); +``` \ No newline at end of file From 31daafe5430eff8815565ac74cca8d69f4a07d3f Mon Sep 17 00:00:00 2001 From: Dylan Staley <88163+dstaley@users.noreply.github.com> Date: Mon, 21 Apr 2025 10:02:56 -0700 Subject: [PATCH 6/6] fix(testing): typo --- .changeset/huge-plums-invent.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/huge-plums-invent.md b/.changeset/huge-plums-invent.md index bb4bf31c918..d5d0dfa4278 100644 --- a/.changeset/huge-plums-invent.md +++ b/.changeset/huge-plums-invent.md @@ -2,7 +2,7 @@ '@clerk/testing': minor --- -Add [Playwright page objects](https://playwright.dev/docs/pom) for Clerk functionality. This functionality is directly extraced from the end-to-end integration test suite that Clerk uses to develop Clerk components. While the API is being refined for public consumption, it will be available under the `@clerk/testing/playwright/unstable` import, and is not subject to [SemVer](https://semver.org) compatibility guidelines. +Add [Playwright page objects](https://playwright.dev/docs/pom) for Clerk functionality. This functionality is directly extracted from the end-to-end integration test suite that Clerk uses to develop Clerk components. While the API is being refined for public consumption, it will be available under the `@clerk/testing/playwright/unstable` import, and is not subject to [SemVer](https://semver.org) compatibility guidelines. ```ts import { test } from "@playwright/test";