diff --git a/apps/sim/app/extension/sign-in.tsx b/apps/sim/app/extension/sign-in.tsx index e4641ea6011..74963d6697f 100644 --- a/apps/sim/app/extension/sign-in.tsx +++ b/apps/sim/app/extension/sign-in.tsx @@ -99,7 +99,7 @@ function PanelSignIn({ expectedUserId }: ExtensionSignInProps) { > Sign in - {failed && window.location.reload()}>Try again} + window.location.reload()}>{failed ? 'Try again' : 'Refresh'} ) } diff --git a/apps/sim/scripts/test-browser-extension-e2e.ts b/apps/sim/scripts/test-browser-extension-e2e.ts index 5724146db2a..e0a622741b0 100644 --- a/apps/sim/scripts/test-browser-extension-e2e.ts +++ b/apps/sim/scripts/test-browser-extension-e2e.ts @@ -78,6 +78,7 @@ const checks: { const screenshots: string[] = [] const browserErrors: string[] = [] const voiceDiagnostics: Record[] = [] +const signInRecoveryReceipts: Record[] = [] const httpErrors: { path: string; status: number; injected: boolean }[] = [] const injectedRequests = new WeakSet() const pendingRequests = new Set() @@ -534,10 +535,89 @@ try { userId = String(id) await seed() } + let login: Page | null = null + await checkNavigation( + 'Manual refresh recovers sign-in without focus or visibility changes', + async () => { + login = await openPage() + await login.goto(`${base.origin}/login`) + const recovery = await openPanel() + try { + const signedOutSession = recovery.waitForResponse( + (response) => new URL(response.url()).pathname === '/api/auth/get-session' + ) + await recovery.goto(`chrome-extension://${extensionId}/sidepanel.html`) + const app = recovery.frames().find((candidate) => candidate.url().startsWith(base.origin)) + assert(app, 'Recovery panel must load the real app') + await app.getByRole('heading', { name: 'Sign in to Sim', exact: true }).click() + assert.equal(await (await signedOutSession).json(), null) + await expect + .poll( + () => + [...pendingRequests].filter( + (request) => + request.frame().page() === recovery && + new URL(request.url()).pathname === '/api/auth/get-session' + ).length + ) + .toBe(0) + const events = await app.evaluateHandle(() => { + let focus = 0 + let visibility = 0 + const focused = () => focus++ + const visible = () => visibility++ + window.addEventListener('focus', focused) + document.addEventListener('visibilitychange', visible) + return { + snapshot: () => ({ + focus, + visibility, + focused: document.hasFocus(), + visibilityState: document.visibilityState, + }), + dispose: () => { + window.removeEventListener('focus', focused) + document.removeEventListener('visibilitychange', visible) + }, + } + }) + try { + await signInAndSeed() + const beforeRefresh = await events.evaluate((observer) => observer.snapshot()) + signInRecoveryReceipts.push({ phase: 'before-refresh', ...beforeRefresh }) + assert.deepEqual(beforeRefresh, { + focus: 0, + visibility: 0, + focused: true, + visibilityState: 'visible', + }) + await expect( + app.getByRole('heading', { name: 'Sign in to Sim', exact: true }) + ).toBeVisible() + await screenshot('sign-in-manual-refresh', recovery) + const [response] = await Promise.all([ + app.waitForNavigation({ waitUntil: 'load' }), + app.getByRole('button', { name: 'Refresh', exact: true }).click(), + ]) + assert(response, 'Refresh must return a document response') + assert.equal(response.status(), 200) + assert(response.request().isNavigationRequest(), 'Refresh must reload the app document') + await expect( + app.getByRole('link', { name: 'Extension QA alpha', exact: true }) + ).toBeVisible() + signInRecoveryReceipts.push({ phase: 'recovered', documentStatus: response.status() }) + await screenshot('sign-in-manual-recovered', recovery) + } finally { + await events.evaluate((observer) => observer.dispose()).catch(() => {}) + await events.dispose() + } + } finally { + await recovery.close() + } + } + ) await checkNavigation('A real Better Auth session reaches the extension iframe', async () => { - const login = await openPage() - await login.goto(`${base.origin}/login`) - await signInAndSeed() + assert(login, 'The sign-in tab must remain open until returning to the original panel') await login.close() await shell.bringToFront() await expect( @@ -3343,6 +3423,7 @@ try { screenshots, browserErrors, voiceDiagnostics, + signInRecoveryReceipts, failedRequests, pendingRequests: pendingAtFailure, failureState,