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([]) })