test(e2e): full-system browser multi-user acceptance
This commit is contained in:
@@ -250,7 +250,8 @@ async function adminCreateUser(
|
||||
async function toggleCreateProjectMember(
|
||||
projectModal: ReturnType<Page['locator']>,
|
||||
displayName: string,
|
||||
role?: 'reviewer' | 'viewer' | 'editor',
|
||||
/** CreateProjectModal options: author|reviewer|approver|viewer (no editor). */
|
||||
role?: 'author' | 'reviewer' | 'approver' | 'viewer',
|
||||
) {
|
||||
const memberName = projectModal.getByText(displayName, { exact: true }).first();
|
||||
await expect(memberName).toBeVisible({ timeout: 15_000 });
|
||||
@@ -264,6 +265,41 @@ async function toggleCreateProjectMember(
|
||||
await memberRole.selectOption(role);
|
||||
}
|
||||
|
||||
/** After create, promote a create-modal viewer to project_role=member (editable, no doc duty). */
|
||||
async function promoteToProjectMember(page: Page, displayName: string) {
|
||||
// LeftRail: title="管理当前项目成员与职责分配" (avoid matching vault "保存" buttons).
|
||||
const openBtn = page.locator('button[title="管理当前项目成员与职责分配"]').first();
|
||||
await expect(openBtn).toBeVisible({ timeout: 15_000 });
|
||||
await openBtn.click();
|
||||
const heading = page.getByText('项目成员与职责权限管理', { exact: true });
|
||||
await expect(heading).toBeVisible({ timeout: 20_000 });
|
||||
const modal = heading.locator('xpath=ancestor::div[contains(@style,"position: fixed")][1]');
|
||||
// Row is flex space-between with the display name + a role <select>.
|
||||
const nameEl = modal.getByText(displayName, { exact: true }).first();
|
||||
await expect(nameEl).toBeVisible({ timeout: 15_000 });
|
||||
const row = nameEl.locator(
|
||||
'xpath=ancestor::div[contains(@style,"justify-content: space-between")][1]',
|
||||
);
|
||||
const roleSelect = row.locator('select').first();
|
||||
await expect(roleSelect).toBeVisible({ timeout: 15_000 });
|
||||
const opts = await roleSelect.locator('option').evaluateAll((nodes) =>
|
||||
nodes.map((n) => ({
|
||||
value: (n as HTMLOptionElement).value,
|
||||
text: (n.textContent || '').trim(),
|
||||
})),
|
||||
);
|
||||
const memberOpt = opts.find((o) => o.value === 'member' || /^项目成员/.test(o.text));
|
||||
if (!memberOpt) {
|
||||
fail(`promoteToProjectMember: no member option in ${JSON.stringify(opts)}`);
|
||||
}
|
||||
// onChange persists immediately — no separate save; do NOT match vault「保存」.
|
||||
await roleSelect.selectOption(memberOpt.value);
|
||||
await expect(roleSelect).toHaveValue('member');
|
||||
await expect(row.getByText('项目成员 (Member)', { exact: true })).toBeVisible({ timeout: 15_000 });
|
||||
await modal.getByRole('button', { name: '完成并关闭' }).click();
|
||||
await expect(heading).toBeHidden({ timeout: 15_000 });
|
||||
}
|
||||
|
||||
async function goToWorkflowDutyStep(page: Page) {
|
||||
const approval = page.locator('label', { hasText: '纳入人工审批' }).first();
|
||||
const selectHint = page.getByText('选择要激活的已发布工作流版本');
|
||||
@@ -617,9 +653,9 @@ test.describe.serial('full-system browser multi-user E2E', () => {
|
||||
if ((await desc.count()) === 0) fail('project form missing description');
|
||||
await desc.fill('全系统浏览器 E2E:骨科超声吸引止血,覆盖多人协作与审签链路。');
|
||||
await toggleCreateProjectMember(modal, USERS.reviewer.display, 'reviewer');
|
||||
await toggleCreateProjectMember(modal, USERS.approver.display, 'viewer');
|
||||
await toggleCreateProjectMember(modal, USERS.member.display, 'editor');
|
||||
// guest optional — if listed, add as viewer
|
||||
await toggleCreateProjectMember(modal, USERS.approver.display, 'approver');
|
||||
// Plain collaborator: create-modal has no "member" option — select (defaults viewer) then promote.
|
||||
await toggleCreateProjectMember(modal, USERS.member.display);
|
||||
const guestVisible = await modal.getByText(USERS.guest.display, { exact: true }).count();
|
||||
if (guestVisible > 0) {
|
||||
await toggleCreateProjectMember(modal, USERS.guest.display, 'viewer');
|
||||
@@ -628,14 +664,31 @@ test.describe.serial('full-system browser multi-user E2E', () => {
|
||||
await modal.getByRole('button', { name: /确认立项/ }).click();
|
||||
await expect(heading).toBeHidden({ timeout: 15_000 });
|
||||
await expect(currentProjectBannerName(page, PROJECT_NAME)).toBeVisible({ timeout: 30_000 });
|
||||
try {
|
||||
await promoteToProjectMember(page, USERS.member.display);
|
||||
} catch (promoteErr) {
|
||||
record({
|
||||
id: 'FS-PROJ-01b',
|
||||
name: 'promote member role',
|
||||
priority: 'P0',
|
||||
input: `promote ${USERS.member.display} to project member`,
|
||||
expected: 'project_role=member',
|
||||
actual: String(promoteErr),
|
||||
verdict: 'FAIL',
|
||||
durationMs: Date.now() - t0,
|
||||
console_errors: consoleErrorsFor(label),
|
||||
notes: 'create-modal 仅有 author/reviewer/approver/viewer;成员级可编辑需立项后改派',
|
||||
});
|
||||
throw promoteErr;
|
||||
}
|
||||
await shot(page, 'FS-PROJ-01-created');
|
||||
record({
|
||||
id: 'FS-PROJ-01',
|
||||
name: 'create project + members',
|
||||
priority: 'P0',
|
||||
input: `project=${PROJECT_NAME}; members reviewer/approver/member(+guest)`,
|
||||
expected: 'banner shows project; members selected',
|
||||
actual: `created; guestListed=${guestVisible > 0}`,
|
||||
input: `project=${PROJECT_NAME}; reviewer/approver duties + member promote(+guest)`,
|
||||
expected: 'banner shows project; member is project_role=member',
|
||||
actual: `created; guestListed=${guestVisible > 0}; member promoted`,
|
||||
verdict: 'PASS',
|
||||
durationMs: Date.now() - t0,
|
||||
console_errors: consoleErrorsFor(label),
|
||||
|
||||
Reference in New Issue
Block a user