From bc4d7d06fe35e8beda1a3008ffd8c0464af67642 Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Mon, 28 Sep 2026 15:26:31 -0700 Subject: [PATCH 1/2] test(e2e): cover the protect check in prebuilt sign-in and sign-up --- .changeset/protect-check-e2e.md | 2 + integration/tests/protect-check.test.ts | 106 ++++++++++++++++++++++++ 2 files changed, 108 insertions(+) create mode 100644 .changeset/protect-check-e2e.md create mode 100644 integration/tests/protect-check.test.ts diff --git a/.changeset/protect-check-e2e.md b/.changeset/protect-check-e2e.md new file mode 100644 index 00000000000..a845151cc84 --- /dev/null +++ b/.changeset/protect-check-e2e.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/integration/tests/protect-check.test.ts b/integration/tests/protect-check.test.ts new file mode 100644 index 00000000000..724ec2e634b --- /dev/null +++ b/integration/tests/protect-check.test.ts @@ -0,0 +1,106 @@ +import type { Page } from '@playwright/test'; +import { expect, test } from '@playwright/test'; + +import { appConfigs } from '../presets'; +import type { FakeUser } from '../testUtils'; +import { createTestUtils, testAgainstRunningApps } from '../testUtils'; + +const challengeUrl = 'https://protect-check.e2e.clerk.test/challenge.js'; +const proofToken = 'e2e-proof-token'; + +const challengeScript = ` +export default async function (container, { setWidgetVisible }) { + await setWidgetVisible(true); + const button = document.createElement('button'); + button.textContent = 'Complete challenge'; + container.appendChild(button); + await new Promise(resolve => button.addEventListener('click', resolve, { once: true })); + return '${proofToken}'; +} +`; + +const gateNextCreate = async (page: Page, resource: 'sign_in' | 'sign_up') => { + const endpoint = `/v1/client/${resource}s`; + const submittedProofs: string[] = []; + let ungated: { headers: Record; body: unknown } | undefined; + + await page.route(challengeUrl, route => + route.fulfill({ + contentType: 'text/javascript', + headers: { 'access-control-allow-origin': '*' }, + body: challengeScript, + }), + ); + + await page.route( + url => url.pathname.endsWith(endpoint), + async route => { + if (route.request().method() !== 'POST') { + return route.fallback(); + } + const response = await route.fetch(); + const body = await response.json(); + ungated = { headers: response.headers(), body: structuredClone(body) }; + + const protectCheck = { status: 'pending', token: 'e2e-challenge-token', sdk_url: challengeUrl }; + body.response.protect_check = protectCheck; + body.client[resource].protect_check = protectCheck; + await route.fulfill({ response, json: body }); + }, + { times: 1 }, + ); + + await page.route( + url => url.pathname.includes(`${endpoint}/`) && url.pathname.endsWith('/protect_check'), + async route => { + submittedProofs.push(route.request().postDataJSON().proof_token); + await route.fulfill({ status: 200, headers: ungated!.headers, json: ungated!.body }); + }, + ); + + return submittedProofs; +}; + +testAgainstRunningApps({ withEnv: [appConfigs.envs.withEmailCodes] })('protect check @generic', ({ app }) => { + let fakeUser: FakeUser | undefined; + + test.afterEach(async () => { + await fakeUser?.deleteIfExists(); + fakeUser = undefined; + }); + + test('sign-up completes the challenge and continues to email verification', async ({ page, context }) => { + const u = createTestUtils({ app, page, context }); + fakeUser = u.services.users.createFakeUser(test); + const submittedProofs = await gateNextCreate(page, 'sign_up'); + + await u.po.signUp.goTo(); + await u.po.signUp.signUpWithEmailAndPassword({ email: fakeUser.email!, password: fakeUser.password }); + + await expect(u.page.getByText('Verifying your request')).toBeVisible(); + await u.page.getByRole('button', { name: 'Complete challenge' }).click(); + + await u.po.signUp.enterTestOtpCode(); + await u.po.expect.toBeSignedIn(); + expect(submittedProofs).toEqual([proofToken]); + }); + + test('sign-in completes the challenge and continues to the first factor', async ({ page, context }) => { + const u = createTestUtils({ app, page, context }); + fakeUser = u.services.users.createFakeUser(test); + await u.services.users.createBapiUser(fakeUser); + const submittedProofs = await gateNextCreate(page, 'sign_in'); + + await u.po.signIn.goTo(); + await u.po.signIn.setIdentifier(fakeUser.email!); + await u.po.signIn.continue(); + + await expect(u.page.getByText('Verifying your request')).toBeVisible(); + await u.page.getByRole('button', { name: 'Complete challenge' }).click(); + + await u.po.signIn.setPassword(fakeUser.password); + await u.po.signIn.continue(); + await u.po.expect.toBeSignedIn(); + expect(submittedProofs).toEqual([proofToken]); + }); +}); From d2f610459b10f3980f214f18e8038f7663e719ff Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Mon, 28 Sep 2026 15:35:06 -0700 Subject: [PATCH 2/2] test(e2e): run the protect check against the real challenge --- integration/tests/protect-check.test.ts | 106 ++++++++---------------- 1 file changed, 34 insertions(+), 72 deletions(-) diff --git a/integration/tests/protect-check.test.ts b/integration/tests/protect-check.test.ts index 724ec2e634b..4e230ff9dee 100644 --- a/integration/tests/protect-check.test.ts +++ b/integration/tests/protect-check.test.ts @@ -1,106 +1,68 @@ import type { Page } from '@playwright/test'; import { expect, test } from '@playwright/test'; +import type { Application } from '../models/application'; import { appConfigs } from '../presets'; import type { FakeUser } from '../testUtils'; -import { createTestUtils, testAgainstRunningApps } from '../testUtils'; +import { createTestUtils } from '../testUtils'; -const challengeUrl = 'https://protect-check.e2e.clerk.test/challenge.js'; -const proofToken = 'e2e-proof-token'; - -const challengeScript = ` -export default async function (container, { setWidgetVisible }) { - await setWidgetVisible(true); - const button = document.createElement('button'); - button.textContent = 'Complete challenge'; - container.appendChild(button); - await new Promise(resolve => button.addEventListener('click', resolve, { once: true })); - return '${proofToken}'; -} -`; - -const gateNextCreate = async (page: Page, resource: 'sign_in' | 'sign_up') => { - const endpoint = `/v1/client/${resource}s`; - const submittedProofs: string[] = []; - let ungated: { headers: Record; body: unknown } | undefined; - - await page.route(challengeUrl, route => - route.fulfill({ - contentType: 'text/javascript', - headers: { 'access-control-allow-origin': '*' }, - body: challengeScript, - }), - ); - - await page.route( - url => url.pathname.endsWith(endpoint), - async route => { - if (route.request().method() !== 'POST') { - return route.fallback(); - } - const response = await route.fetch(); - const body = await response.json(); - ungated = { headers: response.headers(), body: structuredClone(body) }; - - const protectCheck = { status: 'pending', token: 'e2e-challenge-token', sdk_url: challengeUrl }; - body.response.protect_check = protectCheck; - body.client[resource].protect_check = protectCheck; - await route.fulfill({ response, json: body }); - }, - { times: 1 }, - ); - - await page.route( - url => url.pathname.includes(`${endpoint}/`) && url.pathname.endsWith('/protect_check'), - async route => { - submittedProofs.push(route.request().postDataJSON().proof_token); - await route.fulfill({ status: 200, headers: ungated!.headers, json: ungated!.body }); - }, +const waitForProtectCheckSubmit = (page: Page) => + page.waitForResponse( + response => response.request().method() === 'POST' && response.url().includes('/protect_check'), + { timeout: 30_000 }, ); - return submittedProofs; -}; +test.describe('protect check @generic', () => { + test.describe.configure({ mode: 'serial' }); -testAgainstRunningApps({ withEnv: [appConfigs.envs.withEmailCodes] })('protect check @generic', ({ app }) => { + let app: Application; let fakeUser: FakeUser | undefined; + test.beforeAll(async () => { + test.setTimeout(150_000); + app = await appConfigs.react.vite.clone().commit(); + await app.setup(); + await app.withEnv(appConfigs.envs.withProtectService); + await app.dev(); + }); + test.afterEach(async () => { await fakeUser?.deleteIfExists(); fakeUser = undefined; }); - test('sign-up completes the challenge and continues to email verification', async ({ page, context }) => { + test.afterAll(async () => { + await app.teardown(); + }); + + test('passes the challenge on sign-up', async ({ page, context }) => { const u = createTestUtils({ app, page, context }); fakeUser = u.services.users.createFakeUser(test); - const submittedProofs = await gateNextCreate(page, 'sign_up'); + const protectCheckSubmit = waitForProtectCheckSubmit(page); await u.po.signUp.goTo(); await u.po.signUp.signUpWithEmailAndPassword({ email: fakeUser.email!, password: fakeUser.password }); - await expect(u.page.getByText('Verifying your request')).toBeVisible(); - await u.page.getByRole('button', { name: 'Complete challenge' }).click(); - + expect((await protectCheckSubmit).ok()).toBe(true); await u.po.signUp.enterTestOtpCode(); await u.po.expect.toBeSignedIn(); - expect(submittedProofs).toEqual([proofToken]); }); - test('sign-in completes the challenge and continues to the first factor', async ({ page, context }) => { + test('passes the challenge on sign-in', async ({ page, context }) => { const u = createTestUtils({ app, page, context }); fakeUser = u.services.users.createFakeUser(test); await u.services.users.createBapiUser(fakeUser); - const submittedProofs = await gateNextCreate(page, 'sign_in'); + const protectCheckSubmit = waitForProtectCheckSubmit(page); await u.po.signIn.goTo(); - await u.po.signIn.setIdentifier(fakeUser.email!); - await u.po.signIn.continue(); - - await expect(u.page.getByText('Verifying your request')).toBeVisible(); - await u.page.getByRole('button', { name: 'Complete challenge' }).click(); - - await u.po.signIn.setPassword(fakeUser.password); - await u.po.signIn.continue(); + await u.po.signIn.signInWithEmailAndInstantPassword({ + email: fakeUser.email!, + password: fakeUser.password, + waitForSession: false, + }); + + expect((await protectCheckSubmit).ok()).toBe(true); + await u.po.signIn.enterTestOtpCode(); await u.po.expect.toBeSignedIn(); - expect(submittedProofs).toEqual([proofToken]); }); });