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,