fix(session-export): preserve streamed browser downloads
This commit is contained in:
@@ -52,6 +52,11 @@ async function assertBaselineSucceeded(response: Response, method: string): Prom
|
||||
}
|
||||
|
||||
async function ensureSeedOpen(page: Page): Promise<void> {
|
||||
const welcome = page.locator('[class*="onboardingOverlay"]')
|
||||
if (await welcome.count() > 0) {
|
||||
await welcome.getByRole('button').click()
|
||||
await welcome.waitFor({ state: 'detached', timeout: 15_000 })
|
||||
}
|
||||
const chat = page.getByRole('tab', { name: 'Chat', exact: true })
|
||||
// Search is a collapsed header action; expand it so the input is actionable.
|
||||
const searchButton = page.getByRole('button', { name: 'Search sessions' })
|
||||
@@ -297,7 +302,8 @@ describe('web e2e: navigation & panes over a rich seeded session', () => {
|
||||
}
|
||||
expect(headerBox.x + headerBox.width - (buttonBox.x + buttonBox.width)).toBeLessThanOrEqual(32)
|
||||
const responsePromise = page.waitForResponse(response =>
|
||||
new URL(response.url()).pathname === '/api/session.export', { timeout: 30_000 })
|
||||
response.request().method() === 'HEAD'
|
||||
&& new URL(response.url()).pathname === '/api/session.export', { timeout: 30_000 })
|
||||
const downloadPromise = page.waitForEvent('download', { timeout: 30_000 })
|
||||
await exportButton.click()
|
||||
const response = await responsePromise
|
||||
@@ -315,17 +321,61 @@ describe('web e2e: navigation & panes over a rich seeded session', () => {
|
||||
expect(content).toContain('FIRST_DONE')
|
||||
await dialog.getByText('Close', { exact: true }).click()
|
||||
|
||||
const input = page.locator('textarea').first()
|
||||
const slashDownloadPromise = page.waitForEvent('download', { timeout: 30_000 })
|
||||
await input.fill('/export')
|
||||
await page.getByRole('option', { name: /export/u }).waitFor({ timeout: 10_000 })
|
||||
await input.press('Enter')
|
||||
const slashDownload = await slashDownloadPromise
|
||||
expect(slashDownload.suggestedFilename()).toBe(download.suggestedFilename())
|
||||
await page.getByRole('dialog', { name: 'Session download started' }).waitFor({ timeout: 30_000 })
|
||||
await page.getByRole('dialog', { name: 'Session download started' })
|
||||
.getByText('Close', { exact: true }).click()
|
||||
}, 60_000)
|
||||
const observer = await newEnglishPage(browser)
|
||||
const observerTripwire = watchConsole(observer)
|
||||
const observerSlotErrors: string[] = []
|
||||
let observerDownloads = 0
|
||||
observer.on('download', () => { observerDownloads += 1 })
|
||||
observer.on('console', (message) => {
|
||||
if (message.type() === 'error' && /slot entry crashed/i.test(message.text())) {
|
||||
observerSlotErrors.push(message.text())
|
||||
}
|
||||
})
|
||||
const observerSessionBaseline = baselineResponse(observer, 'session.list')
|
||||
const observerWorkspaceBaseline = baselineResponse(observer, 'workspace.list')
|
||||
const [, observerSessionResponse, observerWorkspaceResponse] = await Promise.all([
|
||||
observer.goto(scaffold.baseUrl, { waitUntil: 'load' }),
|
||||
observerSessionBaseline,
|
||||
observerWorkspaceBaseline,
|
||||
])
|
||||
await Promise.all([
|
||||
assertBaselineSucceeded(observerSessionResponse, 'observer session.list'),
|
||||
assertBaselineSucceeded(observerWorkspaceResponse, 'observer workspace.list'),
|
||||
])
|
||||
await observer.getByText('Ungrouped', { exact: true }).waitFor({ timeout: 30_000 })
|
||||
await ensureSeedOpen(observer)
|
||||
|
||||
try {
|
||||
const input = page.locator('textarea').first()
|
||||
const slashDownloadPromise = page.waitForEvent('download', { timeout: 30_000 })
|
||||
await input.fill('/export')
|
||||
await page.getByRole('option', { name: /export/u }).waitFor({ timeout: 10_000 })
|
||||
await input.press('Enter')
|
||||
const slashDownload = await slashDownloadPromise
|
||||
expect(slashDownload.suggestedFilename()).toBe(download.suggestedFilename())
|
||||
const slashFiles = unzipSync(await readFile(await slashDownload.path()))
|
||||
const slashContent = strFromU8(slashFiles['session.jsonl'] as Uint8Array)
|
||||
const slashEvents = parseSessionLog(slashContent)
|
||||
const exportRun = slashEvents.findLast(event => event.type === 'command/run' && event.data.name === 'export')
|
||||
if (exportRun?.type !== 'command/run') throw new Error('slash ZIP has no export command/run')
|
||||
const exportDone = slashEvents.find(event =>
|
||||
event.type === 'command/done' && event.data.commandId === exportRun.data.commandId)
|
||||
expect(exportDone?.type).toBe('command/done')
|
||||
await page.getByRole('dialog', { name: 'Session download started' }).waitFor({ timeout: 30_000 })
|
||||
await page.getByRole('dialog', { name: 'Session download started' })
|
||||
.getByText('Close', { exact: true }).click()
|
||||
await observer.getByText('Session log download requested.', { exact: true }).waitFor({ timeout: 30_000 })
|
||||
expect(observerDownloads).toBe(0)
|
||||
expect(await observer.getByRole('dialog', { name: 'Session download started' }).count()).toBe(0)
|
||||
expect({
|
||||
pageErrors: observerTripwire.pageErrors,
|
||||
slotErrors: observerSlotErrors,
|
||||
warnings: observerTripwire.warnings,
|
||||
}).toEqual({ pageErrors: [], slotErrors: [], warnings: [] })
|
||||
} finally {
|
||||
await observer.close()
|
||||
}
|
||||
}, 120_000)
|
||||
|
||||
it.skipIf(MODE === 'record')('focuses the ledger by dragging an overview interval', async () => {
|
||||
onTestFailed(() => saveFailureShot(page, 'web-e2e-navigation-timeline'))
|
||||
|
||||
Reference in New Issue
Block a user