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 <cursoragent@cursor.com>
This commit is contained in:
weichen
2026-09-13 11:12:03 +00:00
co-authored by Cursor
parent 91de320b67
commit 59b6db74bb
+31 -30
View File
@@ -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',