Files
Agent-System-Frontend/e2e/surface-recovery.playwright.ts
T
weichen 3a5fbe6e42 Test saved surface recovery through conversation reload
Why: Existing event and helper tests missed the SurfaceMeta to snapshot
reload path that blanked the app after the integration pointer regressed.

Evidence: New restore regressions fail on 1edd872 and pass on compatible
main; independent reviewer replays and real browser contract replay are
recorded in the Dev surface-black-screen-recovery task card.

Impact: Tests only; protects reload, partial failure, newer live revisions,
conversation switching, and two-page browser preview.

Refs: Frontend #68; Dev #454 and #452; Dev surface-black-screen-recovery
2026-10-12 14:20:35 +00:00

61 lines
3.8 KiB
TypeScript

import { expect, test } from '@playwright/test'
import type { Conversation, SurfaceMeta, SurfaceSnapshot } from '../src/contracts'
test.skip(!process.env.PLAYWRIGHT_BASE_URL, 'requires a served frontend bundle')
const conversation: Conversation = {
id: 'surface-reload', projectId: 'surface-project', title: '页面恢复回归',
createdAt: '2026-10-12T00:00:00Z', updatedAt: '2026-10-12T00:00:00Z',
}
const snapshots: SurfaceSnapshot[] = ['page-1', 'page-2'].map((surfaceId, i) => ({
identity: { surfaceId, owner: 'fixture', pluginId: 'generative-ui', conversationId: conversation.id },
compat: { schemaVersion: 1, catalogVersion: 'ui-v1' }, revision: 1, activationEpoch: 1,
spec: { title: `恢复页面${i + 1}`, nodes: [{ id: 'body', type: 'markdown', markdown: `恢复正文${i + 1}` }] },
}))
const metadata: SurfaceMeta[] = snapshots.map(s => ({
surfaceId: s.identity.surfaceId, title: s.spec.title, pluginId: s.identity.pluginId,
revision: s.revision, conversationId: conversation.id, updatedAt: conversation.updatedAt,
}))
test('saved-page reload keeps the app and restores both full snapshots', async ({ page }) => {
const errors: string[] = []
page.on('pageerror', e => errors.push(e.message))
await page.addInitScript(({ id }) => {
localStorage.setItem('as.conv', id)
localStorage.setItem('as.ui', JSON.stringify({ rightTab: 'files', filesView: 'preview', rightCollapsed: false }))
}, { id: conversation.id })
// Replay the two distinct server contracts through the real app. Intercept all
// API requests so this regression can run against a live bundle without writes.
await page.route('**/api/**', async route => {
const request = route.request()
const path = new URL(request.url()).pathname
if (request.method() !== 'GET') return route.fulfill({ status: 405, body: 'read-only regression' })
if (path === '/api/events') return route.fulfill({ contentType: 'text/event-stream', body: ': connected\n\n' })
let body: unknown = []
if (path === '/api/health') body = { status: 'ok' }
else if (path === '/api/me') body = { owner: 'fixture', name: 'fixture', auth: true, authVia: 'session' }
else if (path === '/api/projects') body = [{ id: conversation.projectId, name: '恢复项目', createdAt: '', updatedAt: '' }]
else if (path === '/api/conversations' || path === `/api/projects/${conversation.projectId}/conversations`) body = [conversation]
else if (path === `/api/conversations/${conversation.id}`) body = { conversation, messages: [], runs: [], activeHitl: [], lastSeq: 0 }
else if (path === `/api/conversations/${conversation.id}/surfaces` || path === `/api/projects/${conversation.projectId}/surfaces`) body = metadata
else if (path.startsWith('/api/surfaces/')) {
body = snapshots.find(s => s.identity.surfaceId === path.split('/').at(-1))
} else if (path.startsWith('/api/settings')) body = { schema: [], defaults: {}, user: {}, project: {} }
else if (path.endsWith('/plan') || path.endsWith('/todos')) body = null
await route.fulfill({ contentType: 'application/json', body: JSON.stringify(body) })
})
await page.goto('/')
await expect(page.locator('.preview-pane .surface-view-title')).toHaveText('恢复页面1')
await expect(page.locator('.preview-pane')).toContainText('恢复正文1')
await page.reload()
await expect(page.locator('.preview-pane .surface-view-title')).toHaveText('恢复页面1')
await page.getByRole('button', { name: '返回文件列表' }).click()
await expect(page.locator('[data-surface-row]')).toHaveCount(2)
await page.locator('[data-surface-row="page-2"]').click()
await expect(page.locator('.preview-pane .surface-view-title')).toHaveText('恢复页面2')
await expect(page.locator('.preview-pane')).toContainText('恢复正文2')
expect(errors).toEqual([])
})