From 16cb60b3d8a5870d370b4a01778b6c85b2cee37d Mon Sep 17 00:00:00 2001 From: WeiCHEN Date: Tue, 22 Sep 2026 22:16:05 +0800 Subject: [PATCH] fix(e2e): activate workflow in UI-01 before squad tasks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Product gate blocks document tasks until a human-approval workflow is active; add admin as third member for 三权 and drive 配置工作流. --- web/e2e/document-revision-runtime.spec.ts | 91 +++++++++++++++++++---- 1 file changed, 75 insertions(+), 16 deletions(-) diff --git a/web/e2e/document-revision-runtime.spec.ts b/web/e2e/document-revision-runtime.spec.ts index 892b2c45..db1e6fc5 100644 --- a/web/e2e/document-revision-runtime.spec.ts +++ b/web/e2e/document-revision-runtime.spec.ts @@ -218,6 +218,74 @@ async function logoutViaUI(page: Page) { await expect(page.getByPlaceholder(/系统用户名/)).toBeVisible({ timeout: 20_000 }); } +async function goToWorkflowDutyStep(page: Page) { + const approval = page.locator('label', { hasText: '纳入人工审批' }).first(); + const selectHint = page.getByText('选择要激活的已发布工作流版本'); + await expect(selectHint.or(approval)).toBeVisible({ timeout: 20_000 }); + if (await approval.isVisible().catch(() => false)) return; + await page.getByRole('button', { name: /下一步/ }).click(); + await expect(approval).toBeVisible({ timeout: 20_000 }); +} + +function dutyRoleSelect(page: Page, placeholder: '作者' | '审核' | '批准') { + return page + .locator('select') + .filter({ has: page.getByRole('option', { name: placeholder, exact: true }) }) + .first(); +} + +/** Activate workflow with first intake doc under human approval + full 三权. */ +async function activateProjectWorkflow(page: Page) { + await page.getByRole('button', { name: '配置工作流' }).click(); + await goToWorkflowDutyStep(page); + const toggles = page.locator('label', { hasText: '纳入人工审批' }); + const toggleCount = await toggles.count(); + for (let i = 1; i < toggleCount; i += 1) { + const box = toggles.nth(i).locator('input[type="checkbox"]'); + if (await box.isChecked()) await box.uncheck(); + } + const firstToggle = toggles.first().locator('input[type="checkbox"]'); + if (!(await firstToggle.isChecked())) await firstToggle.check(); + const author = dutyRoleSelect(page, '作者'); + await expect(author).toBeVisible({ timeout: 10_000 }); + const values = ( + await author + .locator('option') + .evaluateAll((nodes) => nodes.map((n) => (n as HTMLOptionElement).value)) + ).filter(Boolean); + if (values.length < 3) { + fail(`workflow activate needs ≥3 project members for 三权, got ${values.length}`); + } + await author.selectOption(values[0]); + await dutyRoleSelect(page, '审核').selectOption(values[1]); + await dutyRoleSelect(page, '批准').selectOption(values[2]); + await page.getByRole('button', { name: /校验并预览/ }).click(); + await expect(page.getByText('激活预览')).toBeVisible({ timeout: 20_000 }); + await page.getByRole('button', { name: '确认激活' }).click(); + await expect(page.getByText('运行中')).toBeVisible({ timeout: 20_000 }); + // Close settings if still open. + const closeBtn = page.getByRole('button', { name: '关闭' }); + if (await closeBtn.isVisible().catch(() => false)) await closeBtn.click(); +} + +async function toggleCreateProjectMember( + projectModal: ReturnType, + displayName: string, + role?: 'reviewer' | 'viewer' | 'editor', +) { + const memberName = projectModal.getByText(displayName, { exact: true }); + await expect(memberName).toBeVisible({ timeout: 15_000 }); + await memberName.locator('xpath=ancestor::div[contains(@style,"cursor: pointer")][1]').click(); + if (!role) return; + const memberContainer = memberName.locator( + 'xpath=ancestor::div[contains(@style,"justify-content: space-between")][1]', + ); + const memberRole = memberContainer.locator('select'); + await expect(memberRole).toBeEnabled(); + await memberRole.selectOption(role); + await expect(memberRole).toHaveValue(role); +} + async function openAdminConsole(page: Page) { await shot(page, 'UI-00-admin-logged-in.png'); const btn = page.locator('button[title*="系统管理与准入治理"]'); @@ -765,20 +833,9 @@ test.describe.serial('document-revision-runtime UI acceptance', () => { const desc = projectModal.locator('textarea'); if ((await desc.count()) === 0) fail('project form missing required description textarea'); await desc.fill('用于骨科微创手术的双频超声吸引与止血,需符合 GB 9706.1 与相关法规要求。'); - // Select B through the visible member row, then assign a real project duty. - const memberName = projectModal.getByText(`协作者B-${suffix}`, { exact: true }); - await expect(memberName).toBeVisible({ timeout: 15_000 }); - const memberToggle = memberName.locator( - 'xpath=ancestor::div[contains(@style,"cursor: pointer")][1]', - ); - await memberToggle.click(); - const memberContainer = memberName.locator( - 'xpath=ancestor::div[contains(@style,"justify-content: space-between")][1]', - ); - const memberRole = memberContainer.locator('select'); - await expect(memberRole).toBeEnabled(); - await memberRole.selectOption('reviewer'); - await expect(memberRole).toHaveValue('reviewer'); + // B = reviewer; 系统管理员 = third member so workflow 三权 can activate (C stays non-member for UI-12). + await toggleCreateProjectMember(projectModal, `协作者B-${suffix}`, 'reviewer'); + await toggleCreateProjectMember(projectModal, '系统管理员', 'viewer'); await projectModal.getByRole('button', { name: /确认立项/ }).click(); await expect(projectModalHeading).toBeHidden({ timeout: 15_000 }); await expect(currentProjectBannerName(page, PROJECT_NAME)).toBeVisible({ timeout: 30_000 }); @@ -792,6 +849,8 @@ test.describe.serial('document-revision-runtime UI acceptance', () => { await expect(page.getByTestId('doc-rev-template-hint')).toBeVisible({ timeout: 15_000 }); await expect(page.getByTestId('doc-rev-template-hint')).toContainText('模板预览'); await expect(page.locator('text=版本账本未就绪')).toHaveCount(0); + // Squad tasks require an activated workflow with ≥1 human-approval doc (product gate). + await activateProjectWorkflow(page); await shot(page, '01-project.png'); assertRevisionStateCleanForNewProject(logs); @@ -806,8 +865,8 @@ test.describe.serial('document-revision-runtime UI acceptance', () => { recordPass({ 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 revision-state 404', + input: `login A; create project ${PROJECT_NAME}; assign B+admin; activate workflow`, + actual: 'template hint visible; workflow 运行中; doc-p revision-state count=0; no revision-state 404', networkLogs: logs, durationMs: Date.now() - t0, verdict: 'PASS',