diff --git a/web/e2e/document-revision-runtime.spec.ts b/web/e2e/document-revision-runtime.spec.ts index 47973b4f..bece3cbc 100644 --- a/web/e2e/document-revision-runtime.spec.ts +++ b/web/e2e/document-revision-runtime.spec.ts @@ -269,12 +269,31 @@ async function waitForSquadCompletion( ); const terminal = (await assistant.getAttribute('data-chat-status')) || ''; - if (terminal === 'failed') { - fail('squad terminal status is failed'); + if ( + terminal === 'failed' || + terminal === 'cancelled' || + terminal === 'quality_failed' + ) { + fail(`squad terminal status is ${terminal}`); + } + if (terminal !== 'completed' && terminal !== 'pending_author_confirmation') { + fail(`squad terminal status unexpected: ${terminal}`); } return Date.now() - startedAt; } +async function selectDeliverableCategory( + page: Page, + category: 'workflow' | 'shared' | 'private', +) { + const trigger = page.getByTestId('doc-category-trigger'); + await expect(trigger).toBeVisible({ timeout: 15_000 }); + await trigger.click(); + const item = page.locator(`.dropdown-menu button[data-category="${category}"]`).first(); + if ((await item.count()) === 0) fail(`category menu missing data-category=${category}`); + await item.click(); +} + async function openWorkflowDocByLabel( page: Page, label: RegExp, @@ -408,6 +427,7 @@ test.describe.serial('document-revision-runtime UI acceptance', () => { let versionBeforePending = ''; let secondDocTitle = ''; let secondDocId = ''; + let uploadedDocId = ''; test.beforeAll(async ({ browser }) => { context = await browser.newContext(); @@ -850,21 +870,27 @@ test.describe.serial('document-revision-runtime UI acceptance', () => { const { v, r } = await readVersionBadge(page); states.push(`${target.title}:${v}/${r}`); } - // Page upload attachment + // Page upload attachment — assert registered doc-free id on pane (ignore any chat assistant side effects). const fileInput = page.locator('input[type="file"]'); if ((await fileInput.count()) === 0) fail('no file input for page upload'); const tmp = path.join(ASSETS, `upload-${suffix}.txt`); fs.writeFileSync(tmp, `attachment ${suffix}\n`, 'utf-8'); await fileInput.first().setInputFiles(tmp); - await expect(page.getByText(`upload-${suffix}`).or(page.getByText('attachment'))).toBeVisible({ - timeout: 30_000, - }); + const pane = page.getByTestId('active-document-pane'); + await expect(pane).toHaveAttribute('data-doc-id', /^doc-free-/, { timeout: 45_000 }); + uploadedDocId = (await pane.getAttribute('data-doc-id')) || ''; + if (!uploadedDocId.startsWith('doc-free-')) { + fail(`UI-11 upload must activate doc-free-* pane, got ${uploadedDocId}`); + } + if (uploadedDocId.startsWith('doc-upload')) { + fail(`UI-11 must not leave doc-upload orphan active: ${uploadedDocId}`); + } await shot(page, 'UI-11-multi-doc.png'); recordPass({ id: 'UI-11', name: 'multi doc + upload', input: 'chat second doc; upload file', - actual: states.join('; '), + actual: `${states.join('; ')}; uploadedDocId=${uploadedDocId}`, networkLogs: logs, durationMs: Date.now() - t0, verdict: 'PASS', @@ -1006,11 +1032,19 @@ test.describe.serial('document-revision-runtime UI acceptance', () => { test('UI-16 registered upload uses server doc-free ledger (no doc-upload orphan)', async () => { const t0 = Date.now(); logs = []; - const uploadTab = page.getByText(`upload-${suffix}`).or(page.getByText(/attachment/)); - if ((await uploadTab.count()) === 0) fail('upload tab missing (UI-11 must have uploaded via page)'); - await uploadTab.first().click(); + if (!uploadedDocId || !uploadedDocId.startsWith('doc-free-')) { + fail(`UI-16 requires uploadedDocId from UI-11 (doc-free-*), got ${uploadedDocId || '(empty)'}`); + } + await selectDeliverableCategory(page, 'private'); + await openWorkflowDocById(page, uploadedDocId); + const pane = page.getByTestId('active-document-pane'); + await expect(pane).toHaveAttribute('data-doc-id', uploadedDocId, { timeout: 15_000 }); + await expect(pane).toHaveAttribute('data-doc-id', /^doc-free-/); + const paneId = (await pane.getAttribute('data-doc-id')) || ''; + if (paneId.startsWith('doc-upload')) { + fail(`UI-16 must not open doc-upload orphan: ${paneId}`); + } await expect(page.locator('text=版本账本未就绪')).toHaveCount(0); - // 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), @@ -1022,8 +1056,8 @@ test.describe.serial('document-revision-runtime UI acceptance', () => { recordPass({ id: 'UI-16', name: 'registered upload server ledger', - input: 'open page-uploaded attachment after createFreeform registration', - actual: 'toolbar V visible; no doc-upload revision-state', + input: `category=private; open exact ${uploadedDocId}`, + actual: `pane=${paneId}; toolbar V visible; no doc-upload revision-state`, networkLogs: logs, durationMs: Date.now() - t0, verdict: 'PASS', diff --git a/web/src/components/doc/SlimTabBar.tsx b/web/src/components/doc/SlimTabBar.tsx index 3f2a4329..764df614 100644 --- a/web/src/components/doc/SlimTabBar.tsx +++ b/web/src/components/doc/SlimTabBar.tsx @@ -177,6 +177,7 @@ export const SlimTabBar: React.FC = ({