Skip to content

Commit 29ee848

Browse files
authored
test(e2e): add AP Core 3 migration coverage (#7884)
1 parent 7670ce7 commit 29ee848

6 files changed

Lines changed: 160 additions & 2 deletions

File tree

integration/presets/envs.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,14 @@ const withEmailCodesQuickstart = withEmailCodes
8686
.setEnvVariable('public', 'CLERK_SIGN_IN_URL', '')
8787
.setEnvVariable('public', 'CLERK_SIGN_UP_URL', '');
8888

89+
// Uses staging instance which runs Core 3
90+
const withAPCore3ClerkV5 = environmentConfig()
91+
.setId('withAPCore3ClerkV5')
92+
.setEnvVariable('public', 'CLERK_TELEMETRY_DISABLED', true)
93+
.setEnvVariable('private', 'CLERK_API_URL', 'https://api.clerkstage.dev')
94+
.setEnvVariable('private', 'CLERK_SECRET_KEY', instanceKeys.get('with-billing-staging').sk)
95+
.setEnvVariable('public', 'CLERK_PUBLISHABLE_KEY', instanceKeys.get('with-billing-staging').pk);
96+
8997
// Uses staging instance which runs Core 3
9098
const withAPCore3ClerkV6 = environmentConfig()
9199
.setId('withAPCore3ClerkV6')
@@ -206,6 +214,7 @@ export const envs = {
206214
sessionsProd1,
207215
withAPIKeys,
208216
withAPCore3ClerkLatest,
217+
withAPCore3ClerkV5,
209218
withAPCore3ClerkV6,
210219
withBilling,
211220
withBillingJwtV2,

integration/presets/next.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,11 @@ const appRouterQuickstartV6 = appRouter
3131
.setName('next-app-router-quickstart-v6')
3232
.useTemplate(templates['next-app-router-quickstart-v6']);
3333

34+
const appRouterAPWithClerkNextV5 = appRouterQuickstartV6
35+
.clone()
36+
.setName('next-app-router-ap-clerk-next-v5')
37+
.addDependency('@clerk/nextjs', '5');
38+
3439
const appRouterAPWithClerkNextV6 = appRouterQuickstartV6
3540
.clone()
3641
.setName('next-app-router-ap-clerk-next-v6')
@@ -67,6 +72,7 @@ export const next = {
6772
appRouterTurbo,
6873
appRouterQuickstart,
6974
appRouterAPWithClerkNextLatest,
75+
appRouterAPWithClerkNextV5,
7076
appRouterAPWithClerkNextV6,
7177
appRouterQuickstartV6,
7278
appRouterBundledUI,
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
import { test } from '@playwright/test';
2+
3+
import type { Application } from '../../models/application';
4+
import { appConfigs } from '../../presets';
5+
import type { FakeUser } from '../../testUtils';
6+
import { createTestUtils } from '../../testUtils';
7+
import { testSignIn, testSignUp, testSSR } from './common';
8+
9+
test.describe('Next with ClerkJS V5 <-> Account Portal Core 3 @ap-flows', () => {
10+
test.describe.configure({ mode: 'serial' });
11+
let app: Application;
12+
let fakeUser: FakeUser;
13+
14+
test.beforeAll(async () => {
15+
test.setTimeout(90_000); // Wait for app to be ready
16+
app = await appConfigs.next.appRouterAPWithClerkNextV5.clone().commit();
17+
await app.setup();
18+
await app.withEnv(appConfigs.envs.withAPCore3ClerkV5);
19+
await app.dev();
20+
const u = createTestUtils({ app });
21+
fakeUser = u.services.users.createFakeUser();
22+
await u.services.users.createBapiUser(fakeUser);
23+
});
24+
25+
test.afterAll(async () => {
26+
await fakeUser.deleteIfExists();
27+
await app.teardown();
28+
});
29+
30+
test('sign in', async ({ page, context }) => {
31+
await testSignIn({ app, page, context, fakeUser });
32+
});
33+
34+
test('sign up', async ({ page, context }) => {
35+
await testSignUp({ app, page, context, fakeUser });
36+
});
37+
38+
test('ssr', async ({ page, context }) => {
39+
await testSSR({ app, page, context, fakeUser });
40+
});
41+
});

integration/tests/next-account-portal/clerk-ap-core-3-v6.test.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type { Application } from '../../models/application';
44
import { appConfigs } from '../../presets';
55
import type { FakeUser } from '../../testUtils';
66
import { createTestUtils } from '../../testUtils';
7-
import { testSignIn, testSignUp, testSSR } from './common';
7+
import { testHandshakeRecovery, testSignIn, testSignOut, testSignUp, testSSR } from './common';
88

99
test.describe('Next with ClerkJS V6 <-> Account Portal Core 3 @ap-flows', () => {
1010
test.describe.configure({ mode: 'serial' });
@@ -38,4 +38,12 @@ test.describe('Next with ClerkJS V6 <-> Account Portal Core 3 @ap-flows', () =>
3838
test('ssr', async ({ page, context }) => {
3939
await testSSR({ app, page, context, fakeUser });
4040
});
41+
42+
test('sign out clears session and AP state', async ({ page, context }) => {
43+
await testSignOut({ app, page, context, fakeUser });
44+
});
45+
46+
test('handshake recovery after session cookie loss', async ({ page, context }) => {
47+
await testHandshakeRecovery({ app, page, context, fakeUser });
48+
});
4149
});

integration/tests/next-account-portal/clerk-ap-core-3-v7.test.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import type { Application } from '../../models/application';
44
import { appConfigs } from '../../presets';
55
import type { FakeUser } from '../../testUtils';
66
import { createTestUtils } from '../../testUtils';
7-
import { testSignIn, testSignUp, testSSR } from './common';
7+
import { testHandshakeRecovery, testSignIn, testSignOut, testSignUp, testSSR } from './common';
88

99
test.describe('Next with ClerkJS V7 <-> Account Portal Core 3 @ap-flows', () => {
1010
test.describe.configure({ mode: 'serial' });
@@ -38,4 +38,12 @@ test.describe('Next with ClerkJS V7 <-> Account Portal Core 3 @ap-flows', () =>
3838
test('ssr', async ({ page, context }) => {
3939
await testSSR({ app, page, context, fakeUser });
4040
});
41+
42+
test('sign out clears session and AP state', async ({ page, context }) => {
43+
await testSignOut({ app, page, context, fakeUser });
44+
});
45+
46+
test('handshake recovery after session cookie loss', async ({ page, context }) => {
47+
await testHandshakeRecovery({ app, page, context, fakeUser });
48+
});
4149
});

integration/tests/next-account-portal/common.ts

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -182,3 +182,89 @@ export const testSSR = async ({ app, page, context, fakeUser }: TestParams) => {
182182

183183
expect(await u.po.userButton.waitForMounted()).not.toBeUndefined();
184184
};
185+
186+
export const testSignOut = async ({ app, page, context, fakeUser }: TestParams) => {
187+
const u = createTestUtils({ app, page, context, useTestingToken: false });
188+
189+
// Sign in via Account Portal first
190+
await u.page.goToAppHome();
191+
await u.page.waitForClerkJsLoaded();
192+
await u.po.expect.toBeSignedOut();
193+
194+
await u.page.getByRole('button', { name: /Sign in/i }).click();
195+
await u.po.signIn.waitForMounted();
196+
await u.po.signIn.signInWithEmailAndInstantPassword({ email: fakeUser.email, password: fakeUser.password });
197+
await u.page.waitForAppUrl('/');
198+
await u.po.expect.toBeSignedIn();
199+
await u.po.userButton.waitForMounted();
200+
201+
// Verify session cookie is set before sign-out
202+
const sessionBefore = await context
203+
.cookies(page.url())
204+
.then(cookies => cookies.find(c => c.name === CLERK_SESSION_COOKIE_NAME)?.value);
205+
expect(!!sessionBefore).toBeTruthy();
206+
207+
// Sign out via Clerk.signOut()
208+
await page.evaluate(() => window.Clerk.signOut());
209+
await u.po.expect.toBeSignedOut();
210+
211+
// Verify session cookie is cleared
212+
const sessionAfter = await context
213+
.cookies(page.url())
214+
.then(cookies => cookies.find(c => c.name === CLERK_SESSION_COOKIE_NAME)?.value);
215+
expect(!!sessionAfter).toBeFalsy();
216+
217+
// Reload and verify user stays signed out (no auto-sign-in from stale state)
218+
await u.page.goToAppHome();
219+
await u.page.waitForClerkJsLoaded();
220+
await u.po.expect.toBeSignedOut();
221+
222+
// Navigate to AP again and verify sign-in form is shown (not auto-signed-in)
223+
await u.page.getByRole('button', { name: /Sign in/i }).click();
224+
await u.po.signIn.waitForMounted();
225+
const apURL = page.url();
226+
expect(apURL).toMatch(/\.accounts(stage\.dev|\.dev|\.stg)/);
227+
};
228+
229+
export const testHandshakeRecovery = async ({ app, page, context, fakeUser }: TestParams) => {
230+
const u = createTestUtils({ app, page, context, useTestingToken: false });
231+
232+
// Sign in via Account Portal
233+
await u.page.goToAppHome();
234+
await u.page.waitForClerkJsLoaded();
235+
await u.po.expect.toBeSignedOut();
236+
237+
await u.page.getByRole('button', { name: /Sign in/i }).click();
238+
await u.po.signIn.waitForMounted();
239+
await u.po.signIn.signInWithEmailAndInstantPassword({ email: fakeUser.email, password: fakeUser.password });
240+
await u.page.waitForAppUrl('/');
241+
await u.po.expect.toBeSignedIn();
242+
243+
// Delete the __session cookie to simulate an expired/invalid session.
244+
// Keep __client_uat so the middleware detects a mismatch and triggers a handshake.
245+
const appUrl = new URL(page.url());
246+
await context.clearCookies({ name: CLERK_SESSION_COOKIE_NAME, domain: appUrl.hostname });
247+
248+
// Reload the page. The middleware should:
249+
// 1. Detect missing session + present client_uat
250+
// 2. Trigger a handshake redirect to FAPI
251+
// 3. FAPI resolves the handshake and returns fresh cookies
252+
// 4. User ends up signed in again (no redirect loop, no error)
253+
await u.page.goToAppHome();
254+
await u.page.waitForClerkJsLoaded();
255+
256+
// The page should load successfully (not stuck in a redirect loop).
257+
// The user should be signed in because the handshake recovered the session.
258+
await u.po.expect.toBeSignedIn();
259+
260+
// Verify the session cookie was re-established by the handshake
261+
const sessionAfterRecovery = await context
262+
.cookies(page.url())
263+
.then(cookies => cookies.find(c => c.name === CLERK_SESSION_COOKIE_NAME)?.value);
264+
expect(!!sessionAfterRecovery).toBeTruthy();
265+
266+
// Verify no leftover handshake params in the URL
267+
const finalURL = new URL(page.url());
268+
expect(finalURL.searchParams.has('__clerk_handshake')).toBeFalsy();
269+
expect(finalURL.searchParams.has('__clerk_handshake_nonce')).toBeFalsy();
270+
};

0 commit comments

Comments
 (0)