From 62f444ce15dcf3af1dbdf75a1ca555eddcd55e8b Mon Sep 17 00:00:00 2001 From: WeiCHEN Date: Tue, 22 Sep 2026 22:17:29 +0800 Subject: [PATCH] =?UTF-8?q?fix(e2e):=20add=20approver=20D=20for=20workflow?= =?UTF-8?q?=20=E4=B8=89=E6=9D=83=20without=20inviting=20C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 系统管理员 name collides in member list; unique 审批人D keeps UI-12. --- web/e2e/document-revision-runtime.spec.ts | 60 ++++++++++++++++------- 1 file changed, 41 insertions(+), 19 deletions(-) diff --git a/web/e2e/document-revision-runtime.spec.ts b/web/e2e/document-revision-runtime.spec.ts index db1e6fc5..a03f47fc 100644 --- a/web/e2e/document-revision-runtime.spec.ts +++ b/web/e2e/document-revision-runtime.spec.ts @@ -182,10 +182,12 @@ const suffix = `${Date.now().toString(36)}`; const USER_A = `uia_${suffix}`; const USER_B = `uib_${suffix}`; const USER_C = `uic_${suffix}`; +const USER_D = `uid_${suffix}`; const INIT_PASS = `Init-${suffix}9`; const FINAL_PASS_A = `PassA-${suffix}9`; const FINAL_PASS_B = `PassB-${suffix}9`; const FINAL_PASS_C = `PassC-${suffix}9`; +const FINAL_PASS_D = `PassD-${suffix}9`; const PROJECT_NAME = `UI验收-超声微创系统-${suffix}`; async function login(page: Page, username: string, password: string) { @@ -234,8 +236,31 @@ function dutyRoleSelect(page: Page, placeholder: '作者' | '审核' | '批准') .first(); } -/** Activate workflow with first intake doc under human approval + full 三权. */ -async function activateProjectWorkflow(page: Page) { +async function selectDutyMemberByName( + page: Page, + placeholder: '作者' | '审核' | '批准', + displayName: string, +) { + const sel = dutyRoleSelect(page, placeholder); + await expect(sel).toBeVisible({ timeout: 10_000 }); + const option = sel.locator('option').filter({ hasText: displayName }).first(); + if ((await option.count()) === 0) { + fail(`duty select ${placeholder} missing member ${displayName}`); + } + const value = await option.getAttribute('value'); + if (!value) fail(`duty select ${placeholder} option ${displayName} has empty value`); + await sel.selectOption(value); + await expect(sel).toHaveValue(value); +} + +/** + * Activate workflow with first intake doc under human approval + full 三权. + * Pass display names so UI-13 can rely on B as reviewer (not option-order lottery). + */ +async function activateProjectWorkflow( + page: Page, + duties: { author: string; reviewer: string; Approver: string }, +) { await page.getByRole('button', { name: '配置工作流' }).click(); await goToWorkflowDutyStep(page); const toggles = page.locator('label', { hasText: '纳入人工审批' }); @@ -246,19 +271,9 @@ async function activateProjectWorkflow(page: Page) { } 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 selectDutyMemberByName(page, '作者', duties.author); + await selectDutyMemberByName(page, '审核', duties.reviewer); + await selectDutyMemberByName(page, '批准', duties.approver); await page.getByRole('button', { name: /校验并预览/ }).click(); await expect(page.getByText('激活预览')).toBeVisible({ timeout: 20_000 }); await page.getByRole('button', { name: '确认激活' }).click(); @@ -273,7 +288,7 @@ async function toggleCreateProjectMember( displayName: string, role?: 'reviewer' | 'viewer' | 'editor', ) { - const memberName = projectModal.getByText(displayName, { exact: true }); + const memberName = projectModal.getByText(displayName, { exact: true }).first(); await expect(memberName).toBeVisible({ timeout: 15_000 }); await memberName.locator('xpath=ancestor::div[contains(@style,"cursor: pointer")][1]').click(); if (!role) return; @@ -775,6 +790,7 @@ test.describe.serial('document-revision-runtime UI acceptance', () => { await adminCreateUser(page, USER_A, `编制人A-${suffix}`, INIT_PASS, 'role-tech-lead'); await adminCreateUser(page, USER_B, `协作者B-${suffix}`, INIT_PASS, 'role-engineer'); await adminCreateUser(page, USER_C, `无关人C-${suffix}`, INIT_PASS, 'role-guest'); + await adminCreateUser(page, USER_D, `审批人D-${suffix}`, INIT_PASS, 'role-engineer'); // Full-page AdminConsoleView (not the old fixed modal): exit via 返回工作台. await page.getByRole('button', { name: '返回工作台' }).click(); await expect(page.getByText('新建立项会话')).toBeVisible({ timeout: 15_000 }); @@ -799,6 +815,12 @@ test.describe.serial('document-revision-runtime UI acceptance', () => { await changePasswordFirstLogin(page, INIT_PASS, FINAL_PASS_C); await logoutViaUI(page); + await page.fill('input[placeholder*="系统用户名"]', USER_D); + await page.fill('input[placeholder*="登录密码"]', INIT_PASS); + await page.click('button:has-text("进入医疗协同工作台")'); + await changePasswordFirstLogin(page, INIT_PASS, FINAL_PASS_D); + await logoutViaUI(page); + writePhaseSecrets({ passA: FINAL_PASS_A, passB: FINAL_PASS_B, passC: FINAL_PASS_C }); writePhaseState({ projectName: PROJECT_NAME, @@ -811,7 +833,7 @@ test.describe.serial('document-revision-runtime UI acceptance', () => { id: 'UI-00', name: 'admin creates A/B/C', input: 'admin UI create users + first-login credential reset', - actual: `users ${USER_A},${USER_B},${USER_C} ready`, + actual: `users ${USER_A},${USER_B},${USER_C},${USER_D} ready`, networkLogs: logs.filter((l) => !/password|login/i.test(l.path)), durationMs: Date.now() - t0, verdict: 'PASS', @@ -833,9 +855,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 与相关法规要求。'); - // B = reviewer; 系统管理员 = third member so workflow 三权 can activate (C stays non-member for UI-12). + // B = reviewer; D = third member for workflow 三权 (C stays non-member for UI-12). await toggleCreateProjectMember(projectModal, `协作者B-${suffix}`, 'reviewer'); - await toggleCreateProjectMember(projectModal, '系统管理员', 'viewer'); + await toggleCreateProjectMember(projectModal, `审批人D-${suffix}`, 'viewer'); await projectModal.getByRole('button', { name: /确认立项/ }).click(); await expect(projectModalHeading).toBeHidden({ timeout: 15_000 }); await expect(currentProjectBannerName(page, PROJECT_NAME)).toBeVisible({ timeout: 30_000 });