From 8b80b249623e4d4a98e93e73d80b0f9a75832491 Mon Sep 17 00:00:00 2001 From: Mike Wickett Date: Mon, 26 Oct 2026 17:46:11 -0400 Subject: [PATCH 1/4] test(e2e): stabilize the SSO sign-up and multi-session fixtures The SSO sign-up test now also sets up a testing token for the E2E OAuth Provider instance, whose sign-in runs on the provider's own frontend API. The transitive-state test waits for the second user to become the current user before navigating to the sign-out paths. --- .changeset/e2e-stabilize-fixtures.md | 2 ++ integration/tests/session-tasks-sign-up.test.ts | 12 ++++++++++++ integration/tests/transitive-state.test.ts | 1 + 3 files changed, 15 insertions(+) create mode 100644 .changeset/e2e-stabilize-fixtures.md diff --git a/.changeset/e2e-stabilize-fixtures.md b/.changeset/e2e-stabilize-fixtures.md new file mode 100644 index 00000000000..a845151cc84 --- /dev/null +++ b/.changeset/e2e-stabilize-fixtures.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/integration/tests/session-tasks-sign-up.test.ts b/integration/tests/session-tasks-sign-up.test.ts index 1753a9efeed..3432235aa92 100644 --- a/integration/tests/session-tasks-sign-up.test.ts +++ b/integration/tests/session-tasks-sign-up.test.ts @@ -1,6 +1,7 @@ import { createClerkClient } from '@clerk/backend'; import { expect, test } from '@playwright/test'; +import { environmentConfig } from '../models/environment'; import { appConfigs } from '../presets'; import { resolveInstanceKeys } from '../presets/envs'; import type { FakeUser } from '../testUtils'; @@ -79,6 +80,17 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withSessionTasks] })( test('with sso, navigate to task on after sign-up', async ({ page, context }) => { const u = await createTestUtils({ app, page, context }); + const providerKeys = await resolveInstanceKeys('oauth-provider'); + await createTestUtils({ + app: { + ...app, + env: environmentConfig() + .setEnvVariable('public', 'CLERK_PUBLISHABLE_KEY', providerKeys.pk) + .setEnvVariable('private', 'CLERK_SECRET_KEY', providerKeys.sk), + }, + page, + context, + }); await u.po.signUp.goTo(); await u.page.getByRole('button', { name: 'E2E OAuth Provider' }).click(); diff --git a/integration/tests/transitive-state.test.ts b/integration/tests/transitive-state.test.ts index 17d6566cd43..1026d593476 100644 --- a/integration/tests/transitive-state.test.ts +++ b/integration/tests/transitive-state.test.ts @@ -227,6 +227,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withEmailCodes] })('transitiv await u.po.signIn.continue(); await u.po.signIn.setPassword(userB.password); await u.po.signIn.continue(); + await u.page.waitForFunction(userId => window.Clerk?.user?.id === userId, userBId); const pathA = '/transitive-state/sign-out'; const pathB = '/transitive-state/sign-out/sign-in'; From 8aff70711bee93a8f2e44e50a04e5c0fec3ef68d Mon Sep 17 00:00:00 2001 From: Robert Soriano Date: Mon, 26 Oct 2026 15:28:25 -0700 Subject: [PATCH 2/4] fix(e2e): stamp the testing token on the OAuth callback when consent is skipped (#10438) --- .../oauth-authorize-continue-testing-token.md | 2 ++ integration/testUtils/setupTestingTokens.ts | 14 ++++++++---- .../tests/session-tasks-sign-up.test.ts | 6 ++++- integration/tests/testing-token.test.ts | 22 +++++++++++++++++++ 4 files changed, 39 insertions(+), 5 deletions(-) create mode 100644 .changeset/oauth-authorize-continue-testing-token.md diff --git a/.changeset/oauth-authorize-continue-testing-token.md b/.changeset/oauth-authorize-continue-testing-token.md new file mode 100644 index 00000000000..a845151cc84 --- /dev/null +++ b/.changeset/oauth-authorize-continue-testing-token.md @@ -0,0 +1,2 @@ +--- +--- diff --git a/integration/testUtils/setupTestingTokens.ts b/integration/testUtils/setupTestingTokens.ts index e70a50d9810..b1ac8738ad6 100644 --- a/integration/testUtils/setupTestingTokens.ts +++ b/integration/testUtils/setupTestingTokens.ts @@ -22,15 +22,21 @@ export const setupTestingTokens = async ({ await context.route( url => - (tokens.has(url.origin) && url.pathname.startsWith('/v1/')) || url.pathname.startsWith('/v1/me/oauth/consent/'), + (tokens.has(url.origin) && url.pathname.startsWith('/v1/')) || + url.pathname.startsWith('/v1/me/oauth/consent/') || + url.pathname === '/oauth/authorize/continue', async route => { - const requestUrl = new URL(route.request().url()); - const requestToken = tokens.get(requestUrl.origin); + const request = route.request(); + const requestUrl = new URL(request.url()); + const requestToken = requestUrl.pathname.startsWith('/v1/') ? tokens.get(requestUrl.origin) : undefined; if (requestToken) { requestUrl.searchParams.set('__clerk_testing_token', requestToken); } - if (route.request().method() !== 'POST' || !requestUrl.pathname.startsWith('/v1/me/oauth/consent/')) { + const redirectsToCallback = + (request.method() === 'POST' && requestUrl.pathname.startsWith('/v1/me/oauth/consent/')) || + (request.method() === 'GET' && requestUrl.pathname === '/oauth/authorize/continue'); + if (!redirectsToCallback) { if (!requestToken) { return route.fallback(); } diff --git a/integration/tests/session-tasks-sign-up.test.ts b/integration/tests/session-tasks-sign-up.test.ts index 3432235aa92..3511ceedebb 100644 --- a/integration/tests/session-tasks-sign-up.test.ts +++ b/integration/tests/session-tasks-sign-up.test.ts @@ -101,7 +101,11 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withSessionTasks] })( await u.po.signUp.waitForMounted(); await u.po.signUp.setEmailAddress(fakeUserForOAuth.email); await u.po.signUp.continue(); - await u.po.signUp.enterTestOtpCode(); + const [callback] = await Promise.all([ + page.waitForRequest(request => new URL(request.url()).pathname === '/v1/oauth_callback'), + u.po.signUp.enterTestOtpCode(), + ]); + expect(new URL(callback.url()).searchParams.has('__clerk_testing_token')).toBe(true); // Resolves task await u.po.signUp.waitForMounted(); diff --git a/integration/tests/testing-token.test.ts b/integration/tests/testing-token.test.ts index c230fada14c..ca18fe85a82 100644 --- a/integration/tests/testing-token.test.ts +++ b/integration/tests/testing-token.test.ts @@ -13,6 +13,7 @@ test.describe('OAuth testing tokens @nextjs', () => { let appOrigin: string; let consentPosts: string[]; let consentTokens: (string | null)[]; + let authorizeTokens: (string | null)[]; test.beforeAll(async () => { app = createServer((request, response) => { @@ -37,6 +38,10 @@ test.describe('OAuth testing tokens @nextjs', () => { }); response.end('Consent accepted'); }); + } else if (url.pathname === '/oauth/authorize/continue') { + authorizeTokens.push(url.searchParams.get('__clerk_testing_token')); + response.writeHead(303, { location: url.searchParams.get('callback') || '/' }); + response.end('Authorized'); } else { response.end(JSON.stringify({ url: request.url, method: request.method })); } @@ -49,6 +54,7 @@ test.describe('OAuth testing tokens @nextjs', () => { test.beforeEach(async ({ context }) => { consentPosts = []; consentTokens = []; + authorizeTokens = []; await setupTestingTokens({ context, frontendApiOrigin: appOrigin, token: 'app-testing-token' }); }); @@ -95,6 +101,22 @@ test.describe('OAuth testing tokens @nextjs', () => { expect((await context.cookies(providerOrigin)).map(cookie => cookie.name).sort()).toEqual(['consent', 'provider']); }); + test('delivers the token on the callback when the provider skips the consent screen', async ({ page, context }) => { + await setupTestingTokens({ context, frontendApiOrigin: providerOrigin, token: 'provider-testing-token' }); + const callback = new URL('/v1/oauth_callback?code=auth-code&state=a%2Bb%2F%3D', appOrigin); + const authorize = new URL('/oauth/authorize/continue', providerOrigin); + authorize.searchParams.set('callback', callback.toString()); + await page.goto(authorize.toString()); + + expect(authorizeTokens).toEqual([null]); + const received = JSON.parse((await page.locator('body').textContent()) || '{}'); + expect(Object.fromEntries(new URL(received.url, appOrigin).searchParams)).toEqual({ + code: 'auth-code', + state: 'a+b/=', + __clerk_testing_token: 'app-testing-token', + }); + }); + for (const [name, getCallback] of [ ['another origin', () => new URL('/v1/oauth_callback?code=auth-code', providerOrigin)], ['another path', () => new URL('/sso-callback?code=auth-code', appOrigin)], From 21ed857ddc6a9e497d43b1144db29a384041b0c7 Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Mon, 26 Oct 2026 15:37:46 -0700 Subject: [PATCH 3/4] test(e2e): wait for the task URL after the SSO sign-up callback --- integration/tests/session-tasks-sign-up.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration/tests/session-tasks-sign-up.test.ts b/integration/tests/session-tasks-sign-up.test.ts index 3511ceedebb..94146b53f5c 100644 --- a/integration/tests/session-tasks-sign-up.test.ts +++ b/integration/tests/session-tasks-sign-up.test.ts @@ -108,7 +108,7 @@ testAgainstRunningApps({ withEnv: [appConfigs.envs.withSessionTasks] })( expect(new URL(callback.url()).searchParams.has('__clerk_testing_token')).toBe(true); // Resolves task - await u.po.signUp.waitForMounted(); + await page.waitForURL(/tasks/); const fakeOrganization = Object.assign(u.services.organizations.createFakeOrganization(), { slug: u.services.organizations.createFakeOrganization().slug + '-with-sign-in-sso', }); From c35da792c0cab63ca2cd505410b160fed8537e72 Mon Sep 17 00:00:00 2001 From: wobsoriano Date: Mon, 26 Oct 2026 15:40:17 -0700 Subject: [PATCH 4/4] chore(repo): drop the duplicate empty changeset --- .changeset/oauth-authorize-continue-testing-token.md | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 .changeset/oauth-authorize-continue-testing-token.md diff --git a/.changeset/oauth-authorize-continue-testing-token.md b/.changeset/oauth-authorize-continue-testing-token.md deleted file mode 100644 index a845151cc84..00000000000 --- a/.changeset/oauth-authorize-continue-testing-token.md +++ /dev/null @@ -1,2 +0,0 @@ ---- ----