Commit 45178f79 authored by liguangyu06's avatar liguangyu06
Browse files

完善用户管理/医共体自动化用例与文档,并同步角色、准入及 CI 套件配置。



用户管理补充 20 条/页、列设置、授权弹框(账号一致、添加/移除)等用例与 PO;医共体对齐组合查询、分页、列设置与授权场景;CSV/MD/测试点与 generate-user-md 一并更新。

Co-authored-by: default avatarCursor <cursoragent@cursor.com>
parent 2eef70bd
import { test } from '../../fixtures';
import { expect, test } from '../../fixtures';
import { assertAdmitHeaders, assertAdmitListOrEmpty, assertAdmitMockEmpty } from './helpers/admit-assert.helper';
/**
* 全量用例:映射 testcase/基础管理-准入管理-供货目录申请-UI自动化测试用例-01.csv
......@@ -7,7 +8,9 @@ test.describe('基础设置 · 准入管理', () => {
test.describe('供货目录申请 · 页面进入', () => {
test('ADMIT-SUP-001 菜单进入供货目录申请页', { tag: '@smoke' }, async ({ admitSupplyPage }) => {
await admitSupplyPage.openFromMenu();
await admitSupplyPage.expectEntered();
await admitSupplyPage.ready();
await expect(admitSupplyPage.button('新增').first(), '新增').toBeVisible();
await expect(admitSupplyPage.button('批量提交').first(), '批量提交').toBeVisible();
});
});
......@@ -15,7 +18,7 @@ test.describe('基础设置 · 准入管理', () => {
test('ADMIT-SUP-002 默认查询列表有数据或空态明确', { tag: '@smoke' }, async ({ admitSupplyPage }) => {
await admitSupplyPage.open();
await admitSupplyPage.query();
await admitSupplyPage.expectListOrEmpty();
await assertAdmitListOrEmpty(await admitSupplyPage.snapshotList());
});
test('ADMIT-SUP-003 按进院供应商筛选', async ({ admitSupplyPage }) => {
......@@ -30,12 +33,22 @@ test.describe('基础设置 · 准入管理', () => {
test('ADMIT-SUP-005 变更单号无匹配空态(mock反例)', async ({ admitSupplyPage }) => {
await admitSupplyPage.open();
await admitSupplyPage.mockEmpty();
await assertAdmitMockEmpty(await admitSupplyPage.mockEmpty());
});
test('ADMIT-SUP-006 列表核心列完整', async ({ admitSupplyPage }) => {
await admitSupplyPage.open();
await admitSupplyPage.expectCoreColumns();
assertAdmitHeaders(await admitSupplyPage.coreHeaders(), [
'申请单号',
'申请人',
'申请时间',
'进院供应商',
'二级供应商',
'业务模式',
'是否默认',
'备注',
'操作',
]);
});
test('ADMIT-SUP-007 切换待提交 Tab 且计数一致', async ({ admitSupplyPage }) => {
......
import { test } from '../../fixtures';
import { expect, test } from '../../fixtures';
import { assertAdmitHeaders, assertAdmitListOrEmpty, assertAdmitMockEmpty } from './helpers/admit-assert.helper';
/**
* 全量用例:映射 testcase/基础管理-准入管理-准入任务分配-UI自动化测试用例-01.csv
......@@ -7,7 +8,10 @@ test.describe('基础设置 · 准入管理', () => {
test.describe('准入任务分配 · 页面进入', () => {
test('ADMIT-TSK-001 菜单进入准入任务分配页', { tag: '@smoke' }, async ({ admitTaskPage }) => {
await admitTaskPage.openFromMenu();
await admitTaskPage.expectEntered();
await admitTaskPage.ready();
await expect(admitTaskPage.tab('待分类'), '待分类 Tab').toBeVisible();
await expect(admitTaskPage.tab('已分类'), '已分类 Tab').toBeVisible();
await expect(admitTaskPage.button('批量分配').first(), '批量分配').toBeVisible();
});
});
......@@ -15,7 +19,7 @@ test.describe('基础设置 · 准入管理', () => {
test('ADMIT-TSK-002 默认查询列表有数据或空态明确', { tag: '@smoke' }, async ({ admitTaskPage }) => {
await admitTaskPage.open();
await admitTaskPage.query();
await admitTaskPage.expectListOrEmpty();
await assertAdmitListOrEmpty(await admitTaskPage.snapshotList());
});
test('ADMIT-TSK-003 产品名称筛选', async ({ admitTaskPage }) => {
......@@ -35,7 +39,7 @@ test.describe('基础设置 · 准入管理', () => {
test('ADMIT-TSK-006 无匹配结果空态(mock反例)', async ({ admitTaskPage }) => {
await admitTaskPage.open();
await admitTaskPage.mockEmpty();
await assertAdmitMockEmpty(await admitTaskPage.mockEmpty());
});
test('ADMIT-TSK-015 多条件组合查询(新增日期等11项)', async ({ admitTaskPage }) => {
await admitTaskPage.open();
......@@ -46,7 +50,19 @@ test.describe('基础设置 · 准入管理', () => {
test.describe('准入任务分配 · 列表分页', () => {
test('ADMIT-TSK-007 列表核心列完整', async ({ admitTaskPage }) => {
await admitTaskPage.open();
await admitTaskPage.expectCoreColumns();
assertAdmitHeaders(await admitTaskPage.coreHeaders(), [
'准入类型',
'产品编号',
'产品名称',
'规格型号',
'规格',
'型号',
'器械注册人',
'操作',
]);
if ((await admitTaskPage.getDataRowCount()) > 0) {
await expect(admitTaskPage.rowAction(await admitTaskPage.firstRow(), '分配类型'), '分配类型').toBeVisible();
}
});
test('ADMIT-TSK-008 翻至第 2 页', async ({ admitTaskPage }) => {
......
import { basicHospitalData, isBasicWriteAllowed } from '../../data/basic-hospital.data';
import { expect, test } from '../../fixtures';
import { userData } from '../../data/user.data';
/**
* 全量用例:与 testcase/基础管理-用户管理-角色管理-UI自动化测试用例-01.csv 严格一致
* 运行:npm run test:basic-role:all
*/
test.describe('基础设置 · 用户管理', () => {
test.describe('角色管理 · 页面进入', () => {
test('USER-ROL-001 菜单进入角色管理页', { tag: '@smoke' }, async ({ basicRolePage }) => {
await basicRolePage.openFromMenu();
expect(basicRolePage.browserPage.url(), 'URL 应含角色管理 path').toContain('/basic/user/platform/role');
expect(basicRolePage.browserPage.url(), '不应进入用户管理').not.toContain('/platform/person');
await expect(basicRolePage.locators.saveBtn, '保 存').toBeVisible();
await expect(basicRolePage.locators.refreshBtn, '刷 新').toBeVisible();
await expect(basicRolePage.locators.roleTree, '右侧权限树').toBeVisible();
expect((await basicRolePage.leftRoleNames()).length, '左侧角色列表').toBeGreaterThan(0);
await expect(basicRolePage.locators.paginationTotal, '无独立分页').toBeHidden();
test.describe('角色管理 · 左侧角色列表', () => {
test('USER-ROL-003 切换角色右侧权限树同步更新', { tag: '@smoke' }, async ({ basicRolePage }) => {
await basicRolePage.open();
const names = await basicRolePage.leftRoleNames();
if (names.length < 2) {
await basicRolePage.ensureHealthyBeforeSkip('数据不足:左栏不足 2 个角色');
test.skip(true, '数据不足:左栏不足 2 个角色');
}
const [first, second] = names;
await basicRolePage.selectRole(first);
await expect(basicRolePage.locators.roleTree, '权限树').toBeVisible();
const sigFirst = await basicRolePage.checkedSignature();
await basicRolePage.selectRole(second);
await expect(basicRolePage.locators.roleTree, '切换后权限树仍可见').toBeVisible();
expect(await basicRolePage.isRoleSelected(second), '第二个角色应选中').toBeTruthy();
await basicRolePage.selectRole(first);
await expect(basicRolePage.locators.roleTree, '切回后权限树仍可见').toBeVisible();
expect(await basicRolePage.isRoleSelected(first), '第一个角色应重新选中').toBeTruthy();
expect(await basicRolePage.checkedSignature(), '切回后勾选应与首次选中一致').toBe(sigFirst);
});
test.describe('未登录', () => {
test.use({ storageState: { cookies: [], origins: [] } });
test('USER-ROL-002 未登录访问拦截', async ({ basicRolePage }) => {
const probe = await basicRolePage.probeUnauthAccess();
expect(probe.onLogin || !probe.hasBusinessRow, '未登录不可见角色配置').toBeTruthy();
});
test('USER-ROL-011 左栏角色列表可上下滑动', async ({ basicRolePage }) => {
await basicRolePage.open();
await basicRolePage.assertLeftRoleListVerticalScroll();
});
});
test.describe('角色管理 · 左侧角色列表', () => {
test('USER-ROL-003 角色列表加载并可点选', { tag: '@smoke' }, async ({ basicRolePage }) => {
test.describe('角色管理 · 角色增删改', () => {
test('USER-ROL-004 编辑角色弹框回填名称一致', async ({ basicRolePage }) => {
await basicRolePage.open();
const name = await basicRolePage.sampleRoleName();
await basicRolePage.selectRole(name);
expect(await basicRolePage.isRoleSelected(name), '该行应处于选中态').toBeTruthy();
const { selectedRoleName, dialog } = await basicRolePage.openEditRoleDialog(name);
await expect(dialog, '编辑弹框可见').toBeVisible();
await expect(dialog, '应为编辑角色弹框').toContainText(/编\s*辑/);
const formName = await basicRolePage.readEditRoleNameFromDialog();
expect(
basicRolePage.roleNamesMatch(selectedRoleName, formName),
`弹框角色名称「${formName}」应与选中「${selectedRoleName}」一致`,
).toBeTruthy();
await basicRolePage.closeDialog();
});
test('USER-ROL-004 角色名称搜索过滤', async ({ basicRolePage }) => {
test('USER-ROL-005 编辑角色保存成功', async ({ basicRolePage }) => {
test.skip(!isBasicWriteAllowed(), basicHospitalData.writeDisabledReason);
await basicRolePage.open();
const name = await basicRolePage.sampleRoleName();
const keyword = name.slice(0, Math.min(2, name.length));
await basicRolePage.searchRole(keyword);
const names = await basicRolePage.leftRoleNames();
expect(names.length, '搜索后应有匹配项').toBeGreaterThan(0);
expect(
names.every((item) => item.includes(keyword)),
'搜索结果角色名包含关键字',
).toBeTruthy();
const { dialog } = await basicRolePage.openEditRoleDialog(name);
await expect(dialog, '编辑弹框可见').toBeVisible();
await basicRolePage.saveEditRoleDialog();
await basicRolePage.expectToastSuccess(/编辑成功/);
await expect(basicRolePage.editRoleDialog(), '保存后弹框应关闭').toBeHidden({ timeout: 15_000 });
});
test('USER-ROL-005 选中角色刷新右侧权限树', async ({ basicRolePage }) => {
test('USER-ROL-006 点击角色按钮打开新增弹框', async ({ basicRolePage }) => {
await basicRolePage.open();
const names = await basicRolePage.leftRoleNames();
if (names.length < 2) {
await basicRolePage.ensureHealthyBeforeSkip('数据不足:左栏不足 2 个角色');
test.skip(true, '数据不足:左栏不足 2 个角色');
}
await basicRolePage.selectRole(names[0]);
await expect(basicRolePage.locators.roleTree, '权限树').toBeVisible();
await basicRolePage.checkedSignature();
await basicRolePage.selectRole(names[1]);
await expect(basicRolePage.locators.roleTree, '切换后权限树仍可见').toBeVisible();
expect(await basicRolePage.isRoleSelected(names[1]), '当前角色应选中').toBeTruthy();
const dialog = await basicRolePage.openAddRoleDialog();
await expect(dialog, '新增角色弹框可见').toBeVisible();
await expect(dialog, '应为新增角色弹框').toContainText(/新增|角\s*色/);
expect(await basicRolePage.browserPage.locator('.ant-message-error').count(), '无错误提示').toBe(0);
await basicRolePage.cancelAddRole();
});
test('USER-ROL-006 清空搜索恢复全量', async ({ basicRolePage }) => {
test('USER-ROL-007 新增角色名称为空保存拦截', async ({ basicRolePage }) => {
await basicRolePage.open();
await basicRolePage.openAddRoleDialog();
await basicRolePage.saveAddRoleDialog();
await basicRolePage.expectAddRoleNameRequired('请输入角色名称!');
await basicRolePage.cancelAddRole();
});
test('USER-ROL-008 新增角色弹框取消关闭', async ({ basicRolePage }) => {
await basicRolePage.open();
const before = (await basicRolePage.leftRoleNames()).length;
const name = await basicRolePage.sampleRoleName();
await basicRolePage.searchRole(name.slice(0, Math.min(2, name.length)));
await basicRolePage.clearRoleSearch();
expect((await basicRolePage.leftRoleNames()).length, '清空后应恢复全量').toBeGreaterThanOrEqual(before);
const dialog = await basicRolePage.openAddRoleDialog();
await expect(dialog, '新增角色弹框可见').toBeVisible();
await basicRolePage.cancelAddRoleDialog();
expect((await basicRolePage.leftRoleNames()).length, '取消后角色数不增加').toBe(before);
});
test('USER-ROL-007 搜索无匹配空态(mock反例)', { tag: '@smoke' }, async ({ basicRolePage }) => {
test('USER-ROL-009 新增弹框查询配置化复选框可勾选', async ({ basicRolePage }) => {
await basicRolePage.open();
await basicRolePage.searchRole(userData.mockRole);
const names = await basicRolePage.leftRoleNames();
const empty = await basicRolePage.locators.empty.isVisible().catch(() => false);
expect(names.length === 0 || empty, '无匹配时空态明确').toBeTruthy();
await basicRolePage.openAddRoleDialog();
await basicRolePage.setQueryConfigChecked(true);
await basicRolePage.setQueryConfigChecked(false);
expect(await basicRolePage.browserPage.locator('.ant-message-error').count(), '无错误提示').toBe(0);
await basicRolePage.cancelAddRoleDialog();
});
test('USER-ROL-014 角色名与用户管理角色下拉同源', async ({ basicRolePage, basicUserPage }) => {
test('USER-ROL-010 新增角色弹框X关闭', async ({ basicRolePage }) => {
await basicRolePage.open();
const roleNames = (await basicRolePage.leftRoleNames()).filter((item) => item && item !== '角色');
expect(roleNames.length, '左栏应有角色').toBeGreaterThan(0);
await basicUserPage.open();
const options = await basicUserPage.collectRoleFilterOptions();
if (options.length === 0) {
await basicUserPage.ensureHealthyBeforeSkip('数据不足:用户管理角色下拉为空');
test.skip(true, '数据不足:用户管理角色下拉为空');
}
const sample = roleNames.slice(0, 8);
for (const name of sample) {
const compact = name.replace(/\s/g, '').replace(/[((].*$/, '');
const hit = options.some((item) => item.includes(compact) || compact.includes(item));
expect(hit, `角色「${name}」应能在用户页下拉对应到`).toBeTruthy();
}
const before = (await basicRolePage.leftRoleNames()).length;
const dialog = await basicRolePage.openAddRoleDialog();
await expect(dialog, '新增角色弹框可见').toBeVisible();
await basicRolePage.closeAddRoleDialogByX();
expect((await basicRolePage.leftRoleNames()).length, 'X 关闭后角色数不增加').toBe(before);
});
});
test.describe('角色管理 · 右侧菜单权限树', () => {
test('USER-ROL-008 权限树按应用与模块展开', async ({ basicRolePage }) => {
test('USER-ROL-001 权限树按应用与模块展开', { tag: '@smoke' }, async ({ basicRolePage }) => {
await basicRolePage.open();
const name = await basicRolePage.sampleRoleName();
await basicRolePage.selectRole(name);
......@@ -115,79 +119,23 @@ test.describe('基础设置 · 用户管理', () => {
).toBeVisible();
});
test('USER-ROL-009 勾选父节点联动子节点', { tag: '@smoke' }, async ({ basicRolePage }) => {
test('USER-ROL-002 权限树模块节点收起正常', async ({ basicRolePage }) => {
await basicRolePage.open();
const name = await basicRolePage.sampleRoleName();
await basicRolePage.selectRole(name);
await basicRolePage.expandTreeNode('厂商管理');
await basicRolePage.checkModule('厂商管理');
expect(await basicRolePage.isTreeChecked('厂商管理'), '父节点应勾选').toBeTruthy();
const child = basicRolePage.locators.treeNode('厂商查询');
if (await child.isVisible().catch(() => false)) {
expect(await basicRolePage.isTreeChecked('厂商查询'), '子节点应联动勾选').toBeTruthy();
}
await basicRolePage.refreshTree();
});
test('USER-ROL-010 刷新恢复未保存勾选', async ({ basicRolePage }) => {
await basicRolePage.open();
const name = await basicRolePage.sampleRoleName();
await basicRolePage.selectRole(name);
const before = await basicRolePage.checkedSignature();
await basicRolePage.checkModule('厂商管理');
await basicRolePage.refreshTree();
const after = await basicRolePage.checkedSignature();
expect(after, '刷新应丢弃未保存勾选').toBe(before);
});
});
test.describe('角色管理 · 角色增删改', () => {
test('USER-ROL-011 删除需确认且取消保留', async ({ basicRolePage }) => {
await basicRolePage.open();
const opened = await basicRolePage.openDeleteConfirm();
expect(opened, '应出现删除确认').toBeTruthy();
await expect(opened!.confirm.getByRole('button', { name: /取\s*消|关\s*闭/ }).first(), '取消').toBeVisible();
await expect(opened!.confirm.getByRole('button', { name: /确\s*定|删/ }).first(), '确定').toBeVisible();
const cancel = opened!.confirm.getByRole('button', { name: /取\s*消|关\s*闭/ }).first();
await cancel.click();
const names = await basicRolePage.leftRoleNames();
expect(names.some((item) => item.includes(opened!.roleName)), '取消后角色仍在').toBeTruthy();
});
test('USER-ROL-012 新增角色入口打开后取消', async ({ basicRolePage }) => {
await basicRolePage.open();
const { before, opened } = await basicRolePage.openAddRoleForm();
expect(opened, '新增表单应打开').toBeTruthy();
await basicRolePage.cancelAddRole();
expect((await basicRolePage.leftRoleNames()).length, '取消后角色数不增加').toBe(before);
const child = basicRolePage.locators
.treeNode('医院资料')
.or(basicRolePage.locators.roleTree.getByText('医院资料').first());
await basicRolePage.expandTreeNode('基础设置');
await expect(child, '展开后子菜单可见').toBeVisible();
await basicRolePage.collapseTreeNode('基础设置');
expect(await basicRolePage.isTreeNodeExpanded('基础设置'), '基础设置应收起').toBeFalsy();
await expect(child, '收起后子菜单不可见').toBeHidden();
});
test('USER-ROL-013 预置角色不可删除', async ({ basicRolePage }) => {
test('USER-ROL-012 右侧权限树子菜单可上下滑动', async ({ basicRolePage }) => {
await basicRolePage.open();
const names = await basicRolePage.leftRoleNames();
const preset = names.find((item) => /医院管理员/.test(item));
if (!preset) {
await basicRolePage.ensureHealthyBeforeSkip('数据不足:左栏无医院管理员等预置角色');
test.skip(true, '数据不足:左栏无医院管理员等预置角色');
}
await basicRolePage.selectRole(preset!);
const del = await basicRolePage.deleteEntry(preset!);
const visible = await del.isVisible().catch(() => false);
if (!visible) {
expect(visible, '预置角色删除入口不可见').toBeFalsy();
return;
}
await del.click();
const confirm = basicRolePage.browserPage
.locator('.ant-modal-confirm, .ant-popconfirm, .ant-modal')
.filter({ hasText: /提示|删除|确认/ })
.last();
if (await confirm.isVisible().catch(() => false)) {
const cancel = confirm.getByRole('button', { name: /取\s*消|关\s*闭/ }).first();
if (await cancel.isVisible().catch(() => false)) await cancel.click();
else await basicRolePage.browserPage.keyboard.press('Escape');
}
expect((await basicRolePage.leftRoleNames()).some((item) => item.includes(preset!)), '预置角色仍在').toBeTruthy();
await basicRolePage.assertRoleTreeVerticalScroll();
});
});
});
......@@ -58,16 +58,35 @@ test.describe('基础设置 · 用户管理', () => {
).toBeTruthy();
});
test('USER-UNI-006 姓名+医院+联系方式组合查询', async ({ basicUserUnionPage }) => {
test('USER-UNI-006 姓名+医院+科室+角色+状态组合查询', async ({ basicUserUnionPage }) => {
await basicUserUnionPage.open();
const sample = await basicUserUnionPage.extractUnionSample();
await basicUserUnionPage.queryComboUnion(sample);
await assertListOrEmpty(await basicUserUnionPage.snapshotList(), '组合查询');
if (sample.sampleUserName) {
const nameOk = await basicUserUnionPage.rowsMatchKeyword('姓名', sample.sampleUserName);
const loginOk = await basicUserUnionPage.rowsMatchKeyword('登录账号', sample.sampleLoginName);
expect(nameOk || loginOk || (await basicUserUnionPage.getDataRowCount()) === 0, '组合条件应生效').toBeTruthy();
const list = await basicUserUnionPage.snapshotList();
expect(list.rows, '组合查询应有结果').toBeGreaterThan(0);
expect(list.emptyVisible, '不应为空态').toBeFalsy();
const loginAfter = (await basicUserUnionPage.cellText(await basicUserUnionPage.firstRow(), '登录账号')).replace(
/\s/g,
'',
);
expect(loginAfter, '首行应与采样登录账号一致').toBe(sample.sampleLoginName.replace(/\s/g, ''));
expect(await basicUserUnionPage.rowsMatchKeyword('姓名', sample.sampleUserName), '姓名应匹配').toBeTruthy();
if (sample.sampleHospital) {
expect(await basicUserUnionPage.rowsMatchKeyword('所属医院', sample.sampleHospital), '所属医院应匹配').toBeTruthy();
}
if (sample.sampleDept && sample.sampleDept !== '--') {
expect(await basicUserUnionPage.rowsMatchKeyword('所属科室', sample.sampleDept), '所属科室应匹配').toBeTruthy();
}
if (sample.sampleRole) {
expect(await basicUserUnionPage.rowsMatchKeyword('角色', sample.sampleRole), '角色应匹配').toBeTruthy();
}
expect(await basicUserUnionPage.rowsMatchStatus(sample.sampleStatus), '状态应匹配').toBeTruthy();
});
test('USER-UNI-017 高级筛选按状态过滤', { tag: '@smoke' }, async ({ basicUserUnionPage }) => {
await basicUserUnionPage.open();
await basicUserUnionPage.filterByStatus('启用');
await assertListOrEmpty(await basicUserUnionPage.snapshotList(), '按状态过滤');
});
test('USER-UNI-007 重置恢复默认医院', async ({ basicUserUnionPage }) => {
......@@ -126,6 +145,35 @@ test.describe('基础设置 · 用户管理', () => {
expect(await basicUserUnionPage.currentHospitalLabel(), '翻页后医院筛选应保持').toBe(hospital);
});
test('USER-UNI-018 切换20条每页页面正常且列表同步', async ({ basicUserUnionPage }) => {
await basicUserUnionPage.open();
await basicUserUnionPage.query();
const hospital = await basicUserUnionPage.currentHospitalLabel();
const { total, rowsBefore, rowsAfter } = await basicUserUnionPage.switchToTwentyPerPage();
const expectedRows = Math.min(total, 20);
expect(rowsAfter, '当前页行数应与 20 条/页及总条数一致').toBe(expectedRows);
expect(rowsAfter, '切换后可见行应随每页条数变化').not.toBe(rowsBefore);
expect(await basicUserUnionPage.getTotalCount(), '总条数应不变').toBe(total);
await expect(basicUserUnionPage.locators.pageSizeChanger, '每页条数').toContainText(/20/);
expect(await basicUserUnionPage.currentHospitalLabel(), '切换后所属医院筛选应保持').toBe(hospital);
await expect(basicUserUnionPage.locators.formSelect('所属医院'), '筛选区').toBeVisible();
await expect(basicUserUnionPage.locators.tableHeader.first(), '列表表头').toBeVisible();
});
test('USER-UNI-019 打开列设置面板', { tag: '@smoke' }, async ({ basicUserUnionPage }) => {
await basicUserUnionPage.open();
await basicUserUnionPage.query();
await basicUserUnionPage.openColumnSettings();
await basicUserUnionPage.expectColumnSettingsPanel();
await basicUserUnionPage.closeColumnSettingsPanel();
});
test('USER-UNI-020 列设置取消勾选后列表列恢复', async ({ basicUserUnionPage }) => {
await basicUserUnionPage.open();
await basicUserUnionPage.query();
await basicUserUnionPage.executeColumnSettingsToggleRestoreTest();
});
test('USER-UNI-012 与用户管理页同一用户字段一致', async ({ basicUserUnionPage, basicUserPage }) => {
await basicUserUnionPage.open();
const sample = await basicUserUnionPage.extractUnionSample();
......@@ -186,4 +234,46 @@ test.describe('基础设置 · 用户管理', () => {
await basicUserUnionPage.closeDialog();
});
});
test.describe('医共体用户管理 · 授权', () => {
test('USER-UNI-022 授权弹框账号与所选行一致', { tag: '@smoke' }, async ({ basicUserUnionPage }) => {
await basicUserUnionPage.open();
await basicUserUnionPage.query();
const { row, sampleLoginName, sampleUserName } = await basicUserUnionPage.pickRowWithLogin();
await basicUserUnionPage.openAuthOnRow(row);
await basicUserUnionPage.expectUserRoleAuthDialogAccount(sampleLoginName, sampleUserName);
await basicUserUnionPage.closeDialog();
});
test('USER-UNI-023 授权弹框添加移除角色行', async ({ basicUserUnionPage }) => {
await basicUserUnionPage.open();
await basicUserUnionPage.query();
const { row } = await basicUserUnionPage.pickRowWithLogin();
await basicUserUnionPage.openAuthOnRow(row);
const { before, afterAdd, afterRemove } = await basicUserUnionPage.runAuthAddThenRemove();
expect(afterAdd, '点击添加后应新增一行').toBe(before + 1);
expect(afterRemove, '点击移除后应删除新增行').toBe(before);
await basicUserUnionPage.closeDialog();
});
test('USER-UNI-021 打开授权后取消不生效', async ({ basicUserUnionPage }) => {
await basicUserUnionPage.open();
await basicUserUnionPage.query();
await basicUserUnionPage.openRowAction('授权');
const layer = basicUserUnionPage.dialog().or(basicUserUnionPage.browserPage.locator('.ant-tree, .put-page'));
await expect(layer.first(), '授权层').toBeVisible({ timeout: 12_000 });
const before = await basicUserUnionPage.authCheckedCount();
const unchecked = basicUserUnionPage.browserPage
.locator('.ant-tree-checkbox:not(.ant-tree-checkbox-checked):not(.ant-tree-checkbox-disabled)')
.first();
if (await unchecked.isVisible().catch(() => false)) await unchecked.click();
await basicUserUnionPage.closeDialog();
await basicUserUnionPage.browserPage.keyboard.press('Escape').catch(() => undefined);
await basicUserUnionPage.openRowAction('授权');
await expect(layer.first(), '再次打开授权层').toBeVisible({ timeout: 12_000 });
const after = await basicUserUnionPage.authCheckedCount();
if (before > 0 || after > 0) expect(after, '取消后勾选应恢复').toBe(before);
await basicUserUnionPage.closeDialog();
});
});
});
import { expect, test } from '../../fixtures';
import { assertHeadersContain, assertListOrEmpty, assertMockEmpty } from './helpers/user-assert.helper';
import { assertListOrEmpty, assertMockEmpty } from './helpers/user-assert.helper';
/**
* 全量用例:与 testcase/基础管理-用户管理-用户管理-UI自动化测试用例-01.csv 严格一致
* 运行:npm run test:basic-user:all
*/
test.describe('基础设置 · 用户管理', () => {
test.describe('用户管理 · 页面进入', () => {
test('USER-USR-001 菜单进入用户管理页', { tag: '@smoke' }, async ({ basicUserPage }) => {
await basicUserPage.openFromMenu();
expect(basicUserPage.browserPage.url(), 'URL 应含用户管理 path').toContain('/basic/user/platform/person');
expect(basicUserPage.browserPage.url(), '不应进入医共体').not.toContain('personCom');
expect(basicUserPage.browserPage.url(), '不应进入角色管理').not.toContain('/platform/role');
await expect(basicUserPage.locators.nameInput, '姓名筛选').toBeVisible();
await expect(basicUserPage.button('新增').first(), '新 增').toBeVisible();
await expect(basicUserPage.locators.excelImport, 'Excel导入').toBeVisible();
expect((await basicUserPage.headerTexts()).length, '列表列头').toBeGreaterThan(0);
await expect(basicUserPage.locators.paginationTotal.or(basicUserPage.browserPage.locator('.ant-pagination')).first(), '分页').toBeVisible();
});
test.describe('未登录', () => {
test.use({ storageState: { cookies: [], origins: [] } });
test('USER-USR-002 未登录访问拦截', async ({ basicUserPage }) => {
const probe = await basicUserPage.probeUnauthAccess();
expect(probe.onLogin || !probe.hasBusinessRow, '未登录不可见用户数据').toBeTruthy();
});
});
});
test.describe('用户管理 · 筛选与查询', () => {
test('USER-USR-003 默认查询列表有数据或空态明确', { tag: '@smoke' }, async ({ basicUserPage }) => {
await basicUserPage.open();
await basicUserPage.query();
await assertListOrEmpty(await basicUserPage.snapshotList(), '默认查询');
});
test('USER-USR-004 姓名或登录账号关键字查询', async ({ basicUserPage }) => {
await basicUserPage.open();
const sample = await basicUserPage.extractSample();
const keyword = sample.sampleUserName || sample.sampleLoginName;
await basicUserPage.queryByName(keyword);
const nameOk = await basicUserPage.rowsMatchKeyword('姓名', keyword);
const loginOk = await basicUserPage.rowsMatchKeyword('登录账号', keyword);
expect(nameOk || loginOk, '结果行姓名或登录账号应匹配关键字').toBeTruthy();
});
test('USER-USR-005 科室下拉筛选', async ({ basicUserPage }) => {
await basicUserPage.open();
await basicUserPage.query();
const dept = await basicUserPage.sampleCell('所属科室');
await basicUserPage.queryByDept(dept);
expect(await basicUserPage.rowsMatchKeyword('所属科室', dept), '所属科室应与筛选一致').toBeTruthy();
const rows = await basicUserPage.listRows();
const limit = Math.min(await rows.count(), 5);
for (let i = 0; i < limit; i += 1) {
const text = (await basicUserPage.cellText(rows.nth(i), '所属科室')).replace(/\s/g, '');
expect(text === '--' || text === '-', '筛选后不应含未分配科室').toBeFalsy();
}
});
test('USER-USR-006 姓名+科室+角色组合查询', async ({ basicUserPage }) => {
test('USER-USR-001 姓名+科室+角色+状态组合查询', async ({ basicUserPage }) => {
await basicUserPage.open();
const sample = await basicUserPage.extractSample();
await basicUserPage.queryCombo(sample);
await assertListOrEmpty(await basicUserPage.snapshotList(), '组合查询');
const list = await basicUserPage.snapshotList();
expect(list.rows, '组合查询应有结果').toBeGreaterThan(0);
expect(list.emptyVisible, '不应为空态').toBeFalsy();
const loginAfter = (await basicUserPage.cellText(await basicUserPage.firstRow(), '登录账号')).replace(/\s/g, '');
expect(loginAfter, '首行应与采样登录账号一致').toBe(sample.sampleLoginName.replace(/\s/g, ''));
expect(await basicUserPage.rowsMatchKeyword('姓名', sample.sampleUserName), '姓名应匹配').toBeTruthy();
if (sample.sampleDept && sample.sampleDept !== '--') {
expect(await basicUserPage.rowsMatchKeyword('所属科室', sample.sampleDept), '所属科室应匹配').toBeTruthy();
}
if (sample.sampleRole) {
expect(await basicUserPage.rowsMatchKeyword('角色', sample.sampleRole), '角色应匹配').toBeTruthy();
}
expect(await basicUserPage.rowsMatchStatus(sample.sampleStatus), '状态应匹配').toBeTruthy();
});
test('USER-USR-007 重置恢复默认', async ({ basicUserPage }) => {
test('USER-USR-002 重置恢复默认', async ({ basicUserPage }) => {
await basicUserPage.open();
const sample = await basicUserPage.extractSample();
await basicUserPage.queryByName(sample.sampleUserName);
......@@ -75,7 +37,7 @@ test.describe('基础设置 · 用户管理', () => {
await expect(basicUserPage.locators.nameInput).toHaveValue('');
});
test('USER-USR-008 展开高级筛选', async ({ basicUserPage }) => {
test('USER-USR-003 展开高级筛选', async ({ basicUserPage }) => {
await basicUserPage.open();
await basicUserPage.expandAdvanced();
await expect(basicUserPage.browserPage.getByText('状态').first(), '高级筛选状态').toBeVisible();
......@@ -84,13 +46,13 @@ test.describe('基础设置 · 用户管理', () => {
await expect(basicUserPage.button('筛选').first().or(basicUserPage.button('重置').first()), '筛选或重置').toBeVisible();
});
test('USER-USR-009 高级筛选按状态过滤', { tag: '@smoke' }, async ({ basicUserPage }) => {
test('USER-USR-004 高级筛选按状态过滤', { tag: '@smoke' }, async ({ basicUserPage }) => {
await basicUserPage.open();
await basicUserPage.filterByStatus('启用');
await assertListOrEmpty(await basicUserPage.snapshotList(), '按状态过滤');
});
test('USER-USR-010 无匹配结果空态(mock反例)', { tag: '@smoke' }, async ({ basicUserPage }) => {
test('USER-USR-005 无匹配结果空态(mock反例)', { tag: '@smoke' }, async ({ basicUserPage }) => {
await basicUserPage.open();
await basicUserPage.queryMockEmpty();
await assertMockEmpty(await basicUserPage.snapshotList());
......@@ -98,43 +60,47 @@ test.describe('基础设置 · 用户管理', () => {
});
test.describe('用户管理 · 列表与分页', () => {
test('USER-USR-011 列表核心列完整', async ({ basicUserPage }) => {
test('USER-USR-006 分页翻至第 2 页', async ({ basicUserPage }) => {
await basicUserPage.open();
await basicUserPage.query();
assertHeadersContain(await basicUserPage.headerTexts(), [
'序号',
'姓名',
'登录账号',
'联系方式',
'所属科室',
'医院员工编号',
'角色',
'科室任职数',
'操作',
]);
const row = await basicUserPage.firstRow();
await expect(basicUserPage.rowAction(row, '编辑'), '编辑').toBeVisible();
await expect(basicUserPage.rowAction(row, '授权'), '授权').toBeVisible();
await expect(basicUserPage.rowAction(row, '重置密码'), '重置密码').toBeVisible();
const { firstLogin, secondLogin, active } = await basicUserPage.goToPage2();
expect(active, '页码高亮为 2').toBe('2');
expect(secondLogin, '第 2 页首行应更换').not.toBe(firstLogin);
});
test('USER-USR-012 列表不含所属医院列', async ({ basicUserPage }) => {
test('USER-USR-016 切换20条每页页面正常且列表同步', async ({ basicUserPage }) => {
await basicUserPage.open();
const headers = await basicUserPage.headerTexts();
expect(headers.includes('所属医院'), '本页不应有所属医院列').toBeFalsy();
await basicUserPage.query();
const { total, rowsBefore, rowsAfter } = await basicUserPage.switchToTwentyPerPage();
const expectedRows = Math.min(total, 20);
expect(rowsAfter, '当前页行数应与 20 条/页及总条数一致').toBe(expectedRows);
expect(rowsAfter, '切换后可见行应随每页条数变化').not.toBe(rowsBefore);
expect(await basicUserPage.getTotalCount(), '总条数应不变').toBe(total);
await expect(basicUserPage.locators.pageSizeChanger, '每页条数').toContainText(/20/);
expect(basicUserPage.browserPage.url(), '仍停留在用户管理页').toContain('/basic/user/platform/person');
await expect(basicUserPage.locators.nameInput, '筛选区').toBeVisible();
await expect(basicUserPage.locators.tableHeader.first(), '列表表头').toBeVisible();
});
test('USER-USR-013 分页翻至第 2 页', async ({ basicUserPage }) => {
test('USER-USR-017 打开列设置面板', { tag: '@smoke' }, async ({ basicUserPage }) => {
await basicUserPage.open();
await basicUserPage.query();
const { firstLogin, secondLogin, active } = await basicUserPage.goToPage2();
expect(active, '页码高亮为 2').toBe('2');
expect(secondLogin, '第 2 页首行应更换').not.toBe(firstLogin);
await basicUserPage.openColumnSettings();
await basicUserPage.expectColumnSettingsPanel();
await basicUserPage.closeColumnSettingsPanel();
});
test('USER-USR-018 列设置取消勾选后列表列恢复', async ({ basicUserPage }) => {
await basicUserPage.open();
await basicUserPage.query();
await basicUserPage.executeColumnSettingsToggleRestoreTest();
});
});
test.describe('用户管理 · 新增用户', () => {
test('USER-USR-014 打开新增用户弹窗', async ({ basicUserPage }) => {
test('USER-USR-007 打开新增用户弹窗', async ({ basicUserPage }) => {
await basicUserPage.open();
const dialog = await basicUserPage.openAddDialog();
await expect(dialog.getByText(/账号|姓名|联系方式|医院员工编号|启用|禁用/).first()).toBeVisible();
......@@ -143,20 +109,20 @@ test.describe('基础设置 · 用户管理', () => {
await basicUserPage.closeDialog();
});
test('USER-USR-015 新增取消不落库', async ({ basicUserPage }) => {
test('USER-USR-008 新增取消不落库', async ({ basicUserPage }) => {
await basicUserPage.open();
const login = await basicUserPage.addAndCancel();
await assertMockEmpty(await basicUserPage.snapshotList(), login);
});
test('USER-USR-016 必填为空拦截', async ({ basicUserPage }) => {
test('USER-USR-009 必填为空拦截', async ({ basicUserPage }) => {
await basicUserPage.open();
await basicUserPage.submitEmptyRequired();
await basicUserPage.expectBlockedAndStay();
await basicUserPage.closeDialog();
});
test('USER-USR-017 登录账号重复拦截', async ({ basicUserPage }) => {
test('USER-USR-010 登录账号重复拦截', async ({ basicUserPage }) => {
await basicUserPage.open();
const sample = await basicUserPage.extractSample();
await basicUserPage.submitDuplicateLogin(sample.sampleLoginName);
......@@ -168,7 +134,7 @@ test.describe('基础设置 · 用户管理', () => {
});
test.describe('用户管理 · 编辑用户', () => {
test('USER-USR-018 打开编辑用户弹窗并回填', async ({ basicUserPage }) => {
test('USER-USR-011 打开编辑用户弹窗并回填', async ({ basicUserPage }) => {
await basicUserPage.open();
await basicUserPage.query();
const { login, name } = await basicUserPage.openEdit();
......@@ -180,7 +146,7 @@ test.describe('基础设置 · 用户管理', () => {
await basicUserPage.closeDialog();
});
test('USER-USR-019 编辑取消不保存', async ({ basicUserPage }) => {
test('USER-USR-012 编辑取消不保存', async ({ basicUserPage }) => {
await basicUserPage.open();
await basicUserPage.query();
const original = await basicUserPage.editAndCancel();
......@@ -189,7 +155,7 @@ test.describe('基础设置 · 用户管理', () => {
await basicUserPage.closeDialog();
});
test('USER-USR-020 编辑时账号只读', async ({ basicUserPage }) => {
test('USER-USR-013 编辑时账号只读', async ({ basicUserPage }) => {
await basicUserPage.open();
await basicUserPage.query();
await basicUserPage.openEdit();
......@@ -200,7 +166,7 @@ test.describe('基础设置 · 用户管理', () => {
await basicUserPage.closeDialog();
});
test('USER-USR-021 编辑弹窗可见签名图片', async ({ basicUserPage }) => {
test('USER-USR-014 编辑弹窗可见签名图片', async ({ basicUserPage }) => {
await basicUserPage.open();
await basicUserPage.query();
await basicUserPage.openEdit();
......@@ -210,7 +176,27 @@ test.describe('基础设置 · 用户管理', () => {
});
test.describe('用户管理 · 授权', () => {
test('USER-USR-022 打开授权后取消不生效', async ({ basicUserPage }) => {
test('USER-USR-020 授权弹框添加移除角色行', async ({ basicUserPage }) => {
await basicUserPage.open();
await basicUserPage.query();
const { row } = await basicUserPage.pickRowWithLogin();
await basicUserPage.openAuthOnRow(row);
const { before, afterAdd, afterRemove } = await basicUserPage.runAuthAddThenRemove();
expect(afterAdd, '点击添加后应新增一行').toBe(before + 1);
expect(afterRemove, '点击移除后应删除新增行').toBe(before);
await basicUserPage.closeDialog();
});
test('USER-USR-019 授权弹框账号与所选行一致', { tag: '@smoke' }, async ({ basicUserPage }) => {
await basicUserPage.open();
await basicUserPage.query();
const { row, sampleLoginName, sampleUserName } = await basicUserPage.pickRowWithLogin();
await basicUserPage.openAuthOnRow(row);
await basicUserPage.expectUserRoleAuthDialogAccount(sampleLoginName, sampleUserName);
await basicUserPage.closeDialog();
});
test('USER-USR-015 打开授权后取消不生效', async ({ basicUserPage }) => {
await basicUserPage.open();
await basicUserPage.query();
await basicUserPage.openRowAction('授权');
......@@ -230,22 +216,4 @@ test.describe('基础设置 · 用户管理', () => {
await basicUserPage.closeDialog();
});
});
test.describe('用户管理 · 重置密码', () => {
test('USER-USR-023 行内重置密码确认后取消', async ({ basicUserPage }) => {
await basicUserPage.open();
await basicUserPage.query();
await basicUserPage.openRowAction('重置密码');
const confirm = basicUserPage.browserPage
.locator('.ant-modal-confirm, .ant-popconfirm, .ant-modal')
.filter({ hasText: /重置|密码|提示|确认/ })
.last();
await expect(confirm.or(basicUserPage.dialog()), '重置确认或表单').toBeVisible({ timeout: 8_000 });
const cancel = basicUserPage.browserPage.getByRole('button', { name: /取\s*消|关\s*闭/ }).last();
if (await cancel.isVisible().catch(() => false)) await cancel.click();
else await basicUserPage.browserPage.keyboard.press('Escape');
expect(basicUserPage.browserPage.url()).toContain('/basic/user/platform/person');
expect((await basicUserPage.snapshotList()).rows, '该行仍在').toBeGreaterThan(0);
});
});
});
/** 准入管理用例侧断言:读 PO 快照,expect 留在 spec。 */
import { expect } from '@playwright/test';
export type AdmitListSnapshot = {
rows: number;
total: number;
emptyVisible: boolean;
};
export async function assertAdmitListOrEmpty(state: AdmitListSnapshot, label = '列表'): Promise<void> {
if (state.rows > 0) {
expect(state.total, `${label}有数据时分页总数应 ≥ 1`).toBeGreaterThanOrEqual(1);
return;
}
expect(state.emptyVisible || state.total === 0, `${label}无数据时空态应明确或总数为 0`).toBeTruthy();
}
export async function assertAdmitMockEmpty(state: AdmitListSnapshot, label = 'mock 空态'): Promise<void> {
expect(state.rows === 0 || state.emptyVisible, `${label}:无匹配应空态`).toBeTruthy();
expect(state.total === 0 || state.rows === 0, `${label}:分页总数应为 0`).toBeTruthy();
}
export function assertAdmitHeaders(headers: string[], names: string[]): void {
const text = headers.join(' ');
for (const name of names) {
expect(text, `表头应含「${name}」`).toContain(name.replace(/\s/g, ''));
}
}
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment