-
Notifications
You must be signed in to change notification settings - Fork 391
test multitab token cache failure #6899
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
base: main
Are you sure you want to change the base?
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
@@ -0,0 +1,129 @@ | ||||||||||||||||||||||||||||||
import { expect, test } from '@playwright/test'; | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
import { appConfigs } from '../presets'; | ||||||||||||||||||||||||||||||
import type { FakeUser } from '../testUtils'; | ||||||||||||||||||||||||||||||
import { createTestUtils, testAgainstRunningApps } from '../testUtils'; | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
testAgainstRunningApps({ withEnv: [appConfigs.envs.withEmailCodes] })( | ||||||||||||||||||||||||||||||
'MemoryTokenCache Multi-Tab Integration @generic @nextjs', | ||||||||||||||||||||||||||||||
({ app }) => { | ||||||||||||||||||||||||||||||
test.describe.configure({ mode: 'serial' }); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
let fakeUser: FakeUser; | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
test.beforeAll(async () => { | ||||||||||||||||||||||||||||||
const u = createTestUtils({ app }); | ||||||||||||||||||||||||||||||
fakeUser = u.services.users.createFakeUser({ | ||||||||||||||||||||||||||||||
withPhoneNumber: true, | ||||||||||||||||||||||||||||||
withUsername: true, | ||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||
await u.services.users.createBapiUser(fakeUser); | ||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
test.afterAll(async () => { | ||||||||||||||||||||||||||||||
await fakeUser.deleteIfExists(); | ||||||||||||||||||||||||||||||
await app.teardown(); | ||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
test('MemoryTokenCache multi-tab token sharing', async ({ context }) => { | ||||||||||||||||||||||||||||||
const page1 = await context.newPage(); | ||||||||||||||||||||||||||||||
const page2 = await context.newPage(); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
await page1.goto(app.serverUrl); | ||||||||||||||||||||||||||||||
await page2.goto(app.serverUrl); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
await page1.waitForFunction(() => (window as any).Clerk?.loaded); | ||||||||||||||||||||||||||||||
await page2.waitForFunction(() => (window as any).Clerk?.loaded); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
const u1 = createTestUtils({ app, page: page1 }); | ||||||||||||||||||||||||||||||
await u1.po.signIn.goTo(); | ||||||||||||||||||||||||||||||
await u1.po.signIn.setIdentifier(fakeUser.email); | ||||||||||||||||||||||||||||||
await u1.po.signIn.continue(); | ||||||||||||||||||||||||||||||
await u1.po.signIn.setPassword(fakeUser.password); | ||||||||||||||||||||||||||||||
await u1.po.signIn.continue(); | ||||||||||||||||||||||||||||||
await u1.po.expect.toBeSignedIn(); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
await page1.waitForTimeout(1000); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
await page2.reload(); | ||||||||||||||||||||||||||||||
await page2.waitForFunction(() => (window as any).Clerk?.loaded); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
const u2 = createTestUtils({ app, page: page2 }); | ||||||||||||||||||||||||||||||
await u2.po.expect.toBeSignedIn(); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
const clerkInfo1 = await page1.evaluate(() => { | ||||||||||||||||||||||||||||||
const clerk = (window as any).Clerk; | ||||||||||||||||||||||||||||||
return { | ||||||||||||||||||||||||||||||
hasBroadcastChannel: !!clerk?._broadcastChannel, | ||||||||||||||||||||||||||||||
loaded: clerk?.loaded, | ||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
const clerkInfo2 = await page2.evaluate(() => { | ||||||||||||||||||||||||||||||
const clerk = (window as any).Clerk; | ||||||||||||||||||||||||||||||
return { | ||||||||||||||||||||||||||||||
hasBroadcastChannel: !!clerk?._broadcastChannel, | ||||||||||||||||||||||||||||||
loaded: clerk?.loaded, | ||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
expect(clerkInfo1.loaded).toBe(true); | ||||||||||||||||||||||||||||||
expect(clerkInfo2.loaded).toBe(true); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
expect(clerkInfo1.hasBroadcastChannel).toBe(true); | ||||||||||||||||||||||||||||||
expect(clerkInfo2.hasBroadcastChannel).toBe(true); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
const page1SessionInfo = await page1.evaluate(() => { | ||||||||||||||||||||||||||||||
const clerk = (window as any).Clerk; | ||||||||||||||||||||||||||||||
return { | ||||||||||||||||||||||||||||||
sessionId: clerk?.session?.id, | ||||||||||||||||||||||||||||||
userId: clerk?.user?.id, | ||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
expect(page1SessionInfo.sessionId).toBeDefined(); | ||||||||||||||||||||||||||||||
expect(page1SessionInfo.userId).toBeDefined(); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
await Promise.all([ | ||||||||||||||||||||||||||||||
page1.evaluate(() => (window as any).Clerk.session?.clearCache()), | ||||||||||||||||||||||||||||||
page2.evaluate(() => (window as any).Clerk.session?.clearCache()), | ||||||||||||||||||||||||||||||
]); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
Comment on lines
+87
to
+91
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Guard If - await Promise.all([
- page1.evaluate(() => (window as any).Clerk.session?.clearCache()),
- page2.evaluate(() => (window as any).Clerk.session?.clearCache()),
- ]);
+ await Promise.all([
+ page1.evaluate(async () => {
+ const s = (window as any).Clerk?.session;
+ if (s && typeof s.clearCache === 'function') await s.clearCache();
+ }),
+ page2.evaluate(async () => {
+ const s = (window as any).Clerk?.session;
+ if (s && typeof s.clearCache === 'function') await s.clearCache();
+ }),
+ ]); 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents
|
||||||||||||||||||||||||||||||
// Track token fetch requests to verify only one network call happens | ||||||||||||||||||||||||||||||
const tokenRequests: string[] = []; | ||||||||||||||||||||||||||||||
await context.route('**/v1/client/sessions/*/tokens*', async route => { | ||||||||||||||||||||||||||||||
tokenRequests.push(route.request().url()); | ||||||||||||||||||||||||||||||
await route.continue(); | ||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
const page1Token = await page1.evaluate(async () => { | ||||||||||||||||||||||||||||||
const clerk = (window as any).Clerk; | ||||||||||||||||||||||||||||||
return await clerk.session?.getToken({ skipCache: true }); | ||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
expect(page1Token).toBeTruthy(); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
await page1.waitForTimeout(1500); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
const page2Result = await page2.evaluate(async () => { | ||||||||||||||||||||||||||||||
const clerk = (window as any).Clerk; | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
const token = await clerk.session?.getToken(); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
return { | ||||||||||||||||||||||||||||||
sessionId: clerk?.session?.id, | ||||||||||||||||||||||||||||||
token, | ||||||||||||||||||||||||||||||
userId: clerk?.user?.id, | ||||||||||||||||||||||||||||||
}; | ||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
expect(page2Result.sessionId).toBe(page1SessionInfo.sessionId); | ||||||||||||||||||||||||||||||
expect(page2Result.userId).toBe(page1SessionInfo.userId); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
expect(page2Result.token).toBe(page1Token); | ||||||||||||||||||||||||||||||
|
||||||||||||||||||||||||||||||
// Verify only one token fetch happened (page1), proving page2 got it from BroadcastChannel | ||||||||||||||||||||||||||||||
expect(tokenRequests.length).toBe(1); | ||||||||||||||||||||||||||||||
}); | ||||||||||||||||||||||||||||||
}, | ||||||||||||||||||||||||||||||
); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid truthy “loaded” checks — can pass too early.
waitForFunction(() => Clerk?.loaded)
may resolve immediately ifloaded
is a Promise/object. Check explicitly for=== true
and a ready session.Based on learnings.
Also applies to: 49-50
🤖 Prompt for AI Agents