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
61 lines
3.8 KiB
TypeScript
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([])
|
|
})
|