From 59b6db74bb7ef7cb4d40c13bddf30c77ccc1409c Mon Sep 17 00:00:00 2001 From: weichen Date: Sun, 13 Sep 2026 11:12:03 +0000 Subject: [PATCH] test(e2e): tighten UI-01 zero revision-state 404 assertions Match only hyphenated doc-p template paths, reset logs after login, and expect registered uploads to use server doc-free ledgers. Co-authored-by: Cursor --- web/e2e/document-revision-runtime.spec.ts | 61 ++++++++++++----------- 1 file changed, 31 insertions(+), 30 deletions(-) diff --git a/web/e2e/document-revision-runtime.spec.ts b/web/e2e/document-revision-runtime.spec.ts index 3d06d5ff..9844b627 100644 --- a/web/e2e/document-revision-runtime.spec.ts +++ b/web/e2e/document-revision-runtime.spec.ts @@ -236,15 +236,21 @@ async function sendChat(page: Page, text: string) { await page.getByRole('button', { name: /发送/ }).click(); } -async function assertNoRevisionState404(logs: NetLog[], projectDocPrefix: string) { +/** Assert zero revision-state 404 and zero requests for template doc-p* slots. */ +function assertRevisionStateCleanForNewProject(logs: NetLog[]) { const hits = logs.filter((l) => l.path.includes('/revision-state')); const bad404 = hits.filter((l) => l.status === 404); if (bad404.length) { fail(`revision-state 404 is FAIL: ${JSON.stringify(bad404)}`); } - const templateHits = hits.filter((l) => l.path.includes(projectDocPrefix) || /doc-p\d+-/.test(l.path)); - if (templateHits.length) { - fail(`template/local doc must not request revision-state: ${JSON.stringify(templateHits)}`); + // Match only hyphen template IDs (doc-p123-intake), NOT doc_proj* (underscore). + const docPTemplateHits = hits.filter((l) => /\/documents\/doc-p\d+-[^/]+\/revision-state/.test(l.path)); + if (docPTemplateHits.length !== 0) { + fail(`new project template must issue 0 revision-state for doc-p*: got ${JSON.stringify(docPTemplateHits)}`); + } + const legacyHits = hits.filter((l) => /\/documents\/doc-\d{3}\/revision-state/.test(l.path)); + if (legacyHits.length !== 0) { + fail(`legacy local mock must not request revision-state: ${JSON.stringify(legacyHits)}`); } } @@ -392,16 +398,15 @@ test.describe.serial('document-revision-runtime UI acceptance', () => { test('UI-01 login A → page create project → template hint, no revision-state for doc-p', async () => { const t0 = Date.now(); - logs = []; await login(page, USER_A, FINAL_PASS_A); + // Isolate network evidence to project-create window (exclude login-era noise). + logs = []; await page.getByRole('button', { name: '新建立项会话' }).click(); await expect(page.getByText('发起医疗器械新项目立项')).toBeVisible(); await page.fill('input[placeholder*="无线便携式"]', PROJECT_NAME); - await page.fill('textarea, input[placeholder*="临床"]', '用于骨科微创手术的双频超声吸引与止血,需符合 GB 9706.1 与相关法规要求。').catch(async () => { - const desc = page.locator('textarea').first(); - if ((await desc.count()) === 0) fail('project form missing description field'); - await desc.fill('用于骨科微创手术的双频超声吸引与止血,需符合 GB 9706.1 与相关法规要求。'); - }); + const desc = page.locator('textarea').first(); + if ((await desc.count()) === 0) fail('project form missing required description textarea'); + await desc.fill('用于骨科微创手术的双频超声吸引与止血,需符合 GB 9706.1 与相关法规要求。'); // Select B as member const memberRow = page.locator('div').filter({ hasText: `协作者B-${suffix}` }).first(); if ((await memberRow.count()) === 0) fail('member B not listed in create-project form'); @@ -415,12 +420,7 @@ test.describe.serial('document-revision-runtime UI acceptance', () => { await expect(page.locator('text=版本账本未就绪')).toHaveCount(0); await shot(page, '01-project.png'); - const revHits = logs.filter((l) => l.path.includes('/revision-state')); - const docPHits = revHits.filter((l) => /doc-p\d+-/.test(l.path)); - if (docPHits.length !== 0) { - fail(`new project template must issue 0 revision-state for doc-p*: got ${JSON.stringify(docPHits)}`); - } - assertNoRevisionState404(logs, 'doc-p'); + assertRevisionStateCleanForNewProject(logs); writePhaseState({ projectName: PROJECT_NAME, @@ -433,7 +433,7 @@ test.describe.serial('document-revision-runtime UI acceptance', () => { id: 'UI-01', name: 'create project workbench', input: `login A; create project ${PROJECT_NAME}; assign B`, - actual: 'template hint visible; doc-p revision-state count=0; no 404', + actual: 'template hint visible; doc-p revision-state count=0; no revision-state 404', networkLogs: logs, durationMs: Date.now() - t0, verdict: 'PASS', @@ -856,26 +856,27 @@ test.describe.serial('document-revision-runtime UI acceptance', () => { saveResults(); }); - test('UI-16 local upload orphan must not fake V1 ledger', async () => { + test('UI-16 registered upload uses server doc-free ledger (no doc-upload orphan)', async () => { const t0 = Date.now(); logs = []; - // Switch to uploaded private tab if present const uploadTab = page.getByText(`upload-${suffix}`).or(page.getByText(/attachment/)); - if ((await uploadTab.count()) === 0) fail('upload orphan tab missing (UI-11 must have uploaded)'); + if ((await uploadTab.count()) === 0) fail('upload tab missing (UI-11 must have uploaded via page)'); await uploadTab.first().click(); await expect(page.locator('text=版本账本未就绪')).toHaveCount(0); - // Must not show real Vn ledger for orphan; template/local hint OK - const hasToolbar = await page.locator('[data-testid="doc-rev-version"]').isVisible().catch(() => false); - if (hasToolbar) fail('IndexedDB-only upload must not mount live revision toolbar / fake V1'); - await expect(page.getByTestId('doc-rev-template-hint').or(page.locator('text=本地预览'))).toBeVisible({ - timeout: 10_000, - }); - await shot(page, 'UI-16-orphan-upload.png'); + // Registered upload must mount real toolbar (doc-free), not orphan hint. + await expect(page.locator('[data-testid="doc-rev-version"]')).toBeVisible({ timeout: 30_000 }); + const orphanHits = logs.filter( + (l) => l.path.includes('/revision-state') && /\/documents\/doc-upload-/.test(l.path), + ); + if (orphanHits.length) { + fail(`must not request revision-state for doc-upload orphan: ${JSON.stringify(orphanHits)}`); + } + await shot(page, 'UI-16-registered-upload.png'); recordPass({ id: 'UI-16', - name: 'upload orphan empty ledger', - input: 'open page-uploaded attachment tab', - actual: 'no fake V1; local/template hint', + name: 'registered upload server ledger', + input: 'open page-uploaded attachment after createFreeform registration', + actual: 'toolbar V visible; no doc-upload revision-state', networkLogs: logs, durationMs: Date.now() - t0, verdict: 'PASS',