Skip to content

feat(repo,testing): Apply testing tokens to E2E tests #5561

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Apr 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .changeset/cute-berries-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'@clerk/testing': patch
---


Update `setupClerk` to handle the explict setting of secret keys via `setupClerk({ secretKey: 'sk_test_***' })`
5 changes: 5 additions & 0 deletions .changeset/rich-breads-play.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@clerk/testing': patch
---

Update `setupClerk` to enable skipping of the automatic loading of dot env files via `setupClerk({ dotenv: false })`
22 changes: 22 additions & 0 deletions integration/models/longRunningApplication.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
import { parsePublishableKey } from '@clerk/shared/keys';
import { clerkSetup } from '@clerk/testing/playwright';

import { awaitableTreekill, fs } from '../scripts';
import type { Application } from './application';
import type { ApplicationConfig } from './applicationConfig';
Expand Down Expand Up @@ -56,6 +59,25 @@ export const longRunningApplication = (params: LongRunningApplicationParams) =>
// will be called by global.setup.ts and by the test runner
// the first time this is called, the app starts and the state is persisted in the state file
init: async () => {
try {
const publishableKey = params.env.publicVariables.get('CLERK_PUBLISHABLE_KEY');
const secretKey = params.env.privateVariables.get('CLERK_SECRET_KEY');
const { instanceType, frontendApi: frontendApiUrl } = parsePublishableKey(publishableKey);

if (instanceType !== 'development') {
console.log('Clerk: skipping setup of testing tokens for non-development instance');
} else {
await clerkSetup({
publishableKey,
frontendApiUrl,
secretKey,
dotenv: false,
});
}
} catch (error) {
console.error('Error setting up testing tokens:', error);
throw error;
}
try {
app = await config.commit();
} catch (error) {
Expand Down
19 changes: 16 additions & 3 deletions integration/testUtils/appPageObject.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,27 @@
import { setupClerkTestingToken } from '@clerk/testing/playwright';
import type { Page } from '@playwright/test';

import type { Application } from '../models/application';

export const createAppPageObject = (testArgs: { page: Page }, app: Application) => {
const { page } = testArgs;
export const createAppPageObject = (testArgs: { page: Page; useTestingToken?: boolean }, app: Application) => {
const { page, useTestingToken = true } = testArgs;
const appPage = Object.create(page) as Page;
const helpers = {
goToAppHome: async () => {
try {
if (useTestingToken) {
await setupClerkTestingToken({ page });
}

await page.goto(app.serverUrl);
} catch {
// do not fail the test if interstitial is returned (401)
}
},
goToRelative: (path: string, opts: { waitUntil?: any; searchParams?: URLSearchParams; timeout?: number } = {}) => {
goToRelative: async (
path: string,
opts: { waitUntil?: any; searchParams?: URLSearchParams; timeout?: number } = {},
) => {
let url: URL;

try {
Expand All @@ -35,6 +43,11 @@ export const createAppPageObject = (testArgs: { page: Page }, app: Application)
if (opts.searchParams) {
url.search = opts.searchParams.toString();
}

if (useTestingToken) {
await setupClerkTestingToken({ page });
}

return page.goto(url.toString(), { timeout: opts.timeout ?? 20000, waitUntil: opts.waitUntil });
},
waitForClerkJsLoaded: async () => {
Expand Down
42 changes: 28 additions & 14 deletions integration/testUtils/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
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';

Expand Down Expand Up @@ -75,55 +76,65 @@ const createClerkUtils = ({ page }: TestArgs) => {
};
};

const createTestingTokenUtils = ({ page }: TestArgs) => {
return {
setup: async () => setupClerkTestingToken({ page }),
};
};

export type CreateAppPageObjectArgs = { page: Page; context: BrowserContext; browser: Browser };

export const createTestUtils = <
Params extends { app: Application } & Partial<CreateAppPageObjectArgs>,
Params extends { app: Application; useTestingToken?: boolean } & Partial<CreateAppPageObjectArgs>,
Services = typeof services,
PO = typeof pageObjects,
BH = typeof browserHelpers,
FullReturn = { services: Services; po: PO; tabs: BH; page: EnchancedPage; nexJsVersion: string },
FullReturn = { services: Services; po: PO; tabs: BH; page: EnchancedPage; nextJsVersion: string },
OnlyAppReturn = { services: Services },
>(
params: Params,
): Params extends Partial<CreateAppPageObjectArgs> ? FullReturn : OnlyAppReturn => {
const { app, context, browser } = params || {};
const { app, context, browser, useTestingToken = true } = params || {};

const clerkClient = createClerkClient(app);
const services = {
clerk: clerkClient,
email: createEmailService(),
users: createUserService(clerkClient),
invitations: createInvitationService(clerkClient),
organizations: createOrganizationsService(clerkClient),
clerk: clerkClient,
};

if (!params.page) {
return { services } as any;
}

const page = createAppPageObject({ page: params.page }, app);
const page = createAppPageObject({ page: params.page, useTestingToken }, app);
const testArgs = { page, context, browser };

const pageObjects = {
clerk: createClerkUtils(testArgs),
expect: createExpectPageObject(testArgs),
keylessPopover: createKeylessPopoverPageObject(testArgs),
signUp: createSignUpComponentPageObject(testArgs),
signIn: createSignInComponentPageObject(testArgs),
userProfile: createUserProfileComponentPageObject(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),
sessionTask: createSessionTaskComponentPageObject(testArgs),
expect: createExpectPageObject(testArgs),
clerk: createClerkUtils(testArgs),
};

const browserHelpers = {
runInNewTab: async (
cb: (u: { services: Services; po: PO; page: EnchancedPage }, context: BrowserContext) => Promise<unknown>,
) => {
const u = createTestUtils({ app, page: createAppPageObject({ page: await context.newPage() }, app) });
const u = createTestUtils({
app,
page: createAppPageObject({ page: await context.newPage(), useTestingToken }, app),
});
await cb(u as any, context);
return u;
},
Expand All @@ -134,7 +145,10 @@ export const createTestUtils = <
throw new Error('Browser is not defined. Did you forget to pass it to createPageObjects?');
}
const context = await browser.newContext();
const u = createTestUtils({ app, page: createAppPageObject({ page: await context.newPage() }, app) });
const u = createTestUtils({
app,
page: createAppPageObject({ page: await context.newPage(), useTestingToken }, app),
});
await cb(u as any, context);
return u;
},
Expand All @@ -146,7 +160,7 @@ export const createTestUtils = <
po: pageObjects,
tabs: browserHelpers,
// eslint-disable-next-line turbo/no-undeclared-env-vars
nexJsVersion: process.env.E2E_NEXTJS_VERSION,
nextJsVersion: process.env.E2E_NEXTJS_VERSION,
} as any;
};

Expand Down
4 changes: 4 additions & 0 deletions integration/tests/email-link.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ const performSignUpVerificationLinkSameDevice = async (
await u.po.signUp.waitForEmailVerificationScreen();
await u.tabs.runInNewTab(async u => {
const verificationLink = await u.services.email.getVerificationLinkForEmailAddress(fakeUser.email);

await u.po.testingToken.setup();
await u.page.goto(verificationLink);
await u.po.expect.toBeSignedIn();
await u.page.close();
Expand All @@ -109,6 +111,8 @@ const performSignUpVerificationLinkDifferentDevice = async (
await u.po.signUp.waitForEmailVerificationScreen();
await u.tabs.runInNewBrowser(async u => {
const verificationLink = await u.services.email.getVerificationLinkForEmailAddress(fakeUser.email);

await u.po.testingToken.setup();
await u.page.goto(verificationLink);
await u.po.expect.toBeSignedOut();
await u.page.pause();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,8 @@ test.describe('multiple apps running on localhost using different Clerk instance
test('sessions are independent between the different apps', async ({ context }) => {
const pages = await Promise.all([context.newPage(), context.newPage()]);
const u = [
createTestUtils({ app: apps[0].app, page: pages[0], context }),
createTestUtils({ app: apps[1].app, page: pages[1], context }),
createTestUtils({ app: apps[0].app, page: pages[0], context, useTestingToken: false }),
createTestUtils({ app: apps[1].app, page: pages[1], context, useTestingToken: false }),
];

await u[0].po.signIn.goTo();
Expand Down Expand Up @@ -86,8 +86,8 @@ test.describe('multiple apps running on localhost using different Clerk instance
test('signing out from the root domains does not affect the sub domain', async ({ context }) => {
const pages = await Promise.all([context.newPage(), context.newPage()]);
const u = [
createTestUtils({ app: apps[0].app, page: pages[0], context }),
createTestUtils({ app: apps[1].app, page: pages[1], context }),
createTestUtils({ app: apps[0].app, page: pages[0], context, useTestingToken: false }),
createTestUtils({ app: apps[1].app, page: pages[1], context, useTestingToken: false }),
];

// signin in tab0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,8 @@ test.describe('multiple apps running on localhost using same Clerk instance @loc
test('the cookies are aligned for the root and sub domains', async ({ context }) => {
const pages = await Promise.all([context.newPage(), context.newPage()]);
const u = [
createTestUtils({ app: apps[0].app, page: pages[0], context }),
createTestUtils({ app: apps[1].app, page: pages[1], context }),
createTestUtils({ app: apps[0].app, page: pages[0], context, useTestingToken: false }),
createTestUtils({ app: apps[1].app, page: pages[1], context, useTestingToken: false }),
];

await u[0].po.signIn.goTo();
Expand Down Expand Up @@ -81,8 +81,8 @@ test.describe('multiple apps running on localhost using same Clerk instance @loc
test('signing out from the root domain affects the sub domain', async ({ context }) => {
const pages = await Promise.all([context.newPage(), context.newPage()]);
const u = [
createTestUtils({ app: apps[0].app, page: pages[0], context }),
createTestUtils({ app: apps[1].app, page: pages[1], context }),
createTestUtils({ app: apps[0].app, page: pages[0], context, useTestingToken: false }),
createTestUtils({ app: apps[1].app, page: pages[1], context, useTestingToken: false }),
];

// sign tab0
Expand Down
4 changes: 2 additions & 2 deletions integration/tests/localhost/localhost-switch-instance.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ test.describe('switching instances on localhost same port @localhost', () => {
// Create app and user for the 1st app
const { app } = await prepareApplication('sessions-dev-1', port);
let page = await context.newPage();
let u = createTestUtils({ app, page, context });
let u = createTestUtils({ app, page, context, useTestingToken: false });
let fakeUser = u.services.users.createFakeUser();
fakeUsers.push(fakeUser);

Expand All @@ -46,7 +46,7 @@ test.describe('switching instances on localhost same port @localhost', () => {
await app.dev({ port });

page = await context.newPage();
u = createTestUtils({ app, page, context });
u = createTestUtils({ app, page, context, useTestingToken: false });
fakeUser = u.services.users.createFakeUser();
fakeUsers.push(fakeUser);
await u.services.users.createBapiUser(fakeUser);
Expand Down
6 changes: 3 additions & 3 deletions integration/tests/next-account-portal/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ type TestParams = {
};

export const testSignIn = async ({ app, page, context, fakeUser }: TestParams) => {
const u = createTestUtils({ app, page, context });
const u = createTestUtils({ app, page, context, useTestingToken: false });
// Begin in localhost
await u.page.goToAppHome();
await u.page.waitForClerkJsLoaded();
Expand Down Expand Up @@ -79,7 +79,7 @@ export const testSignIn = async ({ app, page, context, fakeUser }: TestParams) =
};

export const testSignUp = async ({ app, page, context }: TestParams) => {
const u = createTestUtils({ app, page, context });
const u = createTestUtils({ app, page, context, useTestingToken: false });
const tempUser = u.services.users.createFakeUser({ fictionalEmail: true });

// Begin in localhost
Expand Down Expand Up @@ -148,7 +148,7 @@ export const testSignUp = async ({ app, page, context }: TestParams) => {
};

export const testSSR = async ({ app, page, context, fakeUser }: TestParams) => {
const u = createTestUtils({ app, page, context });
const u = createTestUtils({ app, page, context, useTestingToken: false });

// Begin in localhost
await u.page.goToAppHome();
Expand Down
2 changes: 2 additions & 0 deletions integration/tests/non-secure-context.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ testAgainstRunningApps({ withPattern: ['next.appRouter.withEmailCodes'] })(

test('sign-in flow', async ({ page }) => {
const u = createTestUtils({ app, page });

await u.po.testingToken.setup();
await u.page.goto(`http://${APP_HOST}`, { timeout: 50000 });
await u.po.signIn.goTo();
await u.po.signIn.signInWithEmailAndInstantPassword(fakeUser);
Expand Down
7 changes: 4 additions & 3 deletions integration/tests/resiliency.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withEmailCodes] })('resilienc
await u.po.expect.toBeSignedIn();
});

test('resiliency to not break devBroswer - dummy client and is not created on `/client` 4xx errors', async ({
test('resiliency to not break devBrowser - dummy client and is not created on `/client` 4xx errors', async ({
page,
context,
}) => {
Expand All @@ -100,6 +100,9 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withEmailCodes] })('resilienc
clerk_trace_id: 'some-trace-id',
}),
};

const u = createTestUtils({ app, page, context, useTestingToken: false });

await page.route('**/v1/client?**', route => {
return route.fulfill(response);
});
Expand All @@ -108,8 +111,6 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withEmailCodes] })('resilienc
return route.fulfill(response);
});

const u = createTestUtils({ app, page, context });

const waitForClientImmediately = page.waitForResponse(
response => response.url().includes('/client?') && response.status() === 401,
{ timeout: 3_000 },
Expand Down
1 change: 1 addition & 0 deletions integration/tests/restricted-mode.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,7 @@ export default function Page() {

const invitation = await u.services.invitations.createBapiInvitation(invitedUser.email);

await u.po.testingToken.setup();
await u.page.goto(invitation.url);
await u.po.signUp.waitForMounted();
await expect(u.page.getByText(/Create your account/i).first()).toBeVisible();
Expand Down
1 change: 1 addition & 0 deletions integration/tests/sessions/prod-app-migration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ test.describe('root and subdomain production apps @manual-run', () => {
fakeUsers.push(fakeUser);
await u.services.users.createBapiUser(fakeUser);

await u.po.testingToken.setup();
await u.page.goto(`https://${host}`);
await u.po.signIn.goTo({ timeout: 30000 });
await u.po.signIn.signInWithEmailAndInstantPassword(fakeUser);
Expand Down
Loading