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
......@@ -4,6 +4,7 @@ import { env } from '../../config/env.config';
import { BasePage } from '../../core/base.page';
import { BasicUserSharedLocators } from '../../locators/spd/basic-user-shared.locator';
import { skipIfInsufficientForPage2 } from './helpers/pagination-page2.helper';
import { skipIfInsufficientForPageSizeChange } from './helpers/pagination-pagesize.helper';
export type UserMeta = {
path: string;
......@@ -221,6 +222,30 @@ export class BasicUserSharedPage extends BasePage {
return row.getByRole('button', { name: pattern }).or(row.getByText(pattern)).first();
}
async activePageNumber(): Promise<string> {
return ((await this.page.locator('.ant-pagination-item-active').innerText()) ?? '').trim();
}
async goToPageNumber(pageNum: number): Promise<string> {
if (pageNum <= 1) {
const first = this.page.locator('.ant-pagination-item-1').first();
if (await first.isVisible().catch(() => false)) {
await this.click(first, '回到第 1 页');
} else {
await this.click(this.page.locator('.ant-pagination-prev').first(), '上一页');
}
} else {
const item = this.page.locator(`.ant-pagination-item-${pageNum}`).first();
if (await item.isVisible().catch(() => false)) {
await this.click(item, `翻到第 ${pageNum} 页`);
} else {
await this.click(this.locators.nextPage, '下一页');
}
}
await this.waitForTableReady();
return this.activePageNumber();
}
async goToPage2(): Promise<{ firstLogin: string; secondLogin: string; active: string }> {
await skipIfInsufficientForPage2({
ensureHealthyBeforeSkip: (reason) => this.ensureHealthyBeforeSkip(reason),
......@@ -230,18 +255,61 @@ export class BasicUserSharedPage extends BasePage {
this.page.locator(`.ant-pagination-item-${pageNum}`).first().isVisible().catch(() => false),
});
const firstLogin = await this.tryCell(await this.firstRow(), '登录账号');
const item = this.page.locator('.ant-pagination-item-2').first();
if (await item.isVisible().catch(() => false)) {
await this.click(item, '翻到第 2 页');
} else {
await this.click(this.locators.nextPage, '下一页');
}
await this.waitForTableReady();
const active = ((await this.page.locator('.ant-pagination-item-active').innerText()) ?? '').trim();
const active = await this.goToPageNumber(2);
const secondLogin = await this.tryCell(await this.firstRow(), '登录账号');
return { firstLogin, secondLogin, active };
}
async pageSizeChangerText(): Promise<string> {
return ((await this.locators.pageSizeChanger.innerText().catch(() => '')) ?? '').replace(/\s+/g, ' ');
}
async changePageSizeTo(toLabel: string): Promise<void> {
const changer = this.locators.pageSizeChanger;
if (!(await changer.isVisible().catch(() => false))) {
await this.skipWhenDataInsufficient('当前页面无每页条数切换');
}
const sizeNum = (toLabel.match(/\d+/) ?? ['20'])[0];
const current = await this.pageSizeChangerText();
if (current.includes(sizeNum) && current.includes('条')) {
return;
}
await this.click(changer, '打开每页条数');
const option = this.page
.locator('.ant-select-dropdown:not(.ant-select-dropdown-hidden) .ant-select-item-option')
.filter({ hasText: new RegExp(`${sizeNum}\\s*条`) })
.first();
await this.click(option, `选择 ${toLabel}`);
await this.waitForTableReady();
const after = await this.pageSizeChangerText();
if (!after.includes(sizeNum)) {
throw new Error(`切换每页条数未生效:期望含 ${sizeNum},当前「${after || '(空)'}」`);
}
}
/** 切换为 20 条/页,返回切换前后可见行数与总条数(总条数应不变)。 */
async switchToTwentyPerPage(): Promise<{ total: number; rowsBefore: number; rowsAfter: number }> {
await skipIfInsufficientForPageSizeChange({
ensureHealthyBeforeSkip: (reason) => this.ensureHealthyBeforeSkip(reason),
waitForTableReady: () => this.waitForTableReady(),
getDataRowCount: () => this.getDataRowCount(),
});
const total = await this.getTotalCount();
if (total <= 10) {
await this.skipWhenDataInsufficient('总条数不大于 10,无法验证切换 20 条/页后列表变化');
}
const rowsBefore = await this.getDataRowCount();
const expectedAfter = Math.min(total, 20);
if (rowsBefore === expectedAfter) {
await this.skipWhenDataInsufficient(
'当前页可见行数已等于切换 20 条/页后的期望,无法验证列表同步变化',
);
}
await this.changePageSizeTo('20 条/页');
const rowsAfter = await this.getDataRowCount();
return { total, rowsBefore, rowsAfter };
}
async openAddDialog(): Promise<Locator> {
await this.clickButton('新增');
const dialog = this.dialog();
......
......@@ -25,6 +25,29 @@ export class BasicUserUnionPage extends BasicUserPage {
async extractUnionSample(): Promise<UnionSample> {
await this.query();
const rows = await this.listRows();
const count = Math.min(await rows.count(), 12);
for (let i = 0; i < count; i += 1) {
const row = rows.nth(i);
const sampleDept = await this.tryCell(row, '所属科室');
if (!sampleDept || sampleDept === '--') continue;
const sampleUserName = await this.cellText(row, '姓名');
const sampleLoginName = await this.cellText(row, '登录账号');
const sampleHospital = await this.cellText(row, '所属医院');
const samplePhone = await this.tryCell(row, '联系方式');
const rawRole = await this.tryCell(row, '角色');
const sampleRole = rawRole.replace(/[((].*$/, '').replace(/…|\.{3}.*/, '').trim();
const sampleStatus = await this.inferRowStatus(row);
return {
sampleUserName,
sampleLoginName,
sampleDept,
sampleRole,
sampleStatus,
sampleHospital,
samplePhone,
};
}
const row = await this.firstRow();
const sampleUserName = await this.cellText(row, '姓名');
const sampleLoginName = await this.cellText(row, '登录账号');
......@@ -33,7 +56,8 @@ export class BasicUserUnionPage extends BasicUserPage {
const sampleDept = await this.tryCell(row, '所属科室');
const rawRole = await this.tryCell(row, '角色');
const sampleRole = rawRole.replace(/[((].*$/, '').replace(/…|\.{3}.*/, '').trim();
return { sampleUserName, sampleLoginName, sampleDept, sampleRole, sampleHospital, samplePhone };
const sampleStatus = await this.inferRowStatus(row);
return { sampleUserName, sampleLoginName, sampleDept, sampleRole, sampleStatus, sampleHospital, samplePhone };
}
async currentHospitalLabel(): Promise<string> {
......@@ -74,11 +98,23 @@ export class BasicUserUnionPage extends BasicUserPage {
if (sample.sampleHospital) {
await this.selectFormOption('所属医院', sample.sampleHospital).catch(() => undefined);
}
const phoneInput = this.page.getByPlaceholder(/联系方式/).or(this.locators.formItem('联系方式').locator('input'));
if (sample.samplePhone && sample.samplePhone.length >= 4 && (await phoneInput.first().isVisible().catch(() => false))) {
await this.fill(phoneInput.first(), sample.samplePhone, '联系方式');
if (sample.sampleDept && sample.sampleDept !== '--') {
await this.selectFormOption('科室', sample.sampleDept).catch(() => undefined);
}
await this.query();
if (sample.sampleRole) {
await this.selectFormOption('角色', sample.sampleRole).catch(() => undefined);
}
await this.expandAdvanced();
const panel = this.page.locator('.ant-drawer-open, .ant-pro-table-search-query-filter, .advance-filter-panel').first();
const root = (await panel.isVisible().catch(() => false)) ? panel : this.page;
await this.selectFormOption('状态', sample.sampleStatus);
const filterBtn = this.locators.button('筛选', root).first();
if (await filterBtn.isVisible().catch(() => false)) {
await this.click(filterBtn, '筛 选');
} else {
await this.query();
}
await this.waitForTableReady();
}
async addAndCancelOnHospital(): Promise<string> {
......
import { expect, type Page } from '@playwright/test';
import { expect, type Locator, type Page } from '@playwright/test';
import { userData, uniqLogin } from '../../data/user.data';
import { BasicUserLocators } from '../../locators/spd/basic-user.locator';
import { BasicUserSharedPage, type UserMeta } from './basic-user-shared.page';
import {
ColumnSettingsControls,
executeColumnSettingsToggleRestore,
findUsableColumnSettingsTrigger,
} from './helpers/column-settings.helper';
export type UserSample = {
sampleUserName: string;
sampleLoginName: string;
sampleDept: string;
sampleRole: string;
sampleStatus: '启用' | '禁用';
};
/** 用户管理:动作与取数;断言在 tests/spd/basic-user.spec.ts。 */
export class BasicUserPage extends BasicUserSharedPage {
override readonly locators: BasicUserLocators;
private readonly columnSettingsControls: ColumnSettingsControls;
constructor(page: Page, meta?: UserMeta) {
super(
page,
......@@ -26,6 +34,13 @@ export class BasicUserPage extends BasicUserSharedPage {
},
);
this.locators = new BasicUserLocators(page);
this.columnSettingsControls = new ColumnSettingsControls({
columnSettingsPanel: () => this.locators.columnSettingsPanel(),
resolveTrigger: () => this.resolveColumnSettingsTrigger(),
reportBody: page.locator('body'),
clickOutsideTarget: this.locators.nameInput,
tableClickTarget: this.locators.tableWrapper,
});
}
async extractSample(): Promise<UserSample> {
......@@ -33,10 +48,31 @@ export class BasicUserPage extends BasicUserSharedPage {
const row = await this.firstRow();
const sampleUserName = await this.cellText(row, '姓名');
const sampleLoginName = await this.cellText(row, '登录账号');
const sampleDept = await this.sampleCell('所属科室');
const sampleDept = await this.cellText(row, '所属科室');
const rawRole = await this.tryCell(row, '角色');
const sampleRole = rawRole.replace(/[((].*$/, '').replace(/…|\.{3}.*/, '').trim();
return { sampleUserName, sampleLoginName, sampleDept, sampleRole };
const sampleStatus = await this.inferRowStatus(row);
return { sampleUserName, sampleLoginName, sampleDept, sampleRole, sampleStatus };
}
/** 列表「状态」列或行内启用/禁用文案。 */
async inferRowStatus(row: Locator): Promise<'启用' | '禁用'> {
const fromCol = (await this.tryCell(row, '状态')).replace(/\s/g, '');
if (fromCol.includes('禁用')) return '禁用';
if (fromCol.includes('启用')) return '启用';
const rowText = ((await row.innerText()) ?? '').replace(/\s/g, '');
return rowText.includes('禁用') ? '禁用' : '启用';
}
async rowsMatchStatus(status: '启用' | '禁用', sample = 5): Promise<boolean> {
const rows = await this.listRows();
const count = await rows.count();
if (count === 0) return false;
const limit = Math.min(count, sample);
for (let i = 0; i < limit; i += 1) {
if ((await this.inferRowStatus(rows.nth(i))) !== status) return false;
}
return true;
}
async queryByName(keyword: string): Promise<void> {
......@@ -57,7 +93,17 @@ export class BasicUserPage extends BasicUserSharedPage {
if (sample.sampleRole) {
await this.selectFormOption('角色', sample.sampleRole).catch(() => undefined);
}
await this.query();
await this.expandAdvanced();
const panel = this.page.locator('.ant-drawer-open, .ant-pro-table-search-query-filter, .advance-filter-panel').first();
const root = (await panel.isVisible().catch(() => false)) ? panel : this.page;
await this.selectFormOption('状态', sample.sampleStatus);
const filterBtn = this.locators.button('筛选', root).first();
if (await filterBtn.isVisible().catch(() => false)) {
await this.click(filterBtn, '筛 选');
} else {
await this.query();
}
await this.waitForTableReady();
}
async queryMockEmpty(): Promise<void> {
......@@ -139,4 +185,183 @@ export class BasicUserPage extends BasicUserSharedPage {
if (!(await tree.isVisible().catch(() => false))) return 0;
return tree.locator('.ant-tree-checkbox-checked').count();
}
/** 任选首屏一条登录账号非空的行,供授权等行内操作。 */
async pickRowWithLogin(): Promise<{
row: Locator;
sampleLoginName: string;
sampleUserName: string;
}> {
const rows = await this.listRows();
const count = Math.min(await rows.count(), 12);
for (let i = 0; i < count; i += 1) {
const row = rows.nth(i);
const login = (await this.tryCell(row, '登录账号')).replace(/\s/g, '');
const name = (await this.tryCell(row, '姓名')).trim();
if (login) {
return { row, sampleLoginName: login, sampleUserName: name };
}
}
await this.skipWhenDataInsufficient('列表无可用登录账号');
return { row: await this.firstRow(), sampleLoginName: '', sampleUserName: '' };
}
userRoleAuthLayer(): Locator {
return this.dialog()
.filter({ hasText: /用户角色授权|授权/ })
.or(this.browserPage.locator('.ant-drawer-open').filter({ hasText: /用户角色授权|授权/ }))
.first();
}
async openAuthOnRow(row: Locator): Promise<void> {
await this.click(this.rowAction(row, '授权'), '授权');
await this.waitSettle(400);
}
async expectUserRoleAuthDialogAccount(login: string, userName: string): Promise<void> {
const layer = this.userRoleAuthLayer();
await expect(layer, '用户角色授权弹框').toBeVisible({ timeout: 12_000 });
const title = layer.locator('.ant-modal-title, .ant-drawer-title').first();
await expect(title, '弹框标题').toContainText(/用户角色授权|授权/);
const compactLogin = login.replace(/\s/g, '');
const bodyText = ((await layer.innerText()) ?? '').replace(/\s/g, '');
expect(bodyText, '弹框账号应与所选行登录账号一致').toContain(compactLogin);
const compactName = userName.replace(/\s/g, '');
if (compactName) {
expect(bodyText, '弹框应展示所选行姓名').toContain(compactName);
}
}
authRemoveButtons(): Locator {
return this.userRoleAuthLayer().getByRole('button', { name: /^移\s*除$/ });
}
async authRemovableRowCount(): Promise<number> {
await expect(this.userRoleAuthLayer(), '用户角色授权弹框').toBeVisible({ timeout: 12_000 });
return this.authRemoveButtons().count();
}
async clickAuthAddRow(): Promise<void> {
const layer = this.userRoleAuthLayer();
const add = layer.getByRole('button', { name: /^添\s*加$/ }).first();
if (!(await add.isVisible().catch(() => false))) {
await this.skipWhenDataInsufficient('授权弹框无「添加」按钮(可能为权限树模式)');
}
await this.click(add, '添加');
await this.waitSettle(400);
}
async clickAuthRemoveLastRow(): Promise<void> {
const removes = this.authRemoveButtons();
const count = await removes.count();
if (count === 0) {
throw new Error('授权弹框内无「移除」按钮');
}
await this.click(removes.nth(count - 1), '移除');
await this.waitSettle(400);
}
/** 弹框内角色行:添加一行再移除最后一行(不点保存)。 */
async runAuthAddThenRemove(): Promise<{
before: number;
afterAdd: number;
afterRemove: number;
}> {
const before = await this.authRemovableRowCount();
await this.clickAuthAddRow();
const afterAdd = await this.authRemovableRowCount();
await this.clickAuthRemoveLastRow();
const afterRemove = await this.authRemovableRowCount();
return { before, afterAdd, afterRemove };
}
private async resolveColumnSettingsTrigger(): Promise<Locator> {
const candidates = [this.locators.columnSettingsTrigger];
let trigger = await findUsableColumnSettingsTrigger(candidates);
if (!trigger) {
await this.locators.tableWrapper.scrollIntoViewIfNeeded().catch(() => undefined);
const body = this.locators.tableWrapper.locator('.ant-table-body').first();
await body
.evaluate((el) => {
(el as HTMLElement).scrollLeft = (el as HTMLElement).scrollWidth;
})
.catch(() => undefined);
await this.waitSettle(200);
trigger = await findUsableColumnSettingsTrigger(candidates);
}
if (!trigger) {
await this.skipWhenDataInsufficient('列设置齿轮不可见');
}
return trigger!;
}
async openColumnSettings(): Promise<void> {
const panel = this.locators.columnSettingsPanel();
if (await panel.isVisible().catch(() => false)) {
return;
}
const trigger = await this.resolveColumnSettingsTrigger();
await this.click(trigger, '打开列设置');
await expect(panel, '列设置面板').toBeVisible({ timeout: 10_000 });
}
async expectColumnSettingsPanel(): Promise<void> {
const panel = this.locators.columnSettingsPanel();
await expect(panel).toBeVisible();
await expect(panel.getByText('列展示')).toBeVisible();
await expect(panel.getByText('字段设置')).toBeVisible();
await expect(panel.getByText('重置', { exact: true })).toBeVisible();
}
async closeColumnSettingsPanel(): Promise<void> {
await this.columnSettingsControls.closePanel();
}
async pickToggleColumnName(): Promise<string> {
for (const name of userData.columnSettingsToggleCandidates) {
if (await this.locators.columnHeaderCell(name).isVisible().catch(() => false)) {
return name;
}
}
return userData.columnSettingsToggleCandidates[0];
}
async setColumnVisibleInSettings(columnName: string, visible: boolean): Promise<void> {
const row = this.locators.columnSettingsCheckboxRow(columnName);
await expect(row).toBeVisible();
const checkbox = row.locator('input[type="checkbox"]').first();
const isChecked = await checkbox.isChecked();
if (isChecked !== visible) {
await row.click();
}
}
async expectTableHeaderVisible(columnName: string, visible: boolean): Promise<void> {
const header = this.locators.columnHeaderCell(columnName);
if (visible) {
await expect(header).toBeVisible({ timeout: 10_000 });
} else {
await expect(header).toBeHidden({ timeout: 10_000 });
}
}
async expectSettingsCheckboxChecked(label: string, checked: boolean): Promise<void> {
const checkbox = this.locators.columnSettingsCheckboxRow(label).locator('input[type="checkbox"]').first();
if (checked) {
await expect(checkbox).toBeChecked();
} else {
await expect(checkbox).not.toBeChecked();
}
}
async executeColumnSettingsToggleRestoreTest(): Promise<void> {
await executeColumnSettingsToggleRestore({
pickToggleColumnName: () => this.pickToggleColumnName(),
openColumnSettings: () => this.openColumnSettings(),
setColumnVisibleInSettings: (name, visible) => this.setColumnVisibleInSettings(name, visible),
closeColumnSettingsPanel: () => this.closeColumnSettingsPanel(),
expectTableHeaderVisible: (name, visible) => this.expectTableHeaderVisible(name, visible),
expectSettingsCheckboxChecked: (name, checked) => this.expectSettingsCheckboxChecked(name, checked),
});
}
}
......@@ -66,7 +66,8 @@ PAGES = [
count=219,
spec="UI-SPEC-SPD-USER-001",
spec_file="basic-user.spec.ts",
tp_total=81,
spec_ready=True,
tp_total=82,
write_note=(
"新增/编辑只打开后取消,不提交保存;授权只打开后取消;"
"重置密码只验证确认框后取消,不确认改密。禁止写死生产账号。"
......@@ -83,206 +84,142 @@ PAGES = [
skip="真实保存用户、禁用后登录、真实改密、Excel 导入文件、接口失败注入、会话过期、无菜单权限",
cases=[
case(
1, "页面进入", "功能验证点", "P0", "菜单进入用户管理页", 1,
1, "筛选与查询", "功能验证点", "P0", "姓名+科室+角色+状态组合查询", 13,
[
"按菜单「基础设置 → 用户管理(子菜单)→ 用户管理(叶子)」进入,或 `openPath` 打开 `/spd/basic/user/platform/person`",
"断言 URL 含 `/spd/basic/user/platform/person`",
"断言筛选区、工具栏「新 增」「Excel导入」、列表列头与分页可见",
"点击「查 询」后读取首行 `${sampleUserName}`、`${sampleLoginName}`、`${sampleDept}`、`${sampleRole}`、`${sampleStatus}`(角色取截断前列名;状态优先读列表「状态」列,否则按行内启用/禁用文案推断)",
"填写姓名、科室、角色,展开高级筛选选择 `${sampleStatus}` 后点「筛 选」(无「筛 选」则点「查 询」)",
"断言结果非空;首行登录账号与采样行一致;可见行姓名/科室/角色/状态与已填条件一致",
],
"进入用户管理页;不误入医共体用户管理或角色管理;无报错遮罩。",
auto="USER-USR-001",
"组合查询无报错;查询结果与首行采样条件一致且同时满足全部已填条件。",
pre="默认查询至少 1 条", auto="USER-USR-001",
),
case(
2, "页面进入", "异常验证点", "P0", "未登录访问拦截", 5,
[
"清除登录态后直接打开 `/spd/basic/user/platform/person`",
"断言跳转登录或不展示用户业务列表",
],
"未登录不可见用户数据。",
),
case(
3, "筛选与查询", "功能验证点", "P0", "默认查询列表有数据或空态明确", 9,
[
"点击「查 询」并等待列表稳定",
"断言出现数据行,或「暂无数据」且分页总数为 0",
],
"查询无报错;有数据时分页「共 N 条」N≥1;无数据时空态明确。",
auto="USER-USR-002",
),
case(
4, "筛选与查询", "功能验证点", "P0", "姓名或登录账号关键字查询", 10,
[
"从首行读取 `${sampleUserName}` 或 `${sampleLoginName}` 取关键字",
"在「姓名」框(占位「姓名、登录账号」)填入关键字,点击「查 询」",
"断言结果行姓名或登录账号匹配该关键字",
],
"查询结果与已填关键字匹配;无未捕获异常。",
pre="默认查询至少 1 条且姓名或登录账号非空", auto="USER-USR-003",
),
case(
5, "筛选与查询", "功能验证点", "P0", "科室下拉筛选", 11,
[
"取首条所属科室非 `--` 的行,读取 `${sampleDept}`",
"科室下拉选择该值,点击「查 询」",
"断言结果行「所属科室」与所选一致,且不含未分配 `--` 行",
],
"所属科室列与筛选条件一致。",
pre="列表存在所属科室非空行,否则 skip", auto="USER-USR-004",
),
case(
6, "筛选与查询", "功能验证点", "P0", "姓名+科室+角色组合查询", 13,
[
"读取 `${sampleUserName}`、`${sampleDept}`、`${sampleRole}`(角色可从该行截断前列名)",
"填写姓名、科室、角色后点击「查 询」",
"断言结果同时满足已填条件",
],
"组合查询无报错;结果同时满足全部已填条件。",
pre="默认查询至少 1 条", auto="USER-USR-005",
),
case(
7, "筛选与查询", "功能验证点", "P1", "重置恢复默认", 14,
2, "筛选与查询", "功能验证点", "P1", "重置恢复默认", 14,
[
"填写姓名等筛选条件后点击「重 置」",
"断言筛选字段恢复默认,列表按默认规则刷新",
],
"条件恢复默认;列表刷新。",
auto="USER-USR-006",
auto="USER-USR-002",
),
case(
8, "筛选与查询", "功能验证点", "P1", "展开高级筛选", 15,
3, "筛选与查询", "功能验证点", "P1", "展开高级筛选", 15,
[
"点击「高级筛选」",
"断言展开后面板可见姓名/科室/角色/状态及「重 置」「筛 选」",
],
"高级筛选面板展开;关键字段与筛选按钮可见。",
auto="USER-USR-007",
auto="USER-USR-003",
),
case(
9, "筛选与查询", "功能验证点", "P0", "高级筛选按状态过滤", 16,
4, "筛选与查询", "功能验证点", "P0", "高级筛选按状态过滤", 16,
[
"展开高级筛选,状态下拉选择「启用」或「禁用」",
"点击「筛 选」",
"断言列表仅保留对应状态用户(无匹配则空态且总数为 0)",
],
"按状态过滤成功;无报错。",
auto="USER-USR-008",
auto="USER-USR-004",
),
case(
10, "筛选与查询", "异常验证点", "P1", "无匹配结果空态(mock反例)", 22,
5, "筛选与查询", "异常验证点", "P1", "无匹配结果空态(mock反例)", 22,
[
"姓名框填入 `__NOT_EXIST_USER__99999`",
"点击「查 询」",
"断言空态或「暂无数据」,分页总数为 0",
],
"无匹配时不报错;空态明确;总数为 0。",
auto="USER-USR-009",
auto="USER-USR-005",
),
case(
11, "列表与分页", "功能验证点", "P0", "列表核心列完整", 25,
[
"断言表头含序号、姓名、登录账号、联系方式、所属科室、医院员工编号、角色、科室任职数、操作",
"断言操作列可见「编辑」「授权」「重置密码」",
],
"核心列完整;行内三个操入口可见。",
auto="USER-USR-010",
),
case(
12, "列表与分页", "功能验证点", "P1", "列表不含所属医院列", 26,
[
"断言表头不含「所属医院」",
],
"本页无所属医院列,与医共体用户管理页区分。",
auto="USER-USR-011",
),
case(
13, "列表与分页", "功能验证点", "P1", "分页翻至第 2 页", 31,
6, "列表与分页", "功能验证点", "P1", "分页翻至第 2 页", 31,
[
"记录第 1 页首行登录账号",
"点击页码「2」或「下一页」",
"断言页码高亮为 2,首行账号与第 1 页不同",
],
"翻页成功;数据更换。",
pre="默认查询总条数大于一页,否则 skip", auto="USER-USR-012",
pre="默认查询总条数大于一页,否则 skip", auto="USER-USR-006",
),
case(
14, "新增用户", "功能验证点", "P0", "打开新增用户弹窗", 41,
7, "新增用户", "功能验证点", "P0", "打开新增用户弹窗", 41,
[
"点击「新 增」",
"断言弹出用户表单,可见账号、姓名、联系方式、医院员工编号、启用/禁用、「取 消」「确 定」",
],
"新增弹窗可见;关键字段与按钮齐全。",
auto="USER-USR-013",
auto="USER-USR-007",
),
case(
15, "新增用户", "功能验证点", "P1", "新增取消不落库", 43,
8, "新增用户", "功能验证点", "P1", "新增取消不落库", 43,
[
"打开新增,账号填 `${uniqLogin}`,姓名填任意合法值",
"点击「取 消」",
"按账号查询,断言无该记录",
],
"弹窗关闭;列表不出现 `${uniqLogin}`。",
auto="USER-USR-014",
auto="USER-USR-008",
),
case(
16, "新增用户", "异常验证点", "P0", "必填为空拦截", 47,
9, "新增用户", "异常验证点", "P0", "必填为空拦截", 47,
[
"打开新增,账号或姓名留空后点击「确 定」",
"断言出现必填提示,弹窗仍打开",
],
"确定被拦截;不落库;弹窗不关闭。",
auto="USER-USR-015",
auto="USER-USR-009",
),
case(
17, "新增用户", "异常验证点", "P0", "登录账号重复拦截", 48,
10, "新增用户", "异常验证点", "P0", "登录账号重复拦截", 48,
[
"打开新增,账号填入已存在的 `${sampleLoginName}`,补齐其它必填后点「确 定」",
"断言保存失败并提示,弹窗仍打开",
"查询该账号,断言原用户未被覆盖",
],
"重复账号被拒绝;不覆盖原用户。",
pre="默认查询至少 1 条且登录账号非空", auto="USER-USR-016",
pre="默认查询至少 1 条且登录账号非空", auto="USER-USR-010",
),
case(
18, "编辑用户", "功能验证点", "P0", "打开编辑用户弹窗并回填", 54,
11, "编辑用户", "功能验证点", "P0", "打开编辑用户弹窗并回填", 54,
[
"点击首行「编辑」",
"断言弹窗标题含「编辑用户」",
"断言账号、姓名与该行 `${sampleLoginName}`、`${sampleUserName}` 一致(允许规范化空白)",
],
"编辑弹窗可见;回填与列表行一致。",
pre="列表至少 1 条", auto="USER-USR-017",
pre="列表至少 1 条", auto="USER-USR-011",
),
case(
19, "编辑用户", "功能验证点", "P1", "编辑取消不保存", 58,
12, "编辑用户", "功能验证点", "P1", "编辑取消不保存", 58,
[
"打开首行编辑,记录原姓名",
"将姓名改为临时值后点「取 消」",
"再次打开编辑,断言姓名仍为原值",
],
"取消后列表与回看均为修改前姓名。",
pre="列表至少 1 条", auto="USER-USR-018",
pre="列表至少 1 条", auto="USER-USR-012",
),
case(
20, "编辑用户", "边界验证点", "P1", "编辑时账号只读", 61,
13, "编辑用户", "边界验证点", "P1", "编辑时账号只读", 61,
[
"打开首行编辑",
"断言登录账号不可改(disabled/readonly 或无法输入)",
],
"编辑态登录账号只读。",
pre="列表至少 1 条", auto="USER-USR-019",
pre="列表至少 1 条", auto="USER-USR-013",
),
case(
21, "编辑用户", "功能验证点", "P1", "编辑弹窗可见签名图片", 56,
14, "编辑用户", "功能验证点", "P1", "编辑弹窗可见签名图片", 56,
[
"打开首行编辑",
"断言可见「签名图片」区域(有图则展示,无图则上传入口可见)",
"点「取 消」,不保存",
],
"签名图片控件可见;本条不上传、不保存。",
pre="列表至少 1 条", auto="USER-USR-020",
pre="列表至少 1 条", auto="USER-USR-014",
),
case(
22, "授权", "功能验证点", "P0", "打开授权后取消不生效", 68,
15, "授权", "功能验证点", "P0", "打开授权后取消不生效", 68,
[
"点击首行「授权」",
"断言打开授权层(弹窗或权限树),可见可勾选菜单/功能",
......@@ -290,18 +227,62 @@ PAGES = [
"再次打开同一用户授权,断言勾选与操作前一致",
],
"授权层可打开;取消后勾选不落库。",
pre="列表至少 1 条", auto="USER-USR-021",
pre="列表至少 1 条", auto="USER-USR-015",
),
case(
16, "列表与分页", "功能验证点", "P1", "切换20条每页页面正常且列表同步", 32,
[
"点击「查 询」,记录分页「共 N 条」与当前页可见行数",
"分页区切换每页条数为「20 条/页」",
"断言分页显示 20 条/页;总条数 N 不变;当前页行数为 min(N,20) 且与切换前不同;筛选区、表头可见无报错",
],
"切换 20 条/页成功;页面正常;列表可见行数随每页条数同步变化且总数不变。",
pre="默认查询总条数大于 10 且切换前可见行数不等于 min(N,20),否则 skip",
auto="USER-USR-016",
),
case(
23, "重置密码", "功能验证点", "P0", "行内重置密码确认后取消", 75,
17, "列表与分页", "功能验证点", "P1", "打开列设置面板", 25,
[
"点击首行「重置密码」",
"断言出现确认框或密码表单,含取消/关闭",
"点取消/关闭",
"断言仍停留本页且该行仍在",
"点击「查 询」等待列表稳定",
"点击表格工具栏列设置(齿轮)图标",
"断言弹出面板含「列展示」「字段设置」「重置」,关闭面板",
],
"重置需确认;取消后不改密。本条不点确定,避免污染账号。",
pre="列表至少 1 条", auto="USER-USR-022",
"列设置面板可打开;关键项可见;关闭后列表正常。",
pre="默认查询至少 1 条", auto="USER-USR-017",
),
case(
18, "列表与分页", "功能验证点", "P1", "列设置取消勾选后列表列恢复", 25,
[
"点击「查 询」后打开列设置,取消勾选一列非核心字段并关闭",
"断言该列表头隐藏;再次打开列设置勾选恢复并关闭",
"断言该列表头重新可见",
],
"取消勾选后表头隐藏;再勾选后表头恢复;无报错遮罩。",
pre="默认查询至少 1 条;存在可 toggle 的非核心列,否则 skip", auto="USER-USR-018",
),
case(
19, "授权", "功能验证点", "P0", "授权弹框账号与所选行一致", 68,
[
"点击「查 询」,任选列表一条登录账号非空的行,记录 `${sampleLoginName}`、`${sampleUserName}`",
"点击该行「授权」",
"断言弹出用户角色授权弹框,弹框展示的账号信息与所选行登录账号、姓名一致",
"关闭弹框",
],
"用户角色授权弹框打开;弹框账号与所选列表行一致。",
pre="列表至少 1 条且登录账号非空", auto="USER-USR-019",
),
case(
20, "授权", "功能验证点", "P1", "授权弹框添加移除角色行", 82,
[
"点击「查 询」,任选列表一条数据行,点击该行「授权」",
"断言弹出用户角色授权弹框",
"记录弹框内可「移除」的角色行数,点击「添加」",
"断言可移除行数 +1;点击新增行的「移除」",
"断言可移除行数恢复为添加前;关闭弹框且不保存",
],
"添加后列表下方新增一行;移除后新增行被删除;未保存不落库。",
pre="列表至少 1 条;授权弹框含「添加/移除」(非仅权限树模式),否则 skip",
auto="USER-USR-020",
),
],
),
......@@ -314,6 +295,7 @@ PAGES = [
count=221,
spec="UI-SPEC-SPD-USER-002",
spec_file="basic-user-union.spec.ts",
spec_ready=True,
tp_total=58,
write_note=(
"新增/编辑只打开后取消,不提交保存;授权与改密规则同用户管理,本页不重复真实写操作。"
......@@ -375,14 +357,14 @@ PAGES = [
auto="USER-UNI-004",
),
case(
6, "筛选与查询", "功能验证点", "P0", "姓名+医院+联系方式组合查询", 13,
6, "筛选与查询", "功能验证点", "P0", "姓名+医院+科室+角色+状态组合查询", 13,
[
"读取 `${sampleUserName}`、`${sampleHospital}`、`${samplePhone}`(电话非法占位如 `1` 时改用姓名+医院)",
"填写后点击「查 询」",
"断言结果同时满足已填条件",
"点击「查 询」后读取首行 `${sampleUserName}`、`${sampleLoginName}`、`${sampleHospital}`、`${sampleDept}`、`${sampleRole}`、`${sampleStatus}`(科室为 `--` 时换一行)",
"填写姓名、所属医院、科室、角色,展开高级筛选选择 `${sampleStatus}` 后点「筛 选」(无则点「查 询」)",
"断言结果非空;首行登录账号与采样行一致;可见行姓名/所属医院/科室/角色/状态与已填条件一致",
],
"组合条件同时生效。",
pre="默认查询至少 1 条", auto="USER-UNI-005",
"组合查询无报错;结果与采样条件一致且同时满足全部已填条件。",
pre="默认查询至少 1 条", auto="USER-UNI-006",
),
case(
7, "筛选与查询", "功能验证点", "P1", "重置恢复默认医院", 14,
......@@ -475,7 +457,84 @@ PAGES = [
"点「取 消」",
],
"编辑回填正确;本条不保存。",
pre="列表至少 1 条", auto="USER-UNI-015",
pre="列表至少 1 条", auto="USER-UNI-016",
),
case(
17, "筛选与查询", "功能验证点", "P0", "高级筛选按状态过滤", 42,
[
"展开高级筛选,状态下拉选择「启用」或「禁用」",
"点击「筛 选」",
"断言列表仅保留对应状态用户(无匹配则空态且总数为 0)",
],
"按状态过滤成功;无报错。",
auto="USER-UNI-017",
),
case(
18, "列表与分页", "功能验证点", "P1", "切换20条每页页面正常且列表同步", 27,
[
"点击「查 询」,记录分页「共 N 条」与当前页可见行数",
"分页区切换每页条数为「20 条/页」",
"断言分页显示 20 条/页;总条数 N 不变;当前页行数为 min(N,20) 且与切换前不同;所属医院筛选、表头可见无报错",
],
"切换 20 条/页成功;页面正常;列表可见行数随每页条数同步变化且总数不变。",
pre="默认查询总条数大于 10 且切换前可见行数不等于 min(N,20),否则 skip",
auto="USER-UNI-018",
),
case(
19, "列表与分页", "功能验证点", "P1", "打开列设置面板", 25,
[
"点击「查 询」等待列表稳定",
"点击表格工具栏列设置(齿轮)图标",
"断言弹出面板含「列展示」「字段设置」「重置」,关闭面板",
],
"列设置面板可打开;关键项可见;关闭后列表正常。",
pre="默认查询至少 1 条", auto="USER-UNI-019",
),
case(
20, "列表与分页", "功能验证点", "P1", "列设置取消勾选后列表列恢复", 25,
[
"点击「查 询」后打开列设置,取消勾选一列非核心字段并关闭",
"断言该列表头隐藏;再次打开列设置勾选恢复并关闭",
"断言该列表头重新可见",
],
"取消勾选后表头隐藏;再勾选后表头恢复;无报错遮罩。",
pre="默认查询至少 1 条;存在可 toggle 的非核心列,否则 skip",
auto="USER-UNI-020",
),
case(
21, "授权", "功能验证点", "P0", "打开授权后取消不生效", 51,
[
"点击首行「授权」",
"断言打开用户角色授权层,可见可勾选菜单/功能",
"如可勾选则改一处勾选后点取消/关闭",
"再次打开同一用户授权,断言勾选与操作前一致",
],
"授权层可打开;取消后勾选不落库。",
pre="列表至少 1 条", auto="USER-UNI-021",
),
case(
22, "授权", "功能验证点", "P0", "授权弹框账号与所选行一致", 51,
[
"点击「查 询」,任选列表一条登录账号非空的行,记录 `${sampleLoginName}`、`${sampleUserName}`",
"点击该行「授权」",
"断言弹出用户角色授权弹框,弹框展示的账号信息与所选行登录账号、姓名一致",
"关闭弹框",
],
"用户角色授权弹框打开;弹框账号与所选列表行一致。",
pre="列表至少 1 条且登录账号非空", auto="USER-UNI-022",
),
case(
23, "授权", "功能验证点", "P1", "授权弹框添加移除角色行", 51,
[
"点击「查 询」,任选列表一条数据行,点击该行「授权」",
"断言弹出用户角色授权弹框",
"记录弹框内可「移除」的角色行数,点击「添加」",
"断言可移除行数 +1;点击新增行的「移除」",
"断言可移除行数恢复为添加前;关闭弹框且不保存",
],
"添加后列表下方新增一行;移除后新增行被删除;未保存不落库。",
pre="列表至少 1 条;授权弹框含「添加/移除」(非仅权限树模式),否则 skip",
auto="USER-UNI-023",
),
],
),
......@@ -490,153 +549,150 @@ PAGES = [
spec_file="basic-role.spec.ts",
tp_total=45,
write_note=(
"权限树勾选后点「刷 新」恢复,不点「保 存」;"
"删除只验证确认框后取消;新增只打开后取消。"
"禁止删除预置角色或真实改名。"
"权限树 001/002 只测展开收起,不保存授权;003 切换同步。"
"编辑 004 取消;005 不改名保存(受 SKIP_BASIC_WRITE)。"
"新增 006–010 不提交落库。"
),
nav_note="菜单「基础设置 → 用户管理 → 角色管理」;本页左右分栏、无独立分页。",
samples=(
"左栏读取 `${sampleRoleName}`(优先非预置自定义角色;预置用「医院管理员」若存在);"
"权限树按「模块名 + 菜单名」定位,根为「SPD内网系统PC端」;"
"mock:`__NOT_EXIST_ROLE__99999`。"
"权限树按「模块名 + 菜单名」定位,根为「SPD内网系统PC端」。"
),
skip=(
"未登录与菜单进入、角色搜索与空态、权限树勾选联动/刷新/保存、"
"删除与预置角色、与用户页角色下拉同源、真实新增角色落库、未保存切角色提示等"
),
skip="真实保存权限树、真实新增/改名/删除角色、未保存切角色(待评审)、角色授权后换账号登录、接口失败",
include_mock=False,
spec_ready=True,
cases=[
case(
1, "页面进入", "功能验证点", "P0", "菜单进入角色管理页", 1,
[
"按菜单「基础设置 → 用户管理 → 角色管理」进入,或 `openPath` 打开 `/spd/basic/user/platform/role`",
"断言 URL 含 `/spd/basic/user/platform/role`",
"断言左侧角色列表、右侧菜单权限树、「保 存」「刷 新」可见,无独立分页",
],
"进入角色管理;左右分栏渲染完成;不误入用户管理页。",
auto="USER-ROL-001",
),
case(
2, "页面进入", "异常验证点", "P0", "未登录访问拦截", 5,
[
"清除登录态后直接打开 `/spd/basic/user/platform/role`",
"断言跳转登录或不展示角色/权限树业务数据",
],
"未登录不可见角色配置。",
),
case(
3, "左侧角色列表", "功能验证点", "P0", "角色列表加载并可点选", 9,
1, "右侧菜单权限树", "功能验证点", "P0", "权限树按应用与模块展开", 18,
[
"断言左栏列出角色名称(如医院管理员、护士等)",
"点击首个 `${sampleRoleName}`",
"断言该行处于选中态",
"选中任一角色",
"断言右侧树根为「SPD内网系统PC端」,可见基础设置/运维管理/厂商管理/产品管理等模块",
"展开「基础设置」,断言可见子菜单(如医院资料)",
],
"左栏有角色且可点选。",
auto="USER-ROL-002",
"权限树层级可见;节点可展开。",
),
case(
4, "左侧角色列表", "功能验证点", "P0", "角色名称搜索过滤", 10,
2, "右侧菜单权限树", "功能验证点", "P0", "权限树模块节点收起正常", 18,
[
"在角色名称框输入 `${sampleRoleName}` 的部分关键字",
"断言左栏收敛为匹配项(即时或回车,以现网为准)",
"选中任一角色",
"展开「基础设置」,断言可见子菜单(如医院资料)",
"点击「基础设置」节点展开图标收起该模块",
"断言子菜单(如医院资料)不可见,且「基础设置」节点处于收起态",
],
"搜索结果角色名包含关键字。",
pre="左栏至少 1 个角色", auto="USER-ROL-003",
"权限树节点可收起;子级随父节点隐藏。",
),
case(
5, "左侧角色列表", "功能验证点", "P0", "选中角色刷新右侧权限树", 11,
3, "左侧角色列表", "功能验证点", "P0", "切换角色右侧权限树同步更新", 11,
[
"依次点选两个不同角色",
"断言右侧勾选随当前角色变化,无上一角色残留",
"点选左栏第一个角色,记录右侧权限树勾选快照",
"点选左栏第二个不同角色,断言该行选中且右侧权限树仍可见",
"再次点选第一个角色,断言右侧权限树勾选快照与步骤 2 一致",
],
"权限树与当前角色配置一致。",
pre="左栏至少 2 个角色", auto="USER-ROL-004",
"切换后右侧与当前角色一致;切回后恢复该角色已保存勾选,无错乱残留。",
pre="左栏至少 2 个角色",
),
case(
6, "左侧角色列表", "功能验证点", "P1", "清空搜索恢复全量", 12,
4, "角色增删改", "功能验证点", "P0", "编辑角色弹框回填名称一致", 33,
[
"先按关键字过滤,再清空搜索",
"断言左栏恢复未过滤的完整角色列表",
"点选左栏任一角色,记录其角色名称",
"点击左栏右下角(或行内)编辑图标",
"断言弹出编辑角色弹框,标题或内容含「编辑」",
"断言弹框内「角色名称」输入框回填值与所选角色名称一致",
"点「取 消」或「关 闭」关闭弹框,不保存",
],
"清空后角色列表恢复全量。",
auto="USER-ROL-005",
"编辑弹框正常打开;角色名称与当前选中角色一致;本条不保存。",
pre="左栏至少 1 个角色",
),
case(
7, "左侧角色列表", "异常验证点", "P1", "搜索无匹配空态(mock反例)", 16,
5, "角色增删改", "功能验证点", "P0", "编辑角色保存成功", 33,
[
"角色名称框填入 `__NOT_EXIST_ROLE__99999`",
"断言左栏空态明确",
"点选左栏任一角色",
"点击左栏右下角(或行内)编辑图标,断言弹出编辑角色弹框",
"不修改角色名称,点击弹框内「保 存」",
"断言页面无报错提示,且出现「编辑成功」类成功提示",
"断言编辑弹框关闭",
],
"无匹配不报错;空态明确。",
auto="USER-ROL-006",
"保存成功;提示编辑成功;角色数据无异常报错。",
pre="左栏至少 1 个可编辑角色;环境允许写操作(非 SKIP_BASIC_WRITE=1)",
),
case(
8, "右侧菜单权限树", "功能验证点", "P0", "权限树按应用与模块展开", 18,
6, "角色增删改", "功能验证点", "P0", "点击角色按钮打开新增弹框", 32,
[
"选中任一角色",
"断言右侧树根为「SPD内网系统PC端」,可见基础设置/运维管理/厂商管理/产品管理等模块",
"展开「基础设置」,断言可见子菜单(如医院资料)",
"点击左栏工具区「角 色」按钮",
"断言弹出新增角色弹框,标题或内容含「新增」或「角色」",
"断言页面无报错提示(无错误 toast/遮罩)",
"点「取 消」或「关 闭」关闭弹框,不保存",
],
"权限树层级可见;节点可展开。",
auto="USER-ROL-007",
"新增角色弹框正常打开;页面无报错;本条不提交保存。",
),
case(
9, "右侧菜单权限树", "功能验证点", "P0", "勾选父节点联动子节点", 19,
7, "角色增删改", "异常验证点", "P0", "新增角色名称为空保存拦截", 39,
[
"选中一自定义或可编辑角色(预置被锁则 skip)",
"记录「厂商管理」父节点原勾选",
"勾选「厂商管理」父节点",
"断言其子菜单/功能全部勾选",
"点击「刷 新」,不点「保 存」",
"点击左栏工具区「角 色」按钮,断言弹出新增角色弹框",
"不填写角色名称,直接点击弹框内「保 存」",
"断言「角色名称」输入框呈校验失败态(变红)",
"断言同字段下方提示:请输入角色名称!",
"点「取 消」关闭弹框,不保存",
],
"父节点勾选联动子节点;本条不保存。",
auto="USER-ROL-008",
"空名称保存被拦截;角色名称框变红并提示请输入角色名称!;弹框不提交。",
),
case(
10, "右侧菜单权限树", "功能验证点", "P1", "刷新恢复未保存勾选", 23,
8, "角色增删改", "功能验证点", "P0", "新增角色弹框取消关闭", 32,
[
"改一处勾选后点「刷 新」且未保存",
"断言树恢复为进入该角色时的已保存状态",
"点击左栏工具区「角 色」按钮,断言弹出新增角色弹框",
"不填写任何内容,直接点击弹框内「取 消」",
"断言新增角色弹框关闭",
"断言页面无报错提示,左栏角色数量不增加",
],
"刷新丢弃未保存勾选。",
auto="USER-ROL-009",
"取消后弹框关闭;页面无报错;未新增角色。",
),
case(
11, "角色增删改", "功能验证点", "P0", "删除需确认且取消保留", 34,
9, "角色增删改", "功能验证点", "P0", "新增弹框查询配置化复选框可勾选", 32,
[
"点一自定义角色的删除入口(预置角色无删除则改点自定义;都没有则 skip)",
"断言出现确认框,含取消/确定",
"点「取 消」",
"断言该角色仍在左栏",
"点击左栏工具区「角 色」按钮,断言弹出新增角色弹框",
"定位「查询配置化」复选框,点击勾选,断言为选中态",
"再次点击取消勾选,断言为未选中态",
"断言页面无报错提示",
"点「取 消」关闭弹框,不保存",
],
"删除需二次确认;取消后角色仍在。本条不点确定。",
auto="USER-ROL-010",
"查询配置化可正常勾选与取消勾选;页面无报错;本条不提交保存。",
pre="新增弹框含「查询配置化」复选框",
),
case(
12, "角色增删改", "功能验证点", "P1", "新增角色入口打开后取消", 32,
10, "角色增删改", "功能验证点", "P0", "新增角色弹框X关闭", 32,
[
"通过新增或「角色」工具按钮打开新增表单(扫描未打开则 skip)",
"不填或填写后点取消/关闭",
"断言左栏角色数量不增加",
"点击左栏工具区「角 色」按钮,断言弹出新增角色弹框",
"点击弹框右上角「X」关闭图标",
"断言新增角色弹框关闭",
"断言页面无报错提示,左栏角色数量不增加",
],
"新增入口可达;取消不落库。",
auto="USER-ROL-011",
"X 关闭后弹框正常关闭;页面无报错;未新增角色。",
),
case(
13, "角色增删改", "异常验证点", "P1", "预置角色不可删除", 41,
11, "左侧角色列表", "功能验证点", "P1", "左栏角色列表可上下滑动", 9,
[
"在左栏定位「医院管理员」(或其它预置角色,以现网为准)",
"断言删除入口不可见;若可见则确定后被拒绝,角色仍在左栏",
"若弹出确认则点取消,避免误删",
"定位左栏角色列表滚动区域",
"向下滚动列表,断言 scrollTop 增加且列表可继续交互",
"向上滚回(滚轮或滑至顶部),断言 scrollTop 减小",
"断言页面无报错遮罩",
],
"预置角色不可删除。",
pre="左栏存在可识别的预置角色,否则 skip", auto="USER-ROL-012",
"左栏可正常上下滑动;滚动后页面无异常。",
pre="左栏列表高度溢出可滚动(不足则 skip)",
),
case(
14, "左侧角色列表", "集成验证点", "P1", "角色名与用户管理角色下拉同源", 17,
12, "右侧菜单权限树", "功能验证点", "P1", "右侧权限树子菜单可上下滑动", 25,
[
"记录本页左栏角色名称集合",
"打开用户管理,展开「角色」筛选项",
"断言两边名称集合一致(允许按权限裁剪,本页有的名称用户页应能对应到)",
"点选左栏任一角色,断言右侧菜单权限树可见",
"定位右侧权限树滚动区域(含子菜单列表)",
"向下滚动,断言 scrollTop 增加",
"向上滚回,断言 scrollTop 减小",
"断言页面无报错遮罩",
],
"角色主数据跨页一致,不串名。",
auto="USER-ROL-013",
"右侧子菜单列表可正常上下滑动;滚动后页面无异常。",
pre="已选中任一角色且右侧权限树高度溢出可滚动(不足则 skip)",
),
],
),
......@@ -716,19 +772,31 @@ def render_md(p: dict) -> str:
f"**元素扫描**:`data/scan/{p['scan']}-elements.json`({p['count']} 项) ",
f"**关联规范**:`knowledge-base/08-UI专项规范库/{p['spec']}.md` ",
f"**版本**:{TODAY} ",
f"**自动化框架**:Playwright + TypeScript(规划 `tests/spd/{p['spec_file']}`,依赖 `auth.setup` 登录态)",
f"**自动化框架**:Playwright + TypeScript("
f"{'`tests/spd/' + p['spec_file'] + '`' if p.get('spec_ready') else '规划 `tests/spd/' + p['spec_file'] + '`'}"
f",依赖 `auth.setup` 登录态)",
"",
f"**通用前置**:正例账号 `{POSITIVE_USER}` 经 `auth.setup` 写入 `storageState`(密码仅存 `.env` 的 `TEST_PASS`);具备「基础设置 → 用户管理 → {p['name']}」权限。反例筛选用 mock(`__NOT_EXIST_USER__99999` / `__NOT_EXIST_ROLE__99999`)。 ",
f"**通用前置**:正例账号 `{POSITIVE_USER}` 经 `auth.setup` 写入 `storageState`(密码仅存 `.env` 的 `TEST_PASS`);具备「基础设置 → 用户管理 → {p['name']}」权限。"
+ (
"反例筛选用 mock(`__NOT_EXIST_USER__99999` / `__NOT_EXIST_ROLE__99999`)。 "
if p.get("include_mock", True)
else " "
),
"**页面约定**:整页渲染,**无报表 iframe**;定位均在 `page` 上。 ",
f"**菜单导航**:{p['nav_note']} ",
f"**写操作**:{p['write_note']} ",
f"**动态取数**:{p['samples']}",
"",
"---",
"",
"## 基础设置 · 用户管理",
"",
]
if p.get("write_note"):
lines.append(f"**写操作**:{p['write_note']} ")
lines.append(f"**动态取数**:{p['samples']}")
lines.extend(
[
"",
"---",
"",
"## 基础设置 · 用户管理",
"",
]
)
current_h3 = ""
current_h4 = ""
for c in p["cases"]:
......@@ -750,7 +818,11 @@ def render_md(p: dict) -> str:
]
for i, step in enumerate(c["steps"], 1):
lines.append(f" {i}. {step}")
lines.append(f"- 自动化断言:规划与 CSV `{case_id}` 及 `tests/spd/{p['spec_file']}` 一致")
auto_verb = "与" if p.get("spec_ready") else "规划与"
auto_line = f"- 自动化断言:{auto_verb} CSV `{case_id}` 及 `tests/spd/{p['spec_file']}` 一致"
if p.get("spec_ready") and c["title"] == "编辑角色保存成功":
auto_line += "(`SKIP_BASIC_WRITE=1` 时 skip)"
lines.append(auto_line)
lines += ["", "###### 预期结果", "", c["expect"], "", "---", ""]
while lines and lines[-1] in ("", "---"):
lines.pop()
......
......@@ -195,7 +195,11 @@ def user_body(seq: Seq) -> str:
seq.add("中", "表格密度切换", "密度切换后列表仍可读,列不丢失"),
seq.add("低", "无批量操作", "序号列可勾选,但工具栏无批量启用/删除/授权;按「无批量」处理,不测批量提交"),
seq.add("中", "分页翻页", "总条数大于一页时可翻至第 2 页,数据更换且页码高亮正确"),
seq.add("中", "每页条数切换", "切换「50 条/页」等后当前页行数不超过所选值,总数不变"),
seq.add(
"中",
"每页条数切换",
"切换「20 条/页」「50 条/页」等后当前页行数为 min(N,所选值),总数不变(用户管理自动化 USER-USR-016 覆盖 20 条/页)",
),
seq.add("中", "跳至指定页", "分页「跳至」输入合法页码后跳到对应页"),
],
),
......@@ -309,6 +313,11 @@ def user_body(seq: Seq) -> str:
seq.add("高", "打开授权", "行内「授权」打开授权层(弹窗或权限树),展示可勾选菜单/功能"),
seq.add("高", "保存后勾选持久化", "勾选后保存,关闭再打开同一用户,勾选与保存时一致"),
seq.add("中", "取消不生效", "勾选后点取消/关闭,再次打开仍为操作前勾选"),
seq.add(
"中",
"授权角色行添加与移除",
"用户角色授权弹框内点「添加」增加一行,点「移除」删除新增行;不保存不落库(USER-USR-020)",
),
],
),
(
......@@ -380,7 +389,11 @@ def union_body(seq: Seq) -> str:
seq.add("高", "姓名/登录账号关键字", "「姓名」框(占位「姓名、登录账号」)输入后结果姓名或账号匹配"),
seq.add("高", "所属医院筛选", "切换「所属医院」后列表仅该院用户,「共 N 条」同步变化"),
seq.add("高", "联系方式筛选", "按「联系方式」关键字查询,结果联系方式匹配"),
seq.add("高", "姓名+医院+联系方式组合查询", "多条件同时生效,结果同时满足"),
seq.add(
"高",
"姓名+医院+科室+角色+状态组合查询",
"多条件同时生效,结果同时满足(USER-UNI-006)",
),
seq.add("中", "重置恢复默认", "点「重置」后医院等条件恢复默认(含默认医院),列表刷新"),
seq.add("中", "展开高级筛选", "点「高级筛选」展开后可见所属医院、联系方式、科室、角色及「筛选」"),
seq.add("中", "高级筛选按科室过滤", "高级筛选选择科室后点「筛选」,列表「所属科室」与所选一致"),
......@@ -785,7 +798,7 @@ def patch_index(counts: dict[str, int]) -> None:
),
"USER-003": (
"| 角色管理 | USER-003 | `/spd/basic/user/platform/role` | "
f"已扫描 · `basic-role` · **1170** 项(含权限树) · 测试点 **{counts['USER-003']}** 条 · `TP_USER_ROL` |"
f"已扫描 · `basic-role` · **1170** 项 · 测试点 **{counts['USER-003']}** 条 · UI 自动化 **12** 条 · `TP_USER_ROL` / `USER-ROL` |"
),
}
text = re.sub(
......
......@@ -345,7 +345,7 @@ USER_PAGES: list[dict] = [
("角色名称", "文本(左侧列表顶部)", "占位含「角色」,输入即过滤"),
],
"actions": "左侧搜索框实时过滤;右侧权限树按模块展开勾选;底部 **收起导航** 为全局导航按钮,非本页功能",
"toolbar": "左侧角色行内 **编辑** / 删除类图标按钮(右上角图标按钮按实际文案定位)",
"toolbar": "左栏 **「角 色」** 按钮(新增);选中后底部/行内 **编辑**、删除图标",
"columns": [
("角色名称", "左列主键;示例:医院管理员、中心库库管、护士、护士长、库管、测试01、test、验收-herp、验收-ict、角色测试123"),
("操作", "行内 **编辑** / 删除类图标按钮"),
......@@ -358,14 +358,14 @@ USER_PAGES: list[dict] = [
"菜单导航:`page.getByRole('menuitem', { name: '角色管理', exact: true }).click()`;或 `openPath('/spd/basic/user/platform/role')` 断言 URL。",
"左右分栏:先断言角色列表与权限树均可见;选中角色后再断言右侧勾选状态已刷新(避免读到上一个角色的树)。",
"权限树按模块展开,断言时优先定位「模块名 + 菜单名」层级,避免全局文本匹配到多份同名菜单。",
"授权保存属写操作:测试账号权限变更会影响其他用例,执行前记录原勾选,结束后还原。",
"新增弹窗:「角 色」按钮;含角色名称、查询配置化复选框;取消/X 关闭不落库。",
"自动化:`tests/spd/basic-role.spec.ts`(12 条 USER-ROL);`npm run test:basic-role:all`。",
],
"points": [
"角色搜索过滤结果与关键字匹配;清空后恢复全量。",
"切换角色后右侧菜单权限勾选与该角色已配置权限一致(无串选/残留)。",
"父节点勾选/取消后子节点全选/全不选;部分勾选时父节点为半选态。",
"保存授权后重新进入页面,勾选状态持久化;取消不保存。",
"角色增删改后,用户管理页「角色」下拉与用户行「角色」列同步更新。",
"权限树模块可展开/收起;长列表可纵向滚动(左栏角色列表、右侧子菜单树)。",
"新增/编辑角色弹窗:必填校验、查询配置化勾选、取消与 X 关闭。",
"编辑保存成功提示;权限树批量授权保存与用户页下拉同步见测试点层,当前自动化未全覆盖。",
],
"modules": [
("基础设置", "医院资料、医院信息登记、院区管理、公告管理、财务月份设置"),
......
"用例编号","功能模块","功能测试点","用例标题","优先级","前置条件","测试步骤","预期结果","实际结果"
"用例编号","功能模块","功能测试点","用例标题","优先级","前置条件","测试步骤","预期结果","实际结果"
"ADMIT-PLS-001","基础设置 · 准入管理","产品准入单列表 · 页面进入","菜单进入产品准入单列表","高","测试环境可访问;已通过 auth.setup 写入 storageState 登录(已登录);具备「基础设置 → 准入管理」权限;已进入产品准入单列表页(/spd/basic/dataSync/dataSync/productOrder/list,无 iframe)","1. 按菜单「基础设置 → 准入管理 → 产品准入单列表」进入
2. 断言 URL 含 `/spd/basic/dataSync/dataSync/productOrder/list`,且路径不是 `zrlist`
3. 断言制单日期、查询、状态 Tab、列表表头可见","进入产品准入单列表;不误入物料准入单列表。",""
......
......@@ -18,10 +18,10 @@ USER-UNI-005,基础设置 · 用户管理,医共体用户管理 · 筛选与查
3. 切换「所属医院」为另一可选项(仅 1 家则 skip)
4. 点击「查 询」
5. 断言结果行「所属医院」均为所选值,「共 N 条」可与默认不同",列表仅该院用户;总数随医院变化。,
USER-UNI-006,基础设置 · 用户管理,医共体用户管理 · 筛选与查询,姓名+医院+联系方式组合查询,高,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 医共体用户管理」权限;已进入医共体用户管理页(/spd/basic/user/platform/personCom,无 iframe);默认查询至少 1 条,"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 读取 `${sampleUserName}`、`${sampleHospital}`、`${samplePhone}`(电话非法占位如 `1` 时改用姓名+医院)
3. 填写后点击「查 询」
4. 断言结果同时满足已填条件",组合条件同时生效。,
USER-UNI-006,基础设置 · 用户管理,医共体用户管理 · 筛选与查询,姓名+医院+科室+角色+状态组合查询,高,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 医共体用户管理」权限;已进入医共体用户管理页(/spd/basic/user/platform/personCom,无 iframe);默认查询至少 1 条,"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 点击「查 询」后读取首行 `${sampleUserName}`、`${sampleLoginName}`、`${sampleHospital}`、`${sampleDept}`、`${sampleRole}`、`${sampleStatus}`(科室为 `--` 时换一行)
3. 填写姓名、所属医院、科室、角色,展开高级筛选选择 `${sampleStatus}` 后点「筛 选」(无则点「查 询」)
4. 断言结果非空;首行登录账号与采样行一致;可见行姓名/所属医院/科室/角色/状态与已填条件一致",组合查询无报错;结果与采样条件一致且同时满足全部已填条件。,
USER-UNI-007,基础设置 · 用户管理,医共体用户管理 · 筛选与查询,重置恢复默认医院,中,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 医共体用户管理」权限;已进入医共体用户管理页(/spd/basic/user/platform/personCom,无 iframe),"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 更改所属医院或姓名后点击「重 置」
3. 断言所属医院恢复进入页时的默认值,列表刷新",医院等条件恢复默认。,
......@@ -55,3 +55,35 @@ USER-UNI-016,基础设置 · 用户管理,医共体用户管理 · 新增与编
2. 点击首行「编辑」
3. 断言弹窗回填与该行登录账号、姓名、所属医院一致
4. 点「取 消」",编辑回填正确;本条不保存。,
USER-UNI-017,基础设置 · 用户管理,医共体用户管理 · 筛选与查询,高级筛选按状态过滤,高,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 医共体用户管理」权限;已进入医共体用户管理页(/spd/basic/user/platform/personCom,无 iframe),"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 展开高级筛选,状态下拉选择「启用」或「禁用」
3. 点击「筛 选」
4. 断言列表仅保留对应状态用户(无匹配则空态且总数为 0)",按状态过滤成功;无报错。,
USER-UNI-018,基础设置 · 用户管理,医共体用户管理 · 列表与分页,切换20条每页页面正常且列表同步,中,"测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 医共体用户管理」权限;已进入医共体用户管理页(/spd/basic/user/platform/personCom,无 iframe);默认查询总条数大于 10 且切换前可见行数不等于 min(N,20),否则 skip","1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 点击「查 询」,记录分页「共 N 条」与当前页可见行数
3. 分页区切换每页条数为「20 条/页」
4. 断言分页显示 20 条/页;总条数 N 不变;当前页行数为 min(N,20) 且与切换前不同;所属医院筛选、表头可见无报错",切换 20 条/页成功;页面正常;列表可见行数随每页条数同步变化且总数不变。,
USER-UNI-019,基础设置 · 用户管理,医共体用户管理 · 列表与分页,打开列设置面板,中,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 医共体用户管理」权限;已进入医共体用户管理页(/spd/basic/user/platform/personCom,无 iframe);默认查询至少 1 条,"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 点击「查 询」等待列表稳定
3. 点击表格工具栏列设置(齿轮)图标
4. 断言弹出面板含「列展示」「字段设置」「重置」,关闭面板",列设置面板可打开;关键项可见;关闭后列表正常。,
USER-UNI-020,基础设置 · 用户管理,医共体用户管理 · 列表与分页,列设置取消勾选后列表列恢复,中,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 医共体用户管理」权限;已进入医共体用户管理页(/spd/basic/user/platform/personCom,无 iframe);默认查询至少 1 条;存在可 toggle 的非核心列,否则 skip,"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 点击「查 询」后打开列设置,取消勾选一列非核心字段并关闭
3. 断言该列表头隐藏;再次打开列设置勾选恢复并关闭
4. 断言该列表头重新可见",取消勾选后表头隐藏;再勾选后表头恢复;无报错遮罩。,
USER-UNI-021,基础设置 · 用户管理,医共体用户管理 · 授权,打开授权后取消不生效,高,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 医共体用户管理」权限;已进入医共体用户管理页(/spd/basic/user/platform/personCom,无 iframe);列表至少 1 条,"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 点击首行「授权」
3. 断言打开用户角色授权层,可见可勾选菜单/功能
4. 如可勾选则改一处勾选后点取消/关闭
5. 再次打开同一用户授权,断言勾选与操作前一致",授权层可打开;取消后勾选不落库。,
USER-UNI-022,基础设置 · 用户管理,医共体用户管理 · 授权,授权弹框账号与所选行一致,高,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 医共体用户管理」权限;已进入医共体用户管理页(/spd/basic/user/platform/personCom,无 iframe);列表至少 1 条且登录账号非空,"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 点击「查 询」,任选列表一条登录账号非空的行,记录 `${sampleLoginName}`、`${sampleUserName}`
3. 点击该行「授权」
4. 断言弹出用户角色授权弹框,弹框展示的账号信息与所选行登录账号、姓名一致
5. 关闭弹框",用户角色授权弹框打开;弹框账号与所选列表行一致。,
USER-UNI-023,基础设置 · 用户管理,医共体用户管理 · 授权,授权弹框添加移除角色行,中,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 医共体用户管理」权限;已进入医共体用户管理页(/spd/basic/user/platform/personCom,无 iframe);列表至少 1 条;授权弹框含「添加/移除」(非仅权限树模式),否则 skip,"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 点击「查 询」,任选列表一条数据行,点击该行「授权」
3. 断言弹出用户角色授权弹框
4. 记录弹框内可「移除」的角色行数,点击「添加」
5. 断言可移除行数 +1;点击新增行的「移除」
6. 断言可移除行数恢复为添加前;关闭弹框且不保存",添加后列表下方新增一行;移除后新增行被删除;未保存不落库。,
用例编号,功能模块,功能测试点,用例标题,优先级,前置条件,测试步骤,预期结果,实际结果
USER-USR-001,基础设置 · 用户管理,用户管理 · 页面进入,菜单进入用户管理页,高,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 用户管理」权限,"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 按菜单「基础设置 → 用户管理(子菜单)→ 用户管理(叶子)」进入,或 `openPath` 打开 `/spd/basic/user/platform/person`
3. 断言 URL 含 `/spd/basic/user/platform/person`
4. 断言筛选区、工具栏「新 增」「Excel导入」、列表列头与分页可见",进入用户管理页;不误入医共体用户管理或角色管理;无报错遮罩。,
USER-USR-002,基础设置 · 用户管理,用户管理 · 页面进入,未登录访问拦截,高,测试环境可访问;使用独立 context,storageState 为空(不加载 test_1 登录态),"1. 新建独立 BrowserContext,不加载 auth/user.json
2. 清除登录态后直接打开 `/spd/basic/user/platform/person`
3. 断言跳转登录或不展示用户业务列表",未登录不可见用户数据。,
USER-USR-003,基础设置 · 用户管理,用户管理 · 筛选与查询,默认查询列表有数据或空态明确,高,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 用户管理」权限;已进入用户管理页(/spd/basic/user/platform/person,无 iframe),"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 点击「查 询」并等待列表稳定
3. 断言出现数据行,或「暂无数据」且分页总数为 0",查询无报错;有数据时分页「共 N 条」N≥1;无数据时空态明确。,
USER-USR-004,基础设置 · 用户管理,用户管理 · 筛选与查询,姓名或登录账号关键字查询,高,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 用户管理」权限;已进入用户管理页(/spd/basic/user/platform/person,无 iframe);默认查询至少 1 条且姓名或登录账号非空,"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 从首行读取 `${sampleUserName}` 或 `${sampleLoginName}` 取关键字
3. 在「姓名」框(占位「姓名、登录账号」)填入关键字,点击「查 询」
4. 断言结果行姓名或登录账号匹配该关键字",查询结果与已填关键字匹配;无未捕获异常。,
USER-USR-005,基础设置 · 用户管理,用户管理 · 筛选与查询,科室下拉筛选,高,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 用户管理」权限;已进入用户管理页(/spd/basic/user/platform/person,无 iframe);列表存在所属科室非空行,否则 skip,"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 取首条所属科室非 `--` 的行,读取 `${sampleDept}`
3. 科室下拉选择该值,点击「查 询」
4. 断言结果行「所属科室」与所选一致,且不含未分配 `--` 行",所属科室列与筛选条件一致。,
USER-USR-006,基础设置 · 用户管理,用户管理 · 筛选与查询,姓名+科室+角色组合查询,高,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 用户管理」权限;已进入用户管理页(/spd/basic/user/platform/person,无 iframe);默认查询至少 1 条,"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 读取 `${sampleUserName}`、`${sampleDept}`、`${sampleRole}`(角色可从该行截断前列名)
3. 填写姓名、科室、角色后点击「查 询」
4. 断言结果同时满足已填条件",组合查询无报错;结果同时满足全部已填条件。,
USER-USR-007,基础设置 · 用户管理,用户管理 · 筛选与查询,重置恢复默认,中,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 用户管理」权限;已进入用户管理页(/spd/basic/user/platform/person,无 iframe),"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
USER-USR-001,基础设置 · 用户管理,用户管理 · 筛选与查询,姓名+科室+角色+状态组合查询,高,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 用户管理」权限;默认查询至少 1 条,"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 点击「查 询」后读取首行 `${sampleUserName}`、`${sampleLoginName}`、`${sampleDept}`、`${sampleRole}`、`${sampleStatus}`(角色取截断前列名;状态优先读列表「状态」列,否则按行内启用/禁用文案推断)
3. 填写姓名、科室、角色,展开高级筛选选择 `${sampleStatus}` 后点「筛 选」(无「筛 选」则点「查 询」)
4. 断言结果非空;首行登录账号与采样行一致;可见行姓名/科室/角色/状态与已填条件一致",组合查询无报错;查询结果与首行采样条件一致且同时满足全部已填条件。,
USER-USR-002,基础设置 · 用户管理,用户管理 · 筛选与查询,重置恢复默认,中,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 用户管理」权限;已进入用户管理页(/spd/basic/user/platform/person,无 iframe),"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 填写姓名等筛选条件后点击「重 置」
3. 断言筛选字段恢复默认,列表按默认规则刷新",条件恢复默认;列表刷新。,
USER-USR-008,基础设置 · 用户管理,用户管理 · 筛选与查询,展开高级筛选,中,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 用户管理」权限;已进入用户管理页(/spd/basic/user/platform/person,无 iframe),"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
USER-USR-003,基础设置 · 用户管理,用户管理 · 筛选与查询,展开高级筛选,中,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 用户管理」权限;已进入用户管理页(/spd/basic/user/platform/person,无 iframe),"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 点击「高级筛选」
3. 断言展开后面板可见姓名/科室/角色/状态及「重 置」「筛 选」",高级筛选面板展开;关键字段与筛选按钮可见。,
USER-USR-009,基础设置 · 用户管理,用户管理 · 筛选与查询,高级筛选按状态过滤,高,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 用户管理」权限;已进入用户管理页(/spd/basic/user/platform/person,无 iframe),"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
USER-USR-004,基础设置 · 用户管理,用户管理 · 筛选与查询,高级筛选按状态过滤,高,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 用户管理」权限;已进入用户管理页(/spd/basic/user/platform/person,无 iframe),"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 展开高级筛选,状态下拉选择「启用」或「禁用」
3. 点击「筛 选」
4. 断言列表仅保留对应状态用户(无匹配则空态且总数为 0)",按状态过滤成功;无报错。,
USER-USR-010,基础设置 · 用户管理,用户管理 · 筛选与查询,无匹配结果空态(mock反例),中,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 用户管理」权限;已进入用户管理页(/spd/basic/user/platform/person,无 iframe),"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
USER-USR-005,基础设置 · 用户管理,用户管理 · 筛选与查询,无匹配结果空态(mock反例),中,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 用户管理」权限;已进入用户管理页(/spd/basic/user/platform/person,无 iframe),"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 姓名框填入 `__NOT_EXIST_USER__99999`
3. 点击「查 询」
4. 断言空态或「暂无数据」,分页总数为 0",无匹配时不报错;空态明确;总数为 0。,
USER-USR-011,基础设置 · 用户管理,用户管理 · 列表与分页,列表核心列完整,高,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 用户管理」权限;已进入用户管理页(/spd/basic/user/platform/person,无 iframe),"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 断言表头含序号、姓名、登录账号、联系方式、所属科室、医院员工编号、角色、科室任职数、操作
3. 断言操作列可见「编辑」「授权」「重置密码」",核心列完整;行内三个操入口可见。,
USER-USR-012,基础设置 · 用户管理,用户管理 · 列表与分页,列表不含所属医院列,中,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 用户管理」权限;已进入用户管理页(/spd/basic/user/platform/person,无 iframe),"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 断言表头不含「所属医院」",本页无所属医院列,与医共体用户管理页区分。,
USER-USR-013,基础设置 · 用户管理,用户管理 · 列表与分页,分页翻至第 2 页,中,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 用户管理」权限;已进入用户管理页(/spd/basic/user/platform/person,无 iframe);默认查询总条数大于一页,否则 skip,"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
USER-USR-006,基础设置 · 用户管理,用户管理 · 列表与分页,分页翻至第 2 页,中,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 用户管理」权限;已进入用户管理页(/spd/basic/user/platform/person,无 iframe);默认查询总条数大于一页,否则 skip,"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 记录第 1 页首行登录账号
3. 点击页码「2」或「下一页」
4. 断言页码高亮为 2,首行账号与第 1 页不同",翻页成功;数据更换。,
USER-USR-014,基础设置 · 用户管理,用户管理 · 新增用户,打开新增用户弹窗,高,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 用户管理」权限;已进入用户管理页(/spd/basic/user/platform/person,无 iframe),"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
USER-USR-007,基础设置 · 用户管理,用户管理 · 新增用户,打开新增用户弹窗,高,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 用户管理」权限;已进入用户管理页(/spd/basic/user/platform/person,无 iframe),"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 点击「新 增」
3. 断言弹出用户表单,可见账号、姓名、联系方式、医院员工编号、启用/禁用、「取 消」「确 定」",新增弹窗可见;关键字段与按钮齐全。,
USER-USR-015,基础设置 · 用户管理,用户管理 · 新增用户,新增取消不落库,中,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 用户管理」权限;已进入用户管理页(/spd/basic/user/platform/person,无 iframe),"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
USER-USR-008,基础设置 · 用户管理,用户管理 · 新增用户,新增取消不落库,中,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 用户管理」权限;已进入用户管理页(/spd/basic/user/platform/person,无 iframe),"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 打开新增,账号填 `${uniqLogin}`,姓名填任意合法值
3. 点击「取 消」
4. 按账号查询,断言无该记录",弹窗关闭;列表不出现 `${uniqLogin}`。,
USER-USR-016,基础设置 · 用户管理,用户管理 · 新增用户,必填为空拦截,高,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 用户管理」权限;已进入用户管理页(/spd/basic/user/platform/person,无 iframe),"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
USER-USR-009,基础设置 · 用户管理,用户管理 · 新增用户,必填为空拦截,高,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 用户管理」权限;已进入用户管理页(/spd/basic/user/platform/person,无 iframe),"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 打开新增,账号或姓名留空后点击「确 定」
3. 断言出现必填提示,弹窗仍打开",确定被拦截;不落库;弹窗不关闭。,
USER-USR-017,基础设置 · 用户管理,用户管理 · 新增用户,登录账号重复拦截,高,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 用户管理」权限;已进入用户管理页(/spd/basic/user/platform/person,无 iframe);默认查询至少 1 条且登录账号非空,"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
USER-USR-010,基础设置 · 用户管理,用户管理 · 新增用户,登录账号重复拦截,高,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 用户管理」权限;已进入用户管理页(/spd/basic/user/platform/person,无 iframe);默认查询至少 1 条且登录账号非空,"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 打开新增,账号填入已存在的 `${sampleLoginName}`,补齐其它必填后点「确 定」
3. 断言保存失败并提示,弹窗仍打开
4. 查询该账号,断言原用户未被覆盖",重复账号被拒绝;不覆盖原用户。,
USER-USR-018,基础设置 · 用户管理,用户管理 · 编辑用户,打开编辑用户弹窗并回填,高,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 用户管理」权限;已进入用户管理页(/spd/basic/user/platform/person,无 iframe);列表至少 1 条,"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
USER-USR-011,基础设置 · 用户管理,用户管理 · 编辑用户,打开编辑用户弹窗并回填,高,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 用户管理」权限;已进入用户管理页(/spd/basic/user/platform/person,无 iframe);列表至少 1 条,"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 点击首行「编辑」
3. 断言弹窗标题含「编辑用户」
4. 断言账号、姓名与该行 `${sampleLoginName}`、`${sampleUserName}` 一致(允许规范化空白)",编辑弹窗可见;回填与列表行一致。,
USER-USR-019,基础设置 · 用户管理,用户管理 · 编辑用户,编辑取消不保存,中,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 用户管理」权限;已进入用户管理页(/spd/basic/user/platform/person,无 iframe);列表至少 1 条,"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
USER-USR-012,基础设置 · 用户管理,用户管理 · 编辑用户,编辑取消不保存,中,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 用户管理」权限;已进入用户管理页(/spd/basic/user/platform/person,无 iframe);列表至少 1 条,"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 打开首行编辑,记录原姓名
3. 将姓名改为临时值后点「取 消」
4. 再次打开编辑,断言姓名仍为原值",取消后列表与回看均为修改前姓名。,
USER-USR-020,基础设置 · 用户管理,用户管理 · 编辑用户,编辑时账号只读,中,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 用户管理」权限;已进入用户管理页(/spd/basic/user/platform/person,无 iframe);列表至少 1 条,"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
USER-USR-013,基础设置 · 用户管理,用户管理 · 编辑用户,编辑时账号只读,中,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 用户管理」权限;已进入用户管理页(/spd/basic/user/platform/person,无 iframe);列表至少 1 条,"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 打开首行编辑
3. 断言登录账号不可改(disabled/readonly 或无法输入)",编辑态登录账号只读。,
USER-USR-021,基础设置 · 用户管理,用户管理 · 编辑用户,编辑弹窗可见签名图片,中,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 用户管理」权限;已进入用户管理页(/spd/basic/user/platform/person,无 iframe);列表至少 1 条,"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
USER-USR-014,基础设置 · 用户管理,用户管理 · 编辑用户,编辑弹窗可见签名图片,中,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 用户管理」权限;已进入用户管理页(/spd/basic/user/platform/person,无 iframe);列表至少 1 条,"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 打开首行编辑
3. 断言可见「签名图片」区域(有图则展示,无图则上传入口可见)
4. 点「取 消」,不保存",签名图片控件可见;本条不上传、不保存。,
USER-USR-022,基础设置 · 用户管理,用户管理 · 授权,打开授权后取消不生效,高,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 用户管理」权限;已进入用户管理页(/spd/basic/user/platform/person,无 iframe);列表至少 1 条,"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
USER-USR-015,基础设置 · 用户管理,用户管理 · 授权,打开授权后取消不生效,高,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 用户管理」权限;已进入用户管理页(/spd/basic/user/platform/person,无 iframe);列表至少 1 条,"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 点击首行「授权」
3. 断言打开授权层(弹窗或权限树),可见可勾选菜单/功能
4. 如可勾选则改一处勾选后点取消/关闭
5. 再次打开同一用户授权,断言勾选与操作前一致",授权层可打开;取消后勾选不落库。,
USER-USR-023,基础设置 · 用户管理,用户管理 · 重置密码,行内重置密码确认后取消,高,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 用户管理」权限;已进入用户管理页(/spd/basic/user/platform/person,无 iframe);列表至少 1 条,"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 点击首行「重置密码」
3. 断言出现确认框或密码表单,含取消/关闭
4. 点取消/关闭
5. 断言仍停留本页且该行仍在",重置需确认;取消后不改密。本条不点确定,避免污染账号。,
USER-USR-016,基础设置 · 用户管理,用户管理 · 列表与分页,切换20条每页页面正常且列表同步,中,"测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 用户管理」权限;已进入用户管理页(/spd/basic/user/platform/person,无 iframe);默认查询总条数大于 10 且切换前可见行数不等于 min(N,20),否则 skip","1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 点击「查 询」,记录分页「共 N 条」与当前页可见行数
3. 分页区切换每页条数为「20 条/页」
4. 断言分页显示 20 条/页;总条数 N 不变;当前页行数为 min(N,20) 且与切换前不同;筛选区、表头可见无报错",切换 20 条/页成功;页面正常;列表可见行数随每页条数同步变化且总数不变。,
USER-USR-017,基础设置 · 用户管理,用户管理 · 列表与分页,打开列设置面板,中,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 用户管理」权限;已进入用户管理页(/spd/basic/user/platform/person,无 iframe);默认查询至少 1 条,"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 点击「查 询」等待列表稳定
3. 点击表格工具栏列设置(齿轮)图标
4. 断言弹出面板含「列展示」「字段设置」「重置」,关闭面板",列设置面板可打开;关键项可见;关闭后列表正常。,
USER-USR-018,基础设置 · 用户管理,用户管理 · 列表与分页,列设置取消勾选后列表列恢复,中,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 用户管理」权限;已进入用户管理页(/spd/basic/user/platform/person,无 iframe);默认查询至少 1 条;存在可 toggle 的非核心列,否则 skip,"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 点击「查 询」后打开列设置,取消勾选一列非核心字段并关闭
3. 断言该列表头隐藏;再次打开列设置勾选恢复并关闭
4. 断言该列表头重新可见",取消勾选后表头隐藏;再勾选后表头恢复;无报错遮罩。,
USER-USR-019,基础设置 · 用户管理,用户管理 · 授权,授权弹框账号与所选行一致,高,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 用户管理」权限;已进入用户管理页(/spd/basic/user/platform/person,无 iframe);列表至少 1 条且登录账号非空,"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 点击「查 询」,任选列表一条登录账号非空的行,记录 `${sampleLoginName}`、`${sampleUserName}`
3. 点击该行「授权」
4. 断言弹出用户角色授权弹框,弹框展示的账号信息与所选行登录账号、姓名一致
5. 关闭弹框",用户角色授权弹框打开;弹框账号与所选列表行一致。,
USER-USR-020,基础设置 · 用户管理,用户管理 · 授权,授权弹框添加移除角色行,中,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 用户管理」权限;已进入用户管理页(/spd/basic/user/platform/person,无 iframe);列表至少 1 条;授权弹框含「添加/移除」(非仅权限树模式),否则 skip,"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 点击「查 询」,任选列表一条数据行,点击该行「授权」
3. 断言弹出用户角色授权弹框
4. 记录弹框内可「移除」的角色行数,点击「添加」
5. 断言可移除行数 +1;点击新增行的「移除」
6. 断言可移除行数恢复为添加前;关闭弹框且不保存",添加后列表下方新增一行;移除后新增行被删除;未保存不落库。,
用例编号,功能模块,功能测试点,用例标题,优先级,前置条件,测试步骤,预期结果,实际结果
USER-ROL-001,基础设置 · 用户管理,角色管理 · 页面进入,菜单进入角色管理页,高,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 角色管理」权限,"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 按菜单「基础设置 → 用户管理 → 角色管理」进入,或 `openPath` 打开 `/spd/basic/user/platform/role`
3. 断言 URL 含 `/spd/basic/user/platform/role`
4. 断言左侧角色列表、右侧菜单权限树、「保 存」「刷 新」可见,无独立分页",进入角色管理;左右分栏渲染完成;不误入用户管理页。,
USER-ROL-002,基础设置 · 用户管理,角色管理 · 页面进入,未登录访问拦截,高,测试环境可访问;使用独立 context,storageState 为空(不加载 test_1 登录态),"1. 新建独立 BrowserContext,不加载 auth/user.json
2. 清除登录态后直接打开 `/spd/basic/user/platform/role`
3. 断言跳转登录或不展示角色/权限树业务数据",未登录不可见角色配置。,
USER-ROL-003,基础设置 · 用户管理,角色管理 · 左侧角色列表,角色列表加载并可点选,高,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 角色管理」权限;已进入角色管理页(/spd/basic/user/platform/role,无 iframe),"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 断言左栏列出角色名称(如医院管理员、护士等)
3. 点击首个 `${sampleRoleName}`
4. 断言该行处于选中态",左栏有角色且可点选。,
USER-ROL-004,基础设置 · 用户管理,角色管理 · 左侧角色列表,角色名称搜索过滤,高,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 角色管理」权限;已进入角色管理页(/spd/basic/user/platform/role,无 iframe);左栏至少 1 个角色,"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 在角色名称框输入 `${sampleRoleName}` 的部分关键字
3. 断言左栏收敛为匹配项(即时或回车,以现网为准)",搜索结果角色名包含关键字。,
USER-ROL-005,基础设置 · 用户管理,角色管理 · 左侧角色列表,选中角色刷新右侧权限树,高,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 角色管理」权限;已进入角色管理页(/spd/basic/user/platform/role,无 iframe);左栏至少 2 个角色,"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 依次点选两个不同角色
3. 断言右侧勾选随当前角色变化,无上一角色残留",权限树与当前角色配置一致。,
USER-ROL-006,基础设置 · 用户管理,角色管理 · 左侧角色列表,清空搜索恢复全量,中,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 角色管理」权限;已进入角色管理页(/spd/basic/user/platform/role,无 iframe),"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 先按关键字过滤,再清空搜索
3. 断言左栏恢复未过滤的完整角色列表",清空后角色列表恢复全量。,
USER-ROL-007,基础设置 · 用户管理,角色管理 · 左侧角色列表,搜索无匹配空态(mock反例),中,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 角色管理」权限;已进入角色管理页(/spd/basic/user/platform/role,无 iframe),"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 角色名称框填入 `__NOT_EXIST_ROLE__99999`
3. 断言左栏空态明确",无匹配不报错;空态明确。,
USER-ROL-008,基础设置 · 用户管理,角色管理 · 右侧菜单权限树,权限树按应用与模块展开,高,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 角色管理」权限;已进入角色管理页(/spd/basic/user/platform/role,无 iframe),"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
USER-ROL-001,基础设置 · 用户管理,角色管理 · 右侧菜单权限树,权限树按应用与模块展开,高,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 角色管理」权限,"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 选中任一角色
3. 断言右侧树根为「SPD内网系统PC端」,可见基础设置/运维管理/厂商管理/产品管理等模块
4. 展开「基础设置」,断言可见子菜单(如医院资料)",权限树层级可见;节点可展开。,
USER-ROL-009,基础设置 · 用户管理,角色管理 · 右侧菜单权限树,勾选父节点联动子节点,高,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 角色管理」权限;已进入角色管理页(/spd/basic/user/platform/role,无 iframe),"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 选中一自定义或可编辑角色(预置被锁则 skip)
3. 记录「厂商管理」父节点原勾选
4. 勾选「厂商管理」父节点
5. 断言其子菜单/功能全部勾选
6. 点击「刷 新」,不点「保 存」",父节点勾选联动子节点;本条不保存。,
USER-ROL-010,基础设置 · 用户管理,角色管理 · 右侧菜单权限树,刷新恢复未保存勾选,中,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 角色管理」权限;已进入角色管理页(/spd/basic/user/platform/role,无 iframe),"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 改一处勾选后点「刷 新」且未保存
3. 断言树恢复为进入该角色时的已保存状态",刷新丢弃未保存勾选。,
USER-ROL-011,基础设置 · 用户管理,角色管理 · 角色增删改,删除需确认且取消保留,高,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 角色管理」权限;已进入角色管理页(/spd/basic/user/platform/role,无 iframe),"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 点一自定义角色的删除入口(预置角色无删除则改点自定义;都没有则 skip)
3. 断言出现确认框,含取消/确定
4. 点「取 消」
5. 断言该角色仍在左栏",删除需二次确认;取消后角色仍在。本条不点确定。,
USER-ROL-012,基础设置 · 用户管理,角色管理 · 角色增删改,新增角色入口打开后取消,中,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 角色管理」权限;已进入角色管理页(/spd/basic/user/platform/role,无 iframe),"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 通过新增或「角色」工具按钮打开新增表单(扫描未打开则 skip)
3. 不填或填写后点取消/关闭
4. 断言左栏角色数量不增加",新增入口可达;取消不落库。,
USER-ROL-013,基础设置 · 用户管理,角色管理 · 角色增删改,预置角色不可删除,中,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 角色管理」权限;已进入角色管理页(/spd/basic/user/platform/role,无 iframe);左栏存在可识别的预置角色,否则 skip,"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 在左栏定位「医院管理员」(或其它预置角色,以现网为准)
3. 断言删除入口不可见;若可见则确定后被拒绝,角色仍在左栏
4. 若弹出确认则点取消,避免误删",预置角色不可删除。,
USER-ROL-014,基础设置 · 用户管理,角色管理 · 左侧角色列表,角色名与用户管理角色下拉同源,中,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 角色管理」权限;已进入角色管理页(/spd/basic/user/platform/role,无 iframe),"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 记录本页左栏角色名称集合
3. 打开用户管理,展开「角色」筛选项
4. 断言两边名称集合一致(允许按权限裁剪,本页有的名称用户页应能对应到)",角色主数据跨页一致,不串名。,
USER-ROL-002,基础设置 · 用户管理,角色管理 · 右侧菜单权限树,权限树模块节点收起正常,高,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 角色管理」权限;已进入角色管理页(/spd/basic/user/platform/role,无 iframe),"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 选中任一角色
3. 展开「基础设置」,断言可见子菜单(如医院资料)
4. 点击「基础设置」节点展开图标收起该模块
5. 断言子菜单(如医院资料)不可见,且「基础设置」节点处于收起态",权限树节点可收起;子级随父节点隐藏。,
USER-ROL-003,基础设置 · 用户管理,角色管理 · 左侧角色列表,切换角色右侧权限树同步更新,高,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 角色管理」权限;已进入角色管理页(/spd/basic/user/platform/role,无 iframe);左栏至少 2 个角色,"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 点选左栏第一个角色,记录右侧权限树勾选快照
3. 点选左栏第二个不同角色,断言该行选中且右侧权限树仍可见
4. 再次点选第一个角色,断言右侧权限树勾选快照与步骤 2 一致",切换后右侧与当前角色一致;切回后恢复该角色已保存勾选,无错乱残留。,
USER-ROL-004,基础设置 · 用户管理,角色管理 · 角色增删改,编辑角色弹框回填名称一致,高,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 角色管理」权限;已进入角色管理页(/spd/basic/user/platform/role,无 iframe);左栏至少 1 个角色,"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 点选左栏任一角色,记录其角色名称
3. 点击左栏右下角(或行内)编辑图标
4. 断言弹出编辑角色弹框,标题或内容含「编辑」
5. 断言弹框内「角色名称」输入框回填值与所选角色名称一致
6. 点「取 消」或「关 闭」关闭弹框,不保存",编辑弹框正常打开;角色名称与当前选中角色一致;本条不保存。,
USER-ROL-005,基础设置 · 用户管理,角色管理 · 角色增删改,编辑角色保存成功,高,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 角色管理」权限;已进入角色管理页(/spd/basic/user/platform/role,无 iframe);左栏至少 1 个可编辑角色;环境允许写操作(非 SKIP_BASIC_WRITE=1),"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 点选左栏任一角色
3. 点击左栏右下角(或行内)编辑图标,断言弹出编辑角色弹框
4. 不修改角色名称,点击弹框内「保 存」
5. 断言页面无报错提示,且出现「编辑成功」类成功提示
6. 断言编辑弹框关闭",保存成功;提示编辑成功;角色数据无异常报错。,
USER-ROL-006,基础设置 · 用户管理,角色管理 · 角色增删改,点击角色按钮打开新增弹框,高,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 角色管理」权限;已进入角色管理页(/spd/basic/user/platform/role,无 iframe),"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 点击左栏工具区「角 色」按钮
3. 断言弹出新增角色弹框,标题或内容含「新增」或「角色」
4. 断言页面无报错提示(无错误 toast/遮罩)
5. 点「取 消」或「关 闭」关闭弹框,不保存",新增角色弹框正常打开;页面无报错;本条不提交保存。,
USER-ROL-007,基础设置 · 用户管理,角色管理 · 角色增删改,新增角色名称为空保存拦截,高,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 角色管理」权限;已进入角色管理页(/spd/basic/user/platform/role,无 iframe),"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 点击左栏工具区「角 色」按钮,断言弹出新增角色弹框
3. 不填写角色名称,直接点击弹框内「保 存」
4. 断言「角色名称」输入框呈校验失败态(变红)
5. 断言同字段下方提示:请输入角色名称!
6. 点「取 消」关闭弹框,不保存",空名称保存被拦截;角色名称框变红并提示请输入角色名称!;弹框不提交。,
USER-ROL-008,基础设置 · 用户管理,角色管理 · 角色增删改,新增角色弹框取消关闭,高,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 角色管理」权限;已进入角色管理页(/spd/basic/user/platform/role,无 iframe),"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 点击左栏工具区「角 色」按钮,断言弹出新增角色弹框
3. 不填写任何内容,直接点击弹框内「取 消」
4. 断言新增角色弹框关闭
5. 断言页面无报错提示,左栏角色数量不增加",取消后弹框关闭;页面无报错;未新增角色。,
USER-ROL-009,基础设置 · 用户管理,角色管理 · 角色增删改,新增弹框查询配置化复选框可勾选,高,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 角色管理」权限;已进入角色管理页(/spd/basic/user/platform/role,无 iframe);新增弹框含「查询配置化」复选框,"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 点击左栏工具区「角 色」按钮,断言弹出新增角色弹框
3. 定位「查询配置化」复选框,点击勾选,断言为选中态
4. 再次点击取消勾选,断言为未选中态
5. 断言页面无报错提示
6. 点「取 消」关闭弹框,不保存",查询配置化可正常勾选与取消勾选;页面无报错;本条不提交保存。,
USER-ROL-010,基础设置 · 用户管理,角色管理 · 角色增删改,新增角色弹框X关闭,高,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 角色管理」权限;已进入角色管理页(/spd/basic/user/platform/role,无 iframe),"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 点击左栏工具区「角 色」按钮,断言弹出新增角色弹框
3. 点击弹框右上角「X」关闭图标
4. 断言新增角色弹框关闭
5. 断言页面无报错提示,左栏角色数量不增加",X 关闭后弹框正常关闭;页面无报错;未新增角色。,
USER-ROL-011,基础设置 · 用户管理,角色管理 · 左侧角色列表,左栏角色列表可上下滑动,中,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 角色管理」权限;已进入角色管理页(/spd/basic/user/platform/role,无 iframe);左栏列表高度溢出可滚动(不足则 skip),"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 定位左栏角色列表滚动区域
3. 向下滚动列表,断言 scrollTop 增加且列表可继续交互
4. 向上滚回(滚轮或滑至顶部),断言 scrollTop 减小
5. 断言页面无报错遮罩",左栏可正常上下滑动;滚动后页面无异常。,
USER-ROL-012,基础设置 · 用户管理,角色管理 · 右侧菜单权限树,右侧权限树子菜单可上下滑动,中,测试环境可访问;正例账号 test_1 已通过 auth.setup 写入 storageState 登录(密码仅存 .env TEST_PASS,勿写入本表);具备「基础设置 → 用户管理 → 角色管理」权限;已进入角色管理页(/spd/basic/user/platform/role,无 iframe);已选中任一角色且右侧权限树高度溢出可滚动(不足则 skip),"1. 确认 chromium 已加载 storageState(auth.setup,正例账号 test_1),无需在用例内重复登录
2. 点选左栏任一角色,断言右侧菜单权限树可见
3. 定位右侧权限树滚动区域(含子菜单列表)
4. 向下滚动,断言 scrollTop 增加
5. 向上滚回,断言 scrollTop 减小
6. 断言页面无报错遮罩",右侧子菜单列表可正常上下滑动;滚动后页面无异常。,
......@@ -7,7 +7,7 @@
**元素扫描**:`data/scan/basic-user-union-elements.json`(221 项)
**关联规范**:`knowledge-base/08-UI专项规范库/UI-SPEC-SPD-USER-002.md`
**版本**:2026-10-20
**自动化框架**:Playwright + TypeScript(规划 `tests/spd/basic-user-union.spec.ts`,依赖 `auth.setup` 登录态)
**自动化框架**:Playwright + TypeScript(`tests/spd/basic-user-union.spec.ts`,依赖 `auth.setup` 登录态)
**通用前置**:正例账号 `test_1` 经 `auth.setup` 写入 `storageState`(密码仅存 `.env` 的 `TEST_PASS`);具备「基础设置 → 用户管理 → 医共体用户管理」权限。反例筛选用 mock(`__NOT_EXIST_USER__99999` / `__NOT_EXIST_ROLE__99999`)。
**页面约定**:整页渲染,**无报表 iframe**;定位均在 `page` 上。
......@@ -30,7 +30,7 @@
1. 按菜单「基础设置 → 用户管理 → 医共体用户管理」进入,或 `openPath` 打开 `/spd/basic/user/platform/personCom`
2. 断言 URL 含 `/spd/basic/user/platform/personCom`
3. 断言所属医院筛选、工具栏、列表列头与分页可见
- 自动化断言:规划与 CSV `USER-UNI-001` 及 `tests/spd/basic-user-union.spec.ts` 一致
- 自动化断言:与 CSV `USER-UNI-001` 及 `tests/spd/basic-user-union.spec.ts` 一致
###### 预期结果
......@@ -46,7 +46,7 @@
- 操作步骤:
1. 清除登录态后直接打开 `/spd/basic/user/platform/personCom`
2. 断言跳转登录或不展示用户业务列表
- 自动化断言:规划与 CSV `USER-UNI-002` 及 `tests/spd/basic-user-union.spec.ts` 一致
- 自动化断言:与 CSV `USER-UNI-002` 及 `tests/spd/basic-user-union.spec.ts` 一致
###### 预期结果
......@@ -64,7 +64,7 @@
- 操作步骤:
1. 点击「查 询」并等待列表稳定
2. 断言出现数据行,或空态且分页总数为 0
- 自动化断言:规划与 CSV `USER-UNI-003` 及 `tests/spd/basic-user-union.spec.ts` 一致
- 自动化断言:与 CSV `USER-UNI-003` 及 `tests/spd/basic-user-union.spec.ts` 一致
###### 预期结果
......@@ -79,7 +79,7 @@
1. 从首行读取 `${sampleUserName}` 或 `${sampleLoginName}`
2. 在「姓名」框填入关键字,点击「查 询」
3. 断言结果行姓名或登录账号匹配
- 自动化断言:规划与 CSV `USER-UNI-004` 及 `tests/spd/basic-user-union.spec.ts` 一致
- 自动化断言:与 CSV `USER-UNI-004` 及 `tests/spd/basic-user-union.spec.ts` 一致
###### 预期结果
......@@ -95,7 +95,7 @@
2. 切换「所属医院」为另一可选项(仅 1 家则 skip)
3. 点击「查 询」
4. 断言结果行「所属医院」均为所选值,「共 N 条」可与默认不同
- 自动化断言:规划与 CSV `USER-UNI-005` 及 `tests/spd/basic-user-union.spec.ts` 一致
- 自动化断言:与 CSV `USER-UNI-005` 及 `tests/spd/basic-user-union.spec.ts` 一致
###### 预期结果
......@@ -103,18 +103,18 @@
---
##### 【P0】【TC-UI-USER-UNI-006】【TP_USER_UNI_013】姓名+医院+联系方式组合查询
##### 【P0】【TC-UI-USER-UNI-006】【TP_USER_UNI_013】姓名+医院+科室+角色+状态组合查询
- 前置条件:默认查询至少 1 条
- 操作步骤:
1. 读取 `${sampleUserName}`、`${sampleHospital}`、`${samplePhone}`(电话非法占位如 `1` 时改用姓名+医院)
2. 填写后点击「查 询」
3. 断言结果同时满足已填条件
- 自动化断言:规划与 CSV `USER-UNI-006` 及 `tests/spd/basic-user-union.spec.ts` 一致
1. 点击「查 询」后读取首行 `${sampleUserName}`、`${sampleLoginName}`、`${sampleHospital}`、`${sampleDept}`、`${sampleRole}`、`${sampleStatus}`(科室为 `--` 时换一行)
2. 填写姓名、所属医院、科室、角色,展开高级筛选选择 `${sampleStatus}` 后点「筛 选」(无则点「查 询」)
3. 断言结果非空;首行登录账号与采样行一致;可见行姓名/所属医院/科室/角色/状态与已填条件一致
- 自动化断言:与 CSV `USER-UNI-006` 及 `tests/spd/basic-user-union.spec.ts` 一致
###### 预期结果
组合条件同时生效。
组合查询无报错;结果与采样条件一致且同时满足全部已填条件。
---
......@@ -124,7 +124,7 @@
- 操作步骤:
1. 更改所属医院或姓名后点击「重 置」
2. 断言所属医院恢复进入页时的默认值,列表刷新
- 自动化断言:规划与 CSV `USER-UNI-007` 及 `tests/spd/basic-user-union.spec.ts` 一致
- 自动化断言:与 CSV `USER-UNI-007` 及 `tests/spd/basic-user-union.spec.ts` 一致
###### 预期结果
......@@ -138,7 +138,7 @@
- 操作步骤:
1. 点击「高级筛选」
2. 断言可见所属医院、联系方式、科室、角色及「筛 选」
- 自动化断言:规划与 CSV `USER-UNI-008` 及 `tests/spd/basic-user-union.spec.ts` 一致
- 自动化断言:与 CSV `USER-UNI-008` 及 `tests/spd/basic-user-union.spec.ts` 一致
###### 预期结果
......@@ -154,7 +154,7 @@
- 操作步骤:
1. 姓名框填入 `__NOT_EXIST_USER__99999`,点击「查 询」
2. 断言空态且分页总数为 0
- 自动化断言:规划与 CSV `USER-UNI-009` 及 `tests/spd/basic-user-union.spec.ts` 一致
- 自动化断言:与 CSV `USER-UNI-009` 及 `tests/spd/basic-user-union.spec.ts` 一致
###### 预期结果
......@@ -172,7 +172,7 @@
- 操作步骤:
1. 断言列顺序为登录账号、姓名、联系方式、所属医院、所属科室、医院员工编号、角色、科室任职数、操作
2. 勿用用户管理页列断言(用户管理无所属医院且姓名在登录账号前)
- 自动化断言:规划与 CSV `USER-UNI-010` 及 `tests/spd/basic-user-union.spec.ts` 一致
- 自动化断言:与 CSV `USER-UNI-010` 及 `tests/spd/basic-user-union.spec.ts` 一致
###### 预期结果
......@@ -187,7 +187,7 @@
1. 记录第 1 页首行登录账号
2. 翻到第 2 页
3. 断言数据更换且所属医院筛选仍保持
- 自动化断言:规划与 CSV `USER-UNI-011` 及 `tests/spd/basic-user-union.spec.ts` 一致
- 自动化断言:与 CSV `USER-UNI-011` 及 `tests/spd/basic-user-union.spec.ts` 一致
###### 预期结果
......@@ -204,7 +204,7 @@
1. 记录本页首行 `${sampleLoginName}`、姓名、所属科室、角色
2. 打开用户管理页,按登录账号查询同一用户
3. 断言姓名、角色、所属科室一致;本页仅多「所属医院」
- 自动化断言:规划与 CSV `USER-UNI-012` 及 `tests/spd/basic-user-union.spec.ts` 一致
- 自动化断言:与 CSV `USER-UNI-012` 及 `tests/spd/basic-user-union.spec.ts` 一致
###### 预期结果
......@@ -222,7 +222,7 @@
- 操作步骤:
1. 点击「新 增」
2. 断言弹出用户表单,含账号、姓名、联系方式及取消/确定
- 自动化断言:规划与 CSV `USER-UNI-013` 及 `tests/spd/basic-user-union.spec.ts` 一致
- 自动化断言:与 CSV `USER-UNI-013` 及 `tests/spd/basic-user-union.spec.ts` 一致
###### 预期结果
......@@ -236,7 +236,7 @@
- 操作步骤:
1. 打开新增,账号填 `${uniqLogin}` 后点「取 消」
2. 当前医院下列表查询该账号,断言不存在
- 自动化断言:规划与 CSV `USER-UNI-014` 及 `tests/spd/basic-user-union.spec.ts` 一致
- 自动化断言:与 CSV `USER-UNI-014` 及 `tests/spd/basic-user-union.spec.ts` 一致
###### 预期结果
......@@ -252,7 +252,7 @@
- 操作步骤:
1. 打开新增,账号或姓名留空后点「确 定」
2. 断言被拦截,弹窗仍打开,不落库
- 自动化断言:规划与 CSV `USER-UNI-015` 及 `tests/spd/basic-user-union.spec.ts` 一致
- 自动化断言:与 CSV `USER-UNI-015` 及 `tests/spd/basic-user-union.spec.ts` 一致
###### 预期结果
......@@ -269,18 +269,139 @@
1. 点击首行「编辑」
2. 断言弹窗回填与该行登录账号、姓名、所属医院一致
3. 点「取 消」
- 自动化断言:规划与 CSV `USER-UNI-016` 及 `tests/spd/basic-user-union.spec.ts` 一致
- 自动化断言:与 CSV `USER-UNI-016` 及 `tests/spd/basic-user-union.spec.ts` 一致
###### 预期结果
编辑回填正确;本条不保存。
---
### 医共体用户管理 · 筛选与查询
#### 功能验证点
##### 【P0】【TC-UI-USER-UNI-017】【TP_USER_UNI_042】高级筛选按状态过滤
- 前置条件:已进入医共体用户管理页
- 操作步骤:
1. 展开高级筛选,状态下拉选择「启用」或「禁用」
2. 点击「筛 选」
3. 断言列表仅保留对应状态用户(无匹配则空态且总数为 0)
- 自动化断言:与 CSV `USER-UNI-017` 及 `tests/spd/basic-user-union.spec.ts` 一致
###### 预期结果
按状态过滤成功;无报错。
---
### 医共体用户管理 · 列表与分页
#### 功能验证点
##### 【P1】【TC-UI-USER-UNI-018】【TP_USER_UNI_027】切换20条每页页面正常且列表同步
- 前置条件:默认查询总条数大于 10 且切换前可见行数不等于 min(N,20),否则 skip
- 操作步骤:
1. 点击「查 询」,记录分页「共 N 条」与当前页可见行数
2. 分页区切换每页条数为「20 条/页」
3. 断言分页显示 20 条/页;总条数 N 不变;当前页行数为 min(N,20) 且与切换前不同;所属医院筛选、表头可见无报错
- 自动化断言:与 CSV `USER-UNI-018` 及 `tests/spd/basic-user-union.spec.ts` 一致
###### 预期结果
切换 20 条/页成功;页面正常;列表可见行数随每页条数同步变化且总数不变。
---
##### 【P1】【TC-UI-USER-UNI-019】【TP_USER_UNI_025】打开列设置面板
- 前置条件:默认查询至少 1 条
- 操作步骤:
1. 点击「查 询」等待列表稳定
2. 点击表格工具栏列设置(齿轮)图标
3. 断言弹出面板含「列展示」「字段设置」「重置」,关闭面板
- 自动化断言:与 CSV `USER-UNI-019` 及 `tests/spd/basic-user-union.spec.ts` 一致
###### 预期结果
列设置面板可打开;关键项可见;关闭后列表正常。
---
##### 【P1】【TC-UI-USER-UNI-020】【TP_USER_UNI_025】列设置取消勾选后列表列恢复
- 前置条件:默认查询至少 1 条;存在可 toggle 的非核心列,否则 skip
- 操作步骤:
1. 点击「查 询」后打开列设置,取消勾选一列非核心字段并关闭
2. 断言该列表头隐藏;再次打开列设置勾选恢复并关闭
3. 断言该列表头重新可见
- 自动化断言:与 CSV `USER-UNI-020` 及 `tests/spd/basic-user-union.spec.ts` 一致
###### 预期结果
取消勾选后表头隐藏;再勾选后表头恢复;无报错遮罩。
---
### 医共体用户管理 · 授权
#### 功能验证点
##### 【P0】【TC-UI-USER-UNI-021】【TP_USER_UNI_051】打开授权后取消不生效
- 前置条件:列表至少 1 条
- 操作步骤:
1. 点击首行「授权」
2. 断言打开用户角色授权层,可见可勾选菜单/功能
3. 如可勾选则改一处勾选后点取消/关闭
4. 再次打开同一用户授权,断言勾选与操作前一致
- 自动化断言:与 CSV `USER-UNI-021` 及 `tests/spd/basic-user-union.spec.ts` 一致
###### 预期结果
授权层可打开;取消后勾选不落库。
---
##### 【P0】【TC-UI-USER-UNI-022】【TP_USER_UNI_051】授权弹框账号与所选行一致
- 前置条件:列表至少 1 条且登录账号非空
- 操作步骤:
1. 点击「查 询」,任选列表一条登录账号非空的行,记录 `${sampleLoginName}`、`${sampleUserName}`
2. 点击该行「授权」
3. 断言弹出用户角色授权弹框,弹框展示的账号信息与所选行登录账号、姓名一致
4. 关闭弹框
- 自动化断言:与 CSV `USER-UNI-022` 及 `tests/spd/basic-user-union.spec.ts` 一致
###### 预期结果
用户角色授权弹框打开;弹框账号与所选列表行一致。
---
##### 【P1】【TC-UI-USER-UNI-023】【TP_USER_UNI_051】授权弹框添加移除角色行
- 前置条件:列表至少 1 条;授权弹框含「添加/移除」(非仅权限树模式),否则 skip
- 操作步骤:
1. 点击「查 询」,任选列表一条数据行,点击该行「授权」
2. 断言弹出用户角色授权弹框
3. 记录弹框内可「移除」的角色行数,点击「添加」
4. 断言可移除行数 +1;点击新增行的「移除」
5. 断言可移除行数恢复为添加前;关闭弹框且不保存
- 自动化断言:与 CSV `USER-UNI-023` 及 `tests/spd/basic-user-union.spec.ts` 一致
###### 预期结果
添加后列表下方新增一行;移除后新增行被删除;未保存不落库。
## 质量与覆盖说明
| 项 | 说明 |
|----|------|
| 用例数 | **16** 条(P0=10 / P1=6) |
| 用例数 | **23** 条(P0=13 / P1=10) |
| 来源 | 从 58 条测试点选取可稳定自动化的主路径与关键反向 |
| 未纳入自动化 | 授权/改密持久化(引用 TP_USER_USR)、真实跨院保存、Excel 导入文件、接口失败、会话过期 |
| 单一职责 | 每条只断言一个核心业务结果 |
......@@ -7,7 +7,7 @@
**元素扫描**:`data/scan/basic-user-elements.json`(219 项)
**关联规范**:`knowledge-base/08-UI专项规范库/UI-SPEC-SPD-USER-001.md`
**版本**:2026-10-20
**自动化框架**:Playwright + TypeScript(规划 `tests/spd/basic-user.spec.ts`,依赖 `auth.setup` 登录态)
**自动化框架**:Playwright + TypeScript(`tests/spd/basic-user.spec.ts`,依赖 `auth.setup` 登录态)
**通用前置**:正例账号 `test_1` 经 `auth.setup` 写入 `storageState`(密码仅存 `.env` 的 `TEST_PASS`);具备「基础设置 → 用户管理 → 用户管理」权限。反例筛选用 mock(`__NOT_EXIST_USER__99999` / `__NOT_EXIST_ROLE__99999`)。
**页面约定**:整页渲染,**无报表 iframe**;定位均在 `page` 上。
......@@ -19,111 +19,32 @@
## 基础设置 · 用户管理
### 用户管理 · 页面进入
#### 功能验证点
##### 【P0】【TC-UI-USER-USR-001】【TP_USER_USR_001】菜单进入用户管理页
- 前置条件:已进入用户管理页
- 操作步骤:
1. 按菜单「基础设置 → 用户管理(子菜单)→ 用户管理(叶子)」进入,或 `openPath` 打开 `/spd/basic/user/platform/person`
2. 断言 URL 含 `/spd/basic/user/platform/person`
3. 断言筛选区、工具栏「新 增」「Excel导入」、列表列头与分页可见
- 自动化断言:规划与 CSV `USER-USR-001` 及 `tests/spd/basic-user.spec.ts` 一致
###### 预期结果
进入用户管理页;不误入医共体用户管理或角色管理;无报错遮罩。
---
#### 异常验证点
##### 【P0】【TC-UI-USER-USR-002】【TP_USER_USR_005】未登录访问拦截
- 前置条件:已进入用户管理页
- 操作步骤:
1. 清除登录态后直接打开 `/spd/basic/user/platform/person`
2. 断言跳转登录或不展示用户业务列表
- 自动化断言:规划与 CSV `USER-USR-002` 及 `tests/spd/basic-user.spec.ts` 一致
###### 预期结果
未登录不可见用户数据。
---
### 用户管理 · 筛选与查询
#### 功能验证点
##### 【P0】【TC-UI-USER-USR-003】【TP_USER_USR_009】默认查询列表有数据或空态明确
- 前置条件:已进入用户管理页
- 操作步骤:
1. 点击「查 询」并等待列表稳定
2. 断言出现数据行,或「暂无数据」且分页总数为 0
- 自动化断言:规划与 CSV `USER-USR-003` 及 `tests/spd/basic-user.spec.ts` 一致
###### 预期结果
查询无报错;有数据时分页「共 N 条」N≥1;无数据时空态明确。
---
##### 【P0】【TC-UI-USER-USR-004】【TP_USER_USR_010】姓名或登录账号关键字查询
- 前置条件:默认查询至少 1 条且姓名或登录账号非空
- 操作步骤:
1. 从首行读取 `${sampleUserName}` 或 `${sampleLoginName}` 取关键字
2. 在「姓名」框(占位「姓名、登录账号」)填入关键字,点击「查 询」
3. 断言结果行姓名或登录账号匹配该关键字
- 自动化断言:规划与 CSV `USER-USR-004` 及 `tests/spd/basic-user.spec.ts` 一致
###### 预期结果
查询结果与已填关键字匹配;无未捕获异常。
---
##### 【P0】【TC-UI-USER-USR-005】【TP_USER_USR_011】科室下拉筛选
- 前置条件:列表存在所属科室非空行,否则 skip
- 操作步骤:
1. 取首条所属科室非 `--` 的行,读取 `${sampleDept}`
2. 科室下拉选择该值,点击「查 询」
3. 断言结果行「所属科室」与所选一致,且不含未分配 `--` 行
- 自动化断言:规划与 CSV `USER-USR-005` 及 `tests/spd/basic-user.spec.ts` 一致
###### 预期结果
所属科室列与筛选条件一致。
---
##### 【P0】【TC-UI-USER-USR-006】【TP_USER_USR_013】姓名+科室+角色组合查询
##### 【P0】【TC-UI-USER-USR-001】【TP_USER_USR_013】姓名+科室+角色+状态组合查询
- 前置条件:默认查询至少 1 条
- 操作步骤:
1. 读取 `${sampleUserName}`、`${sampleDept}`、`${sampleRole}`(角色可从该行截断前列名)
2. 填写姓名、科室、角色后点击「查 询」
3. 断言结果同时满足已填条件
- 自动化断言:规划与 CSV `USER-USR-006` 及 `tests/spd/basic-user.spec.ts` 一致
1. 点击「查 询」后读取首行 `${sampleUserName}`、`${sampleLoginName}`、`${sampleDept}`、`${sampleRole}`、`${sampleStatus}`(角色取截断前列名;状态优先读列表「状态」列,否则按行内启用/禁用文案推断)
2. 填写姓名、科室、角色,展开高级筛选选择 `${sampleStatus}` 后点「筛 选」(无「筛 选」则点「查 询」)
3. 断言结果非空;首行登录账号与采样行一致;可见行姓名/科室/角色/状态与已填条件一致
- 自动化断言:与 CSV `USER-USR-001` 及 `tests/spd/basic-user.spec.ts` 一致
###### 预期结果
组合查询无报错;结果同时满足全部已填条件。
组合查询无报错;查询结果与首行采样条件一致且同时满足全部已填条件。
---
##### 【P1】【TC-UI-USER-USR-007】【TP_USER_USR_014】重置恢复默认
##### 【P1】【TC-UI-USER-USR-002】【TP_USER_USR_014】重置恢复默认
- 前置条件:已进入用户管理页
- 操作步骤:
1. 填写姓名等筛选条件后点击「重 置」
2. 断言筛选字段恢复默认,列表按默认规则刷新
- 自动化断言:规划与 CSV `USER-USR-007` 及 `tests/spd/basic-user.spec.ts` 一致
- 自动化断言:与 CSV `USER-USR-002` 及 `tests/spd/basic-user.spec.ts` 一致
###### 预期结果
......@@ -131,13 +52,13 @@
---
##### 【P1】【TC-UI-USER-USR-008】【TP_USER_USR_015】展开高级筛选
##### 【P1】【TC-UI-USER-USR-003】【TP_USER_USR_015】展开高级筛选
- 前置条件:已进入用户管理页
- 操作步骤:
1. 点击「高级筛选」
2. 断言展开后面板可见姓名/科室/角色/状态及「重 置」「筛 选」
- 自动化断言:规划与 CSV `USER-USR-008` 及 `tests/spd/basic-user.spec.ts` 一致
- 自动化断言:与 CSV `USER-USR-003` 及 `tests/spd/basic-user.spec.ts` 一致
###### 预期结果
......@@ -145,14 +66,14 @@
---
##### 【P0】【TC-UI-USER-USR-009】【TP_USER_USR_016】高级筛选按状态过滤
##### 【P0】【TC-UI-USER-USR-004】【TP_USER_USR_016】高级筛选按状态过滤
- 前置条件:已进入用户管理页
- 操作步骤:
1. 展开高级筛选,状态下拉选择「启用」或「禁用」
2. 点击「筛 选」
3. 断言列表仅保留对应状态用户(无匹配则空态且总数为 0)
- 自动化断言:规划与 CSV `USER-USR-009` 及 `tests/spd/basic-user.spec.ts` 一致
- 自动化断言:与 CSV `USER-USR-004` 及 `tests/spd/basic-user.spec.ts` 一致
###### 预期结果
......@@ -162,14 +83,14 @@
#### 异常验证点
##### 【P1】【TC-UI-USER-USR-010】【TP_USER_USR_022】无匹配结果空态(mock反例)
##### 【P1】【TC-UI-USER-USR-005】【TP_USER_USR_022】无匹配结果空态(mock反例)
- 前置条件:已进入用户管理页
- 操作步骤:
1. 姓名框填入 `__NOT_EXIST_USER__99999`
2. 点击「查 询」
3. 断言空态或「暂无数据」,分页总数为 0
- 自动化断言:规划与 CSV `USER-USR-010` 及 `tests/spd/basic-user.spec.ts` 一致
- 自动化断言:与 CSV `USER-USR-005` 及 `tests/spd/basic-user.spec.ts` 一致
###### 预期结果
......@@ -181,41 +102,14 @@
#### 功能验证点
##### 【P0】【TC-UI-USER-USR-011】【TP_USER_USR_025】列表核心列完整
- 前置条件:已进入用户管理页
- 操作步骤:
1. 断言表头含序号、姓名、登录账号、联系方式、所属科室、医院员工编号、角色、科室任职数、操作
2. 断言操作列可见「编辑」「授权」「重置密码」
- 自动化断言:规划与 CSV `USER-USR-011` 及 `tests/spd/basic-user.spec.ts` 一致
###### 预期结果
核心列完整;行内三个操入口可见。
---
##### 【P1】【TC-UI-USER-USR-012】【TP_USER_USR_026】列表不含所属医院列
- 前置条件:已进入用户管理页
- 操作步骤:
1. 断言表头不含「所属医院」
- 自动化断言:规划与 CSV `USER-USR-012` 及 `tests/spd/basic-user.spec.ts` 一致
###### 预期结果
本页无所属医院列,与医共体用户管理页区分。
---
##### 【P1】【TC-UI-USER-USR-013】【TP_USER_USR_031】分页翻至第 2 页
##### 【P1】【TC-UI-USER-USR-006】【TP_USER_USR_031】分页翻至第 2 页
- 前置条件:默认查询总条数大于一页,否则 skip
- 操作步骤:
1. 记录第 1 页首行登录账号
2. 点击页码「2」或「下一页」
3. 断言页码高亮为 2,首行账号与第 1 页不同
- 自动化断言:规划与 CSV `USER-USR-013` 及 `tests/spd/basic-user.spec.ts` 一致
- 自动化断言:与 CSV `USER-USR-006` 及 `tests/spd/basic-user.spec.ts` 一致
###### 预期结果
......@@ -227,13 +121,13 @@
#### 功能验证点
##### 【P0】【TC-UI-USER-USR-014】【TP_USER_USR_041】打开新增用户弹窗
##### 【P0】【TC-UI-USER-USR-007】【TP_USER_USR_041】打开新增用户弹窗
- 前置条件:已进入用户管理页
- 操作步骤:
1. 点击「新 增」
2. 断言弹出用户表单,可见账号、姓名、联系方式、医院员工编号、启用/禁用、「取 消」「确 定」
- 自动化断言:规划与 CSV `USER-USR-014` 及 `tests/spd/basic-user.spec.ts` 一致
- 自动化断言:与 CSV `USER-USR-007` 及 `tests/spd/basic-user.spec.ts` 一致
###### 预期结果
......@@ -241,14 +135,14 @@
---
##### 【P1】【TC-UI-USER-USR-015】【TP_USER_USR_043】新增取消不落库
##### 【P1】【TC-UI-USER-USR-008】【TP_USER_USR_043】新增取消不落库
- 前置条件:已进入用户管理页
- 操作步骤:
1. 打开新增,账号填 `${uniqLogin}`,姓名填任意合法值
2. 点击「取 消」
3. 按账号查询,断言无该记录
- 自动化断言:规划与 CSV `USER-USR-015` 及 `tests/spd/basic-user.spec.ts` 一致
- 自动化断言:与 CSV `USER-USR-008` 及 `tests/spd/basic-user.spec.ts` 一致
###### 预期结果
......@@ -258,13 +152,13 @@
#### 异常验证点
##### 【P0】【TC-UI-USER-USR-016】【TP_USER_USR_047】必填为空拦截
##### 【P0】【TC-UI-USER-USR-009】【TP_USER_USR_047】必填为空拦截
- 前置条件:已进入用户管理页
- 操作步骤:
1. 打开新增,账号或姓名留空后点击「确 定」
2. 断言出现必填提示,弹窗仍打开
- 自动化断言:规划与 CSV `USER-USR-016` 及 `tests/spd/basic-user.spec.ts` 一致
- 自动化断言:与 CSV `USER-USR-009` 及 `tests/spd/basic-user.spec.ts` 一致
###### 预期结果
......@@ -272,14 +166,14 @@
---
##### 【P0】【TC-UI-USER-USR-017】【TP_USER_USR_048】登录账号重复拦截
##### 【P0】【TC-UI-USER-USR-010】【TP_USER_USR_048】登录账号重复拦截
- 前置条件:默认查询至少 1 条且登录账号非空
- 操作步骤:
1. 打开新增,账号填入已存在的 `${sampleLoginName}`,补齐其它必填后点「确 定」
2. 断言保存失败并提示,弹窗仍打开
3. 查询该账号,断言原用户未被覆盖
- 自动化断言:规划与 CSV `USER-USR-017` 及 `tests/spd/basic-user.spec.ts` 一致
- 自动化断言:与 CSV `USER-USR-010` 及 `tests/spd/basic-user.spec.ts` 一致
###### 预期结果
......@@ -291,14 +185,14 @@
#### 功能验证点
##### 【P0】【TC-UI-USER-USR-018】【TP_USER_USR_054】打开编辑用户弹窗并回填
##### 【P0】【TC-UI-USER-USR-011】【TP_USER_USR_054】打开编辑用户弹窗并回填
- 前置条件:列表至少 1 条
- 操作步骤:
1. 点击首行「编辑」
2. 断言弹窗标题含「编辑用户」
3. 断言账号、姓名与该行 `${sampleLoginName}`、`${sampleUserName}` 一致(允许规范化空白)
- 自动化断言:规划与 CSV `USER-USR-018` 及 `tests/spd/basic-user.spec.ts` 一致
- 自动化断言:与 CSV `USER-USR-011` 及 `tests/spd/basic-user.spec.ts` 一致
###### 预期结果
......@@ -306,14 +200,14 @@
---
##### 【P1】【TC-UI-USER-USR-019】【TP_USER_USR_058】编辑取消不保存
##### 【P1】【TC-UI-USER-USR-012】【TP_USER_USR_058】编辑取消不保存
- 前置条件:列表至少 1 条
- 操作步骤:
1. 打开首行编辑,记录原姓名
2. 将姓名改为临时值后点「取 消」
3. 再次打开编辑,断言姓名仍为原值
- 自动化断言:规划与 CSV `USER-USR-019` 及 `tests/spd/basic-user.spec.ts` 一致
- 自动化断言:与 CSV `USER-USR-012` 及 `tests/spd/basic-user.spec.ts` 一致
###### 预期结果
......@@ -323,13 +217,13 @@
#### 边界验证点
##### 【P1】【TC-UI-USER-USR-020】【TP_USER_USR_061】编辑时账号只读
##### 【P1】【TC-UI-USER-USR-013】【TP_USER_USR_061】编辑时账号只读
- 前置条件:列表至少 1 条
- 操作步骤:
1. 打开首行编辑
2. 断言登录账号不可改(disabled/readonly 或无法输入)
- 自动化断言:规划与 CSV `USER-USR-020` 及 `tests/spd/basic-user.spec.ts` 一致
- 自动化断言:与 CSV `USER-USR-013` 及 `tests/spd/basic-user.spec.ts` 一致
###### 预期结果
......@@ -339,14 +233,14 @@
#### 功能验证点
##### 【P1】【TC-UI-USER-USR-021】【TP_USER_USR_056】编辑弹窗可见签名图片
##### 【P1】【TC-UI-USER-USR-014】【TP_USER_USR_056】编辑弹窗可见签名图片
- 前置条件:列表至少 1 条
- 操作步骤:
1. 打开首行编辑
2. 断言可见「签名图片」区域(有图则展示,无图则上传入口可见)
3. 点「取 消」,不保存
- 自动化断言:规划与 CSV `USER-USR-021` 及 `tests/spd/basic-user.spec.ts` 一致
- 自动化断言:与 CSV `USER-USR-014` 及 `tests/spd/basic-user.spec.ts` 一致
###### 预期结果
......@@ -358,7 +252,7 @@
#### 功能验证点
##### 【P0】【TC-UI-USER-USR-022】【TP_USER_USR_068】打开授权后取消不生效
##### 【P0】【TC-UI-USER-USR-015】【TP_USER_USR_068】打开授权后取消不生效
- 前置条件:列表至少 1 条
- 操作步骤:
......@@ -366,7 +260,7 @@
2. 断言打开授权层(弹窗或权限树),可见可勾选菜单/功能
3. 如可勾选则改一处勾选后点取消/关闭
4. 再次打开同一用户授权,断言勾选与操作前一致
- 自动化断言:规划与 CSV `USER-USR-022` 及 `tests/spd/basic-user.spec.ts` 一致
- 自动化断言:与 CSV `USER-USR-015` 及 `tests/spd/basic-user.spec.ts` 一致
###### 预期结果
......@@ -374,30 +268,96 @@
---
### 用户管理 · 重置密码
### 用户管理 · 列表与分页
#### 功能验证点
##### 【P0】【TC-UI-USER-USR-023】【TP_USER_USR_075】行内重置密码确认后取消
##### 【P1】【TC-UI-USER-USR-016】【TP_USER_USR_032】切换20条每页页面正常且列表同步
- 前置条件:列表至少 1 条
- 前置条件:默认查询总条数大于 10 且切换前可见行数不等于 min(N,20),否则 skip
- 操作步骤:
1. 点击「查 询」,记录分页「共 N 条」与当前页可见行数
2. 分页区切换每页条数为「20 条/页」
3. 断言分页显示 20 条/页;总条数 N 不变;当前页行数为 min(N,20) 且与切换前不同;筛选区、表头可见无报错
- 自动化断言:与 CSV `USER-USR-016` 及 `tests/spd/basic-user.spec.ts` 一致
###### 预期结果
切换 20 条/页成功;页面正常;列表可见行数随每页条数同步变化且总数不变。
---
##### 【P1】【TC-UI-USER-USR-017】【TP_USER_USR_025】打开列设置面板
- 前置条件:默认查询至少 1 条
- 操作步骤:
1. 点击「查 询」等待列表稳定
2. 点击表格工具栏列设置(齿轮)图标
3. 断言弹出面板含「列展示」「字段设置」「重置」,关闭面板
- 自动化断言:与 CSV `USER-USR-017` 及 `tests/spd/basic-user.spec.ts` 一致
###### 预期结果
列设置面板可打开;关键项可见;关闭后列表正常。
---
##### 【P1】【TC-UI-USER-USR-018】【TP_USER_USR_025】列设置取消勾选后列表列恢复
- 前置条件:默认查询至少 1 条;存在可 toggle 的非核心列,否则 skip
- 操作步骤:
1. 点击「查 询」后打开列设置,取消勾选一列非核心字段并关闭
2. 断言该列表头隐藏;再次打开列设置勾选恢复并关闭
3. 断言该列表头重新可见
- 自动化断言:与 CSV `USER-USR-018` 及 `tests/spd/basic-user.spec.ts` 一致
###### 预期结果
取消勾选后表头隐藏;再勾选后表头恢复;无报错遮罩。
---
### 用户管理 · 授权
#### 功能验证点
##### 【P0】【TC-UI-USER-USR-019】【TP_USER_USR_068】授权弹框账号与所选行一致
- 前置条件:列表至少 1 条且登录账号非空
- 操作步骤:
1. 点击「查 询」,任选列表一条登录账号非空的行,记录 `${sampleLoginName}`、`${sampleUserName}`
2. 点击该行「授权」
3. 断言弹出用户角色授权弹框,弹框展示的账号信息与所选行登录账号、姓名一致
4. 关闭弹框
- 自动化断言:与 CSV `USER-USR-019` 及 `tests/spd/basic-user.spec.ts` 一致
###### 预期结果
用户角色授权弹框打开;弹框账号与所选列表行一致。
---
##### 【P1】【TC-UI-USER-USR-020】【TP_USER_USR_082】授权弹框添加移除角色行
- 前置条件:列表至少 1 条;授权弹框含「添加/移除」(非仅权限树模式),否则 skip
- 操作步骤:
1. 点击首行「重置密码」
2. 断言出现确认框或密码表单,含取消/关闭
3. 点取消/关闭
4. 断言仍停留本页且该行仍在
- 自动化断言:规划与 CSV `USER-USR-023` 及 `tests/spd/basic-user.spec.ts` 一致
1. 点击「查 询」,任选列表一条数据行,点击该行「授权」
2. 断言弹出用户角色授权弹框
3. 记录弹框内可「移除」的角色行数,点击「添加」
4. 断言可移除行数 +1;点击新增行的「移除」
5. 断言可移除行数恢复为添加前;关闭弹框且不保存
- 自动化断言:与 CSV `USER-USR-020` 及 `tests/spd/basic-user.spec.ts` 一致
###### 预期结果
重置需确认;取消后不改密。本条不点确定,避免污染账号。
添加后列表下方新增一行;移除后新增行被删除;未保存不落库。
## 质量与覆盖说明
| 项 | 说明 |
|----|------|
| 用例数 | **23** 条(P0=14 / P1=9) |
| 来源 | 从 81 条测试点选取可稳定自动化的主路径与关键反向 |
| 用例数 | **20** 条(P0=8 / P1=12) |
| 来源 | 从 82 条测试点选取可稳定自动化的主路径与关键反向 |
| 未纳入自动化 | 真实保存用户、禁用后登录、真实改密、Excel 导入文件、接口失败注入、会话过期、无菜单权限 |
| 单一职责 | 每条只断言一个核心业务结果 |
......@@ -7,50 +7,50 @@
**元素扫描**:`data/scan/basic-role-elements.json`(1170 项)
**关联规范**:`knowledge-base/08-UI专项规范库/UI-SPEC-SPD-USER-003.md`
**版本**:2026-10-20
**自动化框架**:Playwright + TypeScript(规划 `tests/spd/basic-role.spec.ts`,依赖 `auth.setup` 登录态)
**自动化框架**:Playwright + TypeScript(`tests/spd/basic-role.spec.ts`,依赖 `auth.setup` 登录态)
**通用前置**:正例账号 `test_1` 经 `auth.setup` 写入 `storageState`(密码仅存 `.env` 的 `TEST_PASS`);具备「基础设置 → 用户管理 → 角色管理」权限。反例筛选用 mock(`__NOT_EXIST_USER__99999` / `__NOT_EXIST_ROLE__99999`)。
**通用前置**:正例账号 `test_1` 经 `auth.setup` 写入 `storageState`(密码仅存 `.env` 的 `TEST_PASS`);具备「基础设置 → 用户管理 → 角色管理」权限。
**页面约定**:整页渲染,**无报表 iframe**;定位均在 `page` 上。
**菜单导航**:菜单「基础设置 → 用户管理 → 角色管理」;本页左右分栏、无独立分页。
**写操作**:权限树勾选后点「刷 新」恢复,不点「保 存」;删除只验证确认框后取消;新增只打开后取消。禁止删除预置角色或真实改名。
**动态取数**:左栏读取 `${sampleRoleName}`(优先非预置自定义角色;预置用「医院管理员」若存在);权限树按「模块名 + 菜单名」定位,根为「SPD内网系统PC端」;mock:`__NOT_EXIST_ROLE__99999`。
**写操作**:权限树 001/002 只测展开收起,不保存授权;003 切换同步。编辑 004 取消;005 不改名保存(受 SKIP_BASIC_WRITE)。新增 006–010 不提交落库。
**动态取数**:左栏读取 `${sampleRoleName}`(优先非预置自定义角色;预置用「医院管理员」若存在);权限树按「模块名 + 菜单名」定位,根为「SPD内网系统PC端」。
---
## 基础设置 · 用户管理
### 角色管理 · 页面进入
### 角色管理 · 右侧菜单权限树
#### 功能验证点
##### 【P0】【TC-UI-USER-ROL-001】【TP_USER_ROL_001】菜单进入角色管理页
##### 【P0】【TC-UI-USER-ROL-001】【TP_USER_ROL_018】权限树按应用与模块展开
- 前置条件:已进入角色管理页
- 操作步骤:
1. 按菜单「基础设置 → 用户管理 → 角色管理」进入,或 `openPath` 打开 `/spd/basic/user/platform/role`
2. 断言 URL 含 `/spd/basic/user/platform/role`
3. 断言左侧角色列表、右侧菜单权限树、「保 存」「刷 新」可见,无独立分页
- 自动化断言:规划与 CSV `USER-ROL-001` 及 `tests/spd/basic-role.spec.ts` 一致
1. 选中任一角色
2. 断言右侧树根为「SPD内网系统PC端」,可见基础设置/运维管理/厂商管理/产品管理等模块
3. 展开「基础设置」,断言可见子菜单(如医院资料)
- 自动化断言:与 CSV `USER-ROL-001` 及 `tests/spd/basic-role.spec.ts` 一致
###### 预期结果
进入角色管理;左右分栏渲染完成;不误入用户管理页。
权限树层级可见;节点可展开。
---
#### 异常验证点
##### 【P0】【TC-UI-USER-ROL-002】【TP_USER_ROL_005】未登录访问拦截
##### 【P0】【TC-UI-USER-ROL-002】【TP_USER_ROL_018】权限树模块节点收起正常
- 前置条件:已进入角色管理页
- 操作步骤:
1. 清除登录态后直接打开 `/spd/basic/user/platform/role`
2. 断言跳转登录或不展示角色/权限树业务数据
- 自动化断言:规划与 CSV `USER-ROL-002` 及 `tests/spd/basic-role.spec.ts` 一致
1. 选中任一角色
2. 展开「基础设置」,断言可见子菜单(如医院资料)
3. 点击「基础设置」节点展开图标收起该模块
4. 断言子菜单(如医院资料)不可见,且「基础设置」节点处于收起态
- 自动化断言:与 CSV `USER-ROL-002` 及 `tests/spd/basic-role.spec.ts` 一致
###### 预期结果
未登录不可见角色配置。
权限树节点可收起;子级随父节点隐藏。
---
......@@ -58,204 +58,190 @@
#### 功能验证点
##### 【P0】【TC-UI-USER-ROL-003】【TP_USER_ROL_009】角色列表加载并可点选
##### 【P0】【TC-UI-USER-ROL-003】【TP_USER_ROL_011】切换角色右侧权限树同步更新
- 前置条件:已进入角色管理页
- 前置条件:左栏至少 2 个角色
- 操作步骤:
1. 断言左栏列出角色名称(如医院管理员、护士等)
2. 点击首个 `${sampleRoleName}`
3. 断言该行处于选中态
- 自动化断言:规划与 CSV `USER-ROL-003` 及 `tests/spd/basic-role.spec.ts` 一致
1. 点选左栏第一个角色,记录右侧权限树勾选快照
2. 点选左栏第二个不同角色,断言该行选中且右侧权限树仍可见
3. 再次点选第一个角色,断言右侧权限树勾选快照与步骤 2 一致
- 自动化断言:与 CSV `USER-ROL-003` 及 `tests/spd/basic-role.spec.ts` 一致
###### 预期结果
左栏有角色且可点选。
切换后右侧与当前角色一致;切回后恢复该角色已保存勾选,无错乱残留。
---
##### 【P0】【TC-UI-USER-ROL-004】【TP_USER_ROL_010】角色名称搜索过滤
### 角色管理 · 角色增删改
#### 功能验证点
##### 【P0】【TC-UI-USER-ROL-004】【TP_USER_ROL_033】编辑角色弹框回填名称一致
- 前置条件:左栏至少 1 个角色
- 操作步骤:
1. 在角色名称框输入 `${sampleRoleName}` 的部分关键字
2. 断言左栏收敛为匹配项(即时或回车,以现网为准)
- 自动化断言:规划与 CSV `USER-ROL-004` 及 `tests/spd/basic-role.spec.ts` 一致
1. 点选左栏任一角色,记录其角色名称
2. 点击左栏右下角(或行内)编辑图标
3. 断言弹出编辑角色弹框,标题或内容含「编辑」
4. 断言弹框内「角色名称」输入框回填值与所选角色名称一致
5. 点「取 消」或「关 闭」关闭弹框,不保存
- 自动化断言:与 CSV `USER-ROL-004` 及 `tests/spd/basic-role.spec.ts` 一致
###### 预期结果
搜索结果角色名包含关键字。
编辑弹框正常打开;角色名称与当前选中角色一致;本条不保存。
---
##### 【P0】【TC-UI-USER-ROL-005】【TP_USER_ROL_011】选中角色刷新右侧权限树
##### 【P0】【TC-UI-USER-ROL-005】【TP_USER_ROL_033】编辑角色保存成功
- 前置条件:左栏至少 2 个角色
- 前置条件:左栏至少 1 个可编辑角色;环境允许写操作(非 SKIP_BASIC_WRITE=1)
- 操作步骤:
1. 依次点选两个不同角色
2. 断言右侧勾选随当前角色变化,无上一角色残留
- 自动化断言:规划与 CSV `USER-ROL-005` 及 `tests/spd/basic-role.spec.ts` 一致
1. 点选左栏任一角色
2. 点击左栏右下角(或行内)编辑图标,断言弹出编辑角色弹框
3. 不修改角色名称,点击弹框内「保 存」
4. 断言页面无报错提示,且出现「编辑成功」类成功提示
5. 断言编辑弹框关闭
- 自动化断言:与 CSV `USER-ROL-005` 及 `tests/spd/basic-role.spec.ts` 一致(`SKIP_BASIC_WRITE=1` 时 skip)
###### 预期结果
权限树与当前角色配置一致。
保存成功;提示编辑成功;角色数据无异常报错。
---
##### 【P1】【TC-UI-USER-ROL-006】【TP_USER_ROL_012】清空搜索恢复全量
##### 【P0】【TC-UI-USER-ROL-006】【TP_USER_ROL_032】点击角色按钮打开新增弹框
- 前置条件:已进入角色管理页
- 操作步骤:
1. 先按关键字过滤,再清空搜索
2. 断言左栏恢复未过滤的完整角色列表
- 自动化断言:规划与 CSV `USER-ROL-006` 及 `tests/spd/basic-role.spec.ts` 一致
1. 点击左栏工具区「角 色」按钮
2. 断言弹出新增角色弹框,标题或内容含「新增」或「角色」
3. 断言页面无报错提示(无错误 toast/遮罩)
4. 点「取 消」或「关 闭」关闭弹框,不保存
- 自动化断言:与 CSV `USER-ROL-006` 及 `tests/spd/basic-role.spec.ts` 一致
###### 预期结果
清空后角色列表恢复全量。
新增角色弹框正常打开;页面无报错;本条不提交保存。
---
#### 异常验证点
##### 【P1】【TC-UI-USER-ROL-007】【TP_USER_ROL_016】搜索无匹配空态(mock反例)
##### 【P0】【TC-UI-USER-ROL-007】【TP_USER_ROL_039】新增角色名称为空保存拦截
- 前置条件:已进入角色管理页
- 操作步骤:
1. 角色名称框填入 `__NOT_EXIST_ROLE__99999`
2. 断言左栏空态明确
- 自动化断言:规划与 CSV `USER-ROL-007` 及 `tests/spd/basic-role.spec.ts` 一致
1. 点击左栏工具区「角 色」按钮,断言弹出新增角色弹框
2. 不填写角色名称,直接点击弹框内「保 存」
3. 断言「角色名称」输入框呈校验失败态(变红)
4. 断言同字段下方提示:请输入角色名称!
5. 点「取 消」关闭弹框,不保存
- 自动化断言:与 CSV `USER-ROL-007` 及 `tests/spd/basic-role.spec.ts` 一致
###### 预期结果
无匹配不报错;空态明确。
空名称保存被拦截;角色名称框变红并提示请输入角色名称!;弹框不提交。
---
### 角色管理 · 右侧菜单权限树
#### 功能验证点
##### 【P0】【TC-UI-USER-ROL-008】【TP_USER_ROL_018】权限树按应用与模块展开
##### 【P0】【TC-UI-USER-ROL-008】【TP_USER_ROL_032】新增角色弹框取消关闭
- 前置条件:已进入角色管理页
- 操作步骤:
1. 选中任一角色
2. 断言右侧树根为「SPD内网系统PC端」,可见基础设置/运维管理/厂商管理/产品管理等模块
3. 展开「基础设置」,断言可见子菜单(如医院资料)
- 自动化断言:规划与 CSV `USER-ROL-008` 及 `tests/spd/basic-role.spec.ts` 一致
1. 点击左栏工具区「角 色」按钮,断言弹出新增角色弹框
2. 不填写任何内容,直接点击弹框内「取 消」
3. 断言新增角色弹框关闭
4. 断言页面无报错提示,左栏角色数量不增加
- 自动化断言:与 CSV `USER-ROL-008` 及 `tests/spd/basic-role.spec.ts` 一致
###### 预期结果
权限树层级可见;节点可展开。
取消后弹框关闭;页面无报错;未新增角色。
---
##### 【P0】【TC-UI-USER-ROL-009】【TP_USER_ROL_019】勾选父节点联动子节点
##### 【P0】【TC-UI-USER-ROL-009】【TP_USER_ROL_032】新增弹框查询配置化复选框可勾选
- 前置条件:已进入角色管理页
- 前置条件:新增弹框含「查询配置化」复选框
- 操作步骤:
1. 选中一自定义或可编辑角色(预置被锁则 skip)
2. 记录「厂商管理」父节点原勾选
3. 勾选「厂商管理」父节点
4. 断言其子菜单/功能全部勾选
5. 点击「刷 新」,不点「保 存」
- 自动化断言:规划与 CSV `USER-ROL-009` 及 `tests/spd/basic-role.spec.ts` 一致
1. 点击左栏工具区「角 色」按钮,断言弹出新增角色弹框
2. 定位「查询配置化」复选框,点击勾选,断言为选中态
3. 再次点击取消勾选,断言为未选中态
4. 断言页面无报错提示
5. 点「取 消」关闭弹框,不保存
- 自动化断言:与 CSV `USER-ROL-009` 及 `tests/spd/basic-role.spec.ts` 一致
###### 预期结果
父节点勾选联动子节点;本条不保存。
查询配置化可正常勾选与取消勾选;页面无报错;本条不提交保存。
---
##### 【P1】【TC-UI-USER-ROL-010】【TP_USER_ROL_023】刷新恢复未保存勾选
##### 【P0】【TC-UI-USER-ROL-010】【TP_USER_ROL_032】新增角色弹框X关闭
- 前置条件:已进入角色管理页
- 操作步骤:
1. 改一处勾选后点「刷 新」且未保存
2. 断言树恢复为进入该角色时的已保存状态
- 自动化断言:规划与 CSV `USER-ROL-010` 及 `tests/spd/basic-role.spec.ts` 一致
1. 点击左栏工具区「角 色」按钮,断言弹出新增角色弹框
2. 点击弹框右上角「X」关闭图标
3. 断言新增角色弹框关闭
4. 断言页面无报错提示,左栏角色数量不增加
- 自动化断言:与 CSV `USER-ROL-010` 及 `tests/spd/basic-role.spec.ts` 一致
###### 预期结果
刷新丢弃未保存勾选。
X 关闭后弹框正常关闭;页面无报错;未新增角色。
---
### 角色管理 · 角色增删改
### 角色管理 · 左侧角色列表
#### 功能验证点
##### 【P0】【TC-UI-USER-ROL-011】【TP_USER_ROL_034】删除需确认且取消保留
##### 【P1】【TC-UI-USER-ROL-011】【TP_USER_ROL_009】左栏角色列表可上下滑动
- 前置条件:已进入角色管理页
- 操作步骤:
1. 点一自定义角色的删除入口(预置角色无删除则改点自定义;都没有则 skip)
2. 断言出现确认框,含取消/确定
3. 点「取 消」
4. 断言该角色仍在左栏
- 自动化断言:规划与 CSV `USER-ROL-011` 及 `tests/spd/basic-role.spec.ts` 一致
###### 预期结果
删除需二次确认;取消后角色仍在。本条不点确定。
---
##### 【P1】【TC-UI-USER-ROL-012】【TP_USER_ROL_032】新增角色入口打开后取消
- 前置条件:已进入角色管理页
- 操作步骤:
1. 通过新增或「角色」工具按钮打开新增表单(扫描未打开则 skip)
2. 不填或填写后点取消/关闭
3. 断言左栏角色数量不增加
- 自动化断言:规划与 CSV `USER-ROL-012` 及 `tests/spd/basic-role.spec.ts` 一致
###### 预期结果
新增入口可达;取消不落库。
---
#### 异常验证点
##### 【P1】【TC-UI-USER-ROL-013】【TP_USER_ROL_041】预置角色不可删除
- 前置条件:左栏存在可识别的预置角色,否则 skip
- 前置条件:左栏列表高度溢出可滚动(不足则 skip)
- 操作步骤:
1. 在左栏定位「医院管理员」(或其它预置角色,以现网为准)
2. 断言删除入口不可见;若可见则确定后被拒绝,角色仍在左栏
3. 若弹出确认则点取消,避免误删
- 自动化断言:规划与 CSV `USER-ROL-013` 及 `tests/spd/basic-role.spec.ts` 一致
1. 定位左栏角色列表滚动区域
2. 向下滚动列表,断言 scrollTop 增加且列表可继续交互
3. 向上滚回(滚轮或滑至顶部),断言 scrollTop 减小
4. 断言页面无报错遮罩
- 自动化断言:与 CSV `USER-ROL-011` 及 `tests/spd/basic-role.spec.ts` 一致
###### 预期结果
预置角色不可删除。
左栏可正常上下滑动;滚动后页面无异常。
---
### 角色管理 · 左侧角色列表
### 角色管理 · 右侧菜单权限树
#### 集成验证点
#### 功能验证点
##### 【P1】【TC-UI-USER-ROL-014】【TP_USER_ROL_017】角色名与用户管理角色下拉同源
##### 【P1】【TC-UI-USER-ROL-012】【TP_USER_ROL_025】右侧权限树子菜单可上下滑动
- 前置条件:已进入角色管理页
- 前置条件:已选中任一角色且右侧权限树高度溢出可滚动(不足则 skip)
- 操作步骤:
1. 记录本页左栏角色名称集合
2. 打开用户管理,展开「角色」筛选项
3. 断言两边名称集合一致(允许按权限裁剪,本页有的名称用户页应能对应到)
- 自动化断言:规划与 CSV `USER-ROL-014` 及 `tests/spd/basic-role.spec.ts` 一致
1. 点选左栏任一角色,断言右侧菜单权限树可见
2. 定位右侧权限树滚动区域(含子菜单列表)
3. 向下滚动,断言 scrollTop 增加
4. 向上滚回,断言 scrollTop 减小
5. 断言页面无报错遮罩
- 自动化断言:与 CSV `USER-ROL-012` 及 `tests/spd/basic-role.spec.ts` 一致
###### 预期结果
角色主数据跨页一致,不串名。
右侧子菜单列表可正常上下滑动;滚动后页面无异常。
## 质量与覆盖说明
| 项 | 说明 |
|----|------|
| 用例数 | **14** 条(P0=8 / P1=6) |
| 用例数 | **12** 条(P0=10 / P1=2) |
| 来源 | 从 45 条测试点选取可稳定自动化的主路径与关键反向 |
| 未纳入自动化 | 真实保存权限树、真实新增/改名/删除角色、未保存切角色(待评审)、角色授权后换账号登录、接口失败 |
| 未纳入自动化 | 未登录与菜单进入、角色搜索与空态、权限树勾选联动/刷新/保存、删除与预置角色、与用户页角色下拉同源、真实新增角色落库、未保存切角色提示等 |
| 单一职责 | 每条只断言一个核心业务结果 |
......@@ -42,7 +42,7 @@
- **TP_USER_UNI_010**(高)|姓名/登录账号关键字|验证要点:「姓名」框(占位「姓名、登录账号」)输入后结果姓名或账号匹配|关联需求:UI-SPEC-SPD-USER-002
- **TP_USER_UNI_011**(高)|所属医院筛选|验证要点:切换「所属医院」后列表仅该院用户,「共 N 条」同步变化|关联需求:UI-SPEC-SPD-USER-002
- **TP_USER_UNI_012**(高)|联系方式筛选|验证要点:按「联系方式」关键字查询,结果联系方式匹配|关联需求:UI-SPEC-SPD-USER-002
- **TP_USER_UNI_013**(高)|姓名+医院+联系方式组合查询|验证要点:多条件同时生效,结果同时满足|关联需求:UI-SPEC-SPD-USER-002
- **TP_USER_UNI_013**(高)|姓名+医院+科室+角色+状态组合查询|验证要点:多条件同时生效,结果同时满足;含所属医院维度(自动化 `USER-UNI-006`)|关联需求:UI-SPEC-SPD-USER-002
- **TP_USER_UNI_014**(中)|重置恢复默认|验证要点:点「重置」后医院等条件恢复默认(含默认医院),列表刷新|关联需求:UI-SPEC-SPD-USER-002
- **TP_USER_UNI_015**(中)|展开高级筛选|验证要点:点「高级筛选」展开后可见所属医院、联系方式、科室、角色及「筛选」|关联需求:UI-SPEC-SPD-USER-002
- **TP_USER_UNI_016**(中)|高级筛选按科室过滤|验证要点:高级筛选选择科室后点「筛选」,列表「所属科室」与所选一致|关联需求:UI-SPEC-SPD-USER-002
......@@ -72,7 +72,7 @@
- **TP_USER_UNI_024**(高)|列表核心列完整且顺序正确|验证要点:列顺序为登录账号、姓名、联系方式、所属医院、所属科室、医院员工编号、角色、科室任职数、操作(编辑/授权/重置密码);勿用用户管理页列断言|关联需求:UI-SPEC-SPD-USER-002
- **TP_USER_UNI_025**(中)|登录账号列截断|验证要点:长账号在列宽内截断;按账号搜索/断言用筛选框而非截断后的单元格文本|关联需求:UI-SPEC-SPD-USER-002
- **TP_USER_UNI_026**(中)|分页翻页|验证要点:总条数大于一页时可翻至第 2 页,数据更换且页码高亮正确|关联需求:UI-SPEC-SPD-USER-002
- **TP_USER_UNI_027**(中)|每页条数切换|验证要点:切换每页条数后当前页行数不超过所选值,总数不变|关联需求:UI-SPEC-SPD-USER-002
- **TP_USER_UNI_027**(中)|每页条数切换|验证要点:切换「20 条/页」等后当前页行数为 min(N,所选值),总数不变(自动化 `USER-UNI-018`)|关联需求:UI-SPEC-SPD-USER-002
- **TP_USER_UNI_028**(中)|跳至指定页|验证要点:分页「跳至」输入合法页码后跳到对应页|关联需求:UI-SPEC-SPD-USER-002
#### 边界验证点
......@@ -128,7 +128,7 @@
#### 功能验证点
- **TP_USER_UNI_051**(高)|行内授权可打开|验证要点:点「授权」打开授权层;勾选持久化规则同 TP_USER_USR,本页只校验当前行|关联需求:UI-SPEC-SPD-USER-002
- **TP_USER_UNI_051**(高)|行内授权可打开|验证要点:点「授权」打开用户角色授权弹框;弹框账号与所选行一致;弹框内「添加/移除」可增删角色行;取消不落库(自动化 `USER-UNI-021`~`023`,规则同 TP_USER_USR)|关联需求:UI-SPEC-SPD-USER-002
- **TP_USER_UNI_052**(高)|行内重置密码可执行|验证要点:点「重置密码」可完成改密;改密后登录规则同 TP_USER_USR|关联需求:UI-SPEC-SPD-USER-002
#### 边界验证点
......@@ -157,5 +157,5 @@
| 分类 | 功能 / 边界 / 异常 / 集成四类均已按功能点拆分;无某类时保留标题并说明 |
| 去重 | 登录拦截、会话过期与同模块其它用户管理页规则相同,本页仅保留菜单/URL/字段差异;编号前缀 `TP_USER_UNI` |
| 待评审项 | ① 新增/授权弹窗扫描未稳定打开,以现网为准 ② 跨院登录账号按拒绝且不覆盖写点,若产品允许共用需改判定 ③ 所属医院默认值与顶栏约定以现网为准 ④ 签名/启用禁用/任职明细/改密后登录同用户管理对应 TP,本页只保留医院维度 |
| 下游 | 可转结构化 MD/CSV;扫描 `npm run discover:scan-user`;自动化建议 `pages/spd/basic-user-union.page.ts` |
| 下游 | 可转结构化 MD/CSV(`npm run generate:md-user`);扫描 `npm run discover:scan-user`;Playwright `tests/spd/basic-user-union.spec.ts` + `pages/spd/basic-user-union.page.ts` |
......@@ -77,7 +77,7 @@
- **TP_USER_USR_029**(中)|表格密度切换|验证要点:密度切换后列表仍可读,列不丢失|关联需求:UI-SPEC-SPD-USER-001
- **TP_USER_USR_030**(低)|无批量操作|验证要点:序号列可勾选,但工具栏无批量启用/删除/授权;按「无批量」处理,不测批量提交|关联需求:UI-SPEC-SPD-USER-001
- **TP_USER_USR_031**(中)|分页翻页|验证要点:总条数大于一页时可翻至第 2 页,数据更换且页码高亮正确|关联需求:UI-SPEC-SPD-USER-001
- **TP_USER_USR_032**(中)|每页条数切换|验证要点:切换「50 条/页」等后当前页行数不超过所选值,总数不变|关联需求:UI-SPEC-SPD-USER-001
- **TP_USER_USR_032**(中)|每页条数切换|验证要点:切换「20 条/页」「50 条/页」等后当前页行数为 min(N,所选值) 且与切换前不同(当数据量足够时),总数不变;UI 自动化 `USER-USR-016` 覆盖切换 20 条/页|关联需求:UI-SPEC-SPD-USER-001
- **TP_USER_USR_033**(中)|跳至指定页|验证要点:分页「跳至」输入合法页码后跳到对应页|关联需求:UI-SPEC-SPD-USER-001
#### 边界验证点
......@@ -161,7 +161,7 @@
#### 功能验证点
- **TP_USER_USR_068**(高)|打开授权|验证要点:行内「授权」打开授权层(弹窗或权限树),展示可勾选菜单/功能|关联需求:UI-SPEC-SPD-USER-001
- **TP_USER_USR_068**(高)|打开授权|验证要点:行内「授权」打开用户角色授权弹框(或权限树层),展示可勾选菜单/功能;弹框账号与所选行登录账号、姓名一致(自动化 `USER-USR-019`)|关联需求:UI-SPEC-SPD-USER-001
- **TP_USER_USR_069**(高)|保存后勾选持久化|验证要点:勾选后保存,关闭再打开同一用户,勾选与保存时一致|关联需求:UI-SPEC-SPD-USER-001
- **TP_USER_USR_070**(中)|取消不生效|验证要点:勾选后点取消/关闭,再次打开仍为操作前勾选|关联需求:UI-SPEC-SPD-USER-001
......@@ -177,6 +177,7 @@
- **TP_USER_USR_073**(高)|授权后仅可见已授权菜单|验证要点:保存后该用户登录仅能看到已授权菜单|关联需求:UI-SPEC-SPD-USER-001
- **TP_USER_USR_074**(中)|授权节点与角色权限树语义一致|验证要点:用户授权层菜单节点名称与角色管理权限树对应节点一致|关联需求:UI-SPEC-SPD-USER-001
- **TP_USER_USR_082**(中)|授权角色行添加与移除|验证要点:用户角色授权弹框内点「添加」列表增加一行,点「移除」后新增行被删除;不保存不落库(自动化 `USER-USR-020`)|关联需求:UI-SPEC-SPD-USER-001
---
......@@ -208,9 +209,9 @@
| 项 | 结论 |
|----|------|
| 覆盖 | 已覆盖:导航、筛选查询、列表分页、新增/编辑(含签名与启用禁用)、授权、重置密码、权限与登录异常、同模块衔接;共 **81** 条原子测试点(未声称全覆盖,断网类与仓库其它基础管理页同样省略) |
| 覆盖 | 已覆盖:导航、筛选查询、列表分页、新增/编辑(含签名与启用禁用)、授权、重置密码、权限与登录异常、同模块衔接;共 **82** 条原子测试点(未声称全覆盖,断网类与仓库其它基础管理页同样省略) |
| 分类 | 功能 / 边界 / 异常 / 集成四类均已按功能点拆分;无某类时保留标题并说明 |
| 去重 | 登录拦截、会话过期与同模块其它用户管理页规则相同,本页仅保留菜单/URL/字段差异;编号前缀 `TP_USER_USR` |
| 待评审项 | ① 新增弹窗扫描未稳定打开,字段以编辑弹窗与 UI-SPEC 为准 ② 授权层扫描未采集到,勾选控件以现网为准 ③ 编辑时登录账号按只读写点,若现网可改需改判定 ④ 空权限按允许保存写点,联系方式非法/签名非图片按拦截写点,禁用用户重置按允许且不启用写点 ⑤ UI-SPEC 截图未见「查询」,现网扫描有「查询」,测试点以扫描为准 ⑥ 列表序号可勾选但无批量工具栏,按无批量处理 |
| 下游 | 可转结构化 MD/CSV;扫描 `npm run discover:scan-user`;自动化建议 `pages/spd/basic-user.page.ts` |
| 下游 | 可转结构化 MD/CSV(`npm run generate:md-user`);扫描 `npm run discover:scan-user`;Playwright `tests/spd/basic-user.spec.ts` + `pages/spd/basic-user.page.ts`(含 `USER-USR-016` 每页 20 条) |
......@@ -127,6 +127,6 @@
| 覆盖 | 已覆盖:导航(左右分栏无分页)、左栏角色搜索、右栏权限树勾选联动/保存/未保存切角色、角色增删改与用户页下拉同步;共 **45** 条原子测试点(未声称全覆盖,断网类与仓库其它基础管理页同样省略) |
| 分类 | 功能 / 边界 / 异常 / 集成四类均已按功能点拆分;无某类时保留标题并说明 |
| 去重 | 登录拦截、会话过期与同模块其它用户管理页规则相同,本页仅保留菜单/URL/字段差异;编号前缀 `TP_USER_ROL` |
| 待评审项 | ① 新增角色弹窗扫描未打开,入口以现网工具栏/行内图标为准,对应点优先级为中 ② 预置角色按不可删除写点,以现网预置名单为准 ③ 未保存切角色按「提示后丢弃、不静默写入」写点,若自动保存需改判定 ④ 本页无授权弹窗、无分页、无所属医院 |
| 下游 | 可转结构化 MD/CSV;扫描 `npm run discover:scan-user`;自动化建议 `pages/spd/basic-role.page.ts` |
| 待评审项 | ① 预置角色按不可删除写点,以现网预置名单为准 ② 未保存切角色按「提示后丢弃、不静默写入」写点,若自动保存需改判定 ③ 本页无授权弹窗、无分页、无所属医院 |
| 下游 | UI 自动化 CSV **12** 条:`testcase/基础管理-用户管理-角色管理-UI自动化测试用例-01.csv`;MD `testcases/基础管理-用户管理-角色管理-UI自动化测试用例-01.md`;`tests/spd/basic-role.spec.ts`;`npm run test:basic-role:all`;扫描 `npm run discover:scan-user` |
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-MLS-001 菜单进入物料准入单列表', { tag: '@smoke' }, async ({ admitMaterialListPage }) => {
await admitMaterialListPage.openFromMenu();
await admitMaterialListPage.expectEntered();
await admitMaterialListPage.ready();
expect(admitMaterialListPage.browserPage.url(), 'URL 应为物料准入单列表').toContain('zrlist');
await expect(admitMaterialListPage.tab('已通过'), '已通过 Tab').toBeVisible();
});
});
......@@ -15,7 +18,7 @@ test.describe('基础设置 · 准入管理', () => {
test('ADMIT-MLS-002 默认查询列表有数据或空态明确', { tag: '@smoke' }, async ({ admitMaterialListPage }) => {
await admitMaterialListPage.open();
await admitMaterialListPage.query();
await admitMaterialListPage.expectListOrEmpty();
await assertAdmitListOrEmpty(await admitMaterialListPage.snapshotList());
});
test('ADMIT-MLS-003 按准入单号筛选', async ({ admitMaterialListPage }) => {
......@@ -30,12 +33,23 @@ test.describe('基础设置 · 准入管理', () => {
test('ADMIT-MLS-005 无匹配结果空态(mock反例)', async ({ admitMaterialListPage }) => {
await admitMaterialListPage.open();
await admitMaterialListPage.mockEmpty();
await assertAdmitMockEmpty(await admitMaterialListPage.mockEmpty());
});
test('ADMIT-MLS-006 列表核心列完整', async ({ admitMaterialListPage }) => {
await admitMaterialListPage.open();
await admitMaterialListPage.expectCoreColumns();
assertAdmitHeaders(await admitMaterialListPage.coreHeaders(), [
'准入类型',
'准入单号',
'进院供应商',
'产品名称',
'注册证号',
'耗材类型',
'产品条数',
'状态',
'操作',
]);
await expect(admitMaterialListPage.rowAction(await admitMaterialListPage.firstRow(), '详情'), '详情').toBeVisible();
});
test('ADMIT-MLS-007 切换已通过 Tab', async ({ admitMaterialListPage }) => {
......
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,12 @@ test.describe('基础设置 · 准入管理', () => {
test.describe('物料准入管理 · 页面进入', () => {
test('ADMIT-MAT-001 菜单进入物料准入管理页', { tag: '@smoke' }, async ({ admitMaterialPage }) => {
await admitMaterialPage.openFromMenu();
await admitMaterialPage.expectEntered();
await admitMaterialPage.ready();
expect(admitMaterialPage.browserPage.url(), 'URL 应为物料准入').toContain('ZRlist');
for (const name of ['关联产品', '生成申请单', '一键生成', '作废']) {
await expect(admitMaterialPage.button(name).first(), name).toBeVisible();
}
await expect(admitMaterialPage.tab('待申请'), '待申请 Tab').toBeVisible();
});
});
......@@ -15,7 +21,7 @@ test.describe('基础设置 · 准入管理', () => {
test('ADMIT-MAT-002 默认查询列表有数据或空态明确', { tag: '@smoke' }, async ({ admitMaterialPage }) => {
await admitMaterialPage.open();
await admitMaterialPage.query();
await admitMaterialPage.expectListOrEmpty();
await assertAdmitListOrEmpty(await admitMaterialPage.snapshotList());
});
test('ADMIT-MAT-003 产品名称筛选', async ({ admitMaterialPage }) => {
......@@ -45,14 +51,27 @@ test.describe('基础设置 · 准入管理', () => {
test('ADMIT-MAT-008 无匹配结果空态(mock反例)', async ({ admitMaterialPage }) => {
await admitMaterialPage.open();
await admitMaterialPage.mockEmptyName();
await assertAdmitMockEmpty(await admitMaterialPage.mockEmptyName());
});
});
test.describe('物料准入管理 · 状态与列表', () => {
test('ADMIT-MAT-009 列表核心列完整', async ({ admitMaterialPage }) => {
await admitMaterialPage.open();
await admitMaterialPage.expectCoreColumns();
assertAdmitHeaders(await admitMaterialPage.coreHeaders(), [
'准入类型',
'产品编号',
'产品名称',
'规格型号',
'规格',
'型号',
'器械注册人',
'单位',
'耗材类型',
'默认进院供应商',
'操作',
]);
await expect(admitMaterialPage.rowAction(await admitMaterialPage.firstRow(), '详情'), '详情').toBeVisible();
});
test('ADMIT-MAT-010 切换待申请 Tab', async ({ admitMaterialPage }) => {
......
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,11 @@ test.describe('基础设置 · 准入管理', () => {
test.describe('产品准入申请 · 页面进入', () => {
test('ADMIT-PAP-001 菜单进入产品准入申请页', { tag: '@smoke' }, async ({ admitProductApplyPage }) => {
await admitProductApplyPage.openFromMenu();
await admitProductApplyPage.expectEntered();
await admitProductApplyPage.ready();
expect(admitProductApplyPage.browserPage.url(), '不应进入物料准入').not.toContain('ZRlist');
for (const name of ['新增产品', 'Excel导入', '生成申请单']) {
await expect(admitProductApplyPage.button(name).first(), name).toBeVisible();
}
});
});
......@@ -15,7 +20,7 @@ test.describe('基础设置 · 准入管理', () => {
test('ADMIT-PAP-002 默认查询列表有数据或空态明确', { tag: '@smoke' }, async ({ admitProductApplyPage }) => {
await admitProductApplyPage.open();
await admitProductApplyPage.query();
await admitProductApplyPage.expectListOrEmpty();
await assertAdmitListOrEmpty(await admitProductApplyPage.snapshotList());
});
test('ADMIT-PAP-003 产品名称筛选', async ({ admitProductApplyPage }) => {
......@@ -35,14 +40,22 @@ test.describe('基础设置 · 准入管理', () => {
test('ADMIT-PAP-006 无匹配结果空态(mock反例)', async ({ admitProductApplyPage }) => {
await admitProductApplyPage.open();
await admitProductApplyPage.mockEmpty();
await assertAdmitMockEmpty(await admitProductApplyPage.mockEmpty());
});
});
test.describe('产品准入申请 · 状态与列表', () => {
test('ADMIT-PAP-007 列表核心列完整', async ({ admitProductApplyPage }) => {
await admitProductApplyPage.open();
await admitProductApplyPage.expectCoreColumns();
assertAdmitHeaders(await admitProductApplyPage.coreHeaders(), [
'准入单号',
'产品编号',
'产品名称',
'规格型号',
'器械注册人',
'单位',
'操作',
]);
});
test('ADMIT-PAP-008 切换待申请 Tab', async ({ admitProductApplyPage }) => {
......
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-PLS-001 菜单进入产品准入单列表', { tag: '@smoke' }, async ({ admitProductListPage }) => {
await admitProductListPage.openFromMenu();
await admitProductListPage.expectEntered();
await admitProductListPage.ready();
expect(admitProductListPage.browserPage.url(), '不应进入物料准入单').not.toContain('zrlist');
await expect(admitProductListPage.tab('已通过'), '已通过 Tab').toBeVisible();
});
});
......@@ -15,7 +18,7 @@ test.describe('基础设置 · 准入管理', () => {
test('ADMIT-PLS-002 默认查询列表有数据或空态明确', { tag: '@smoke' }, async ({ admitProductListPage }) => {
await admitProductListPage.open();
await admitProductListPage.query();
await admitProductListPage.expectListOrEmpty();
await assertAdmitListOrEmpty(await admitProductListPage.snapshotList());
});
test('ADMIT-PLS-003 按产品名称筛选', async ({ admitProductListPage }) => {
......@@ -30,12 +33,23 @@ test.describe('基础设置 · 准入管理', () => {
test('ADMIT-PLS-005 无匹配结果空态(mock反例)', async ({ admitProductListPage }) => {
await admitProductListPage.open();
await admitProductListPage.mockEmpty();
await assertAdmitMockEmpty(await admitProductListPage.mockEmpty());
});
test('ADMIT-PLS-006 列表核心列与附件计数', async ({ admitProductListPage }) => {
await admitProductListPage.open();
await admitProductListPage.expectCoreColumns();
assertAdmitHeaders(await admitProductListPage.coreHeaders(), [
'准入单号',
'进院供应商',
'产品名称',
'注册证号',
'耗材类型',
'产品条数',
'状态',
'备注',
'附件',
'操作',
]);
});
test('ADMIT-PLS-007 切换已通过 Tab', async ({ admitProductListPage }) => {
......@@ -45,7 +59,8 @@ test.describe('基础设置 · 准入管理', () => {
test('ADMIT-PLS-008 与物料准入单列表不串 URL', async ({ admitProductListPage }) => {
await admitProductListPage.open();
await admitProductListPage.expectNotMaterialUrl();
expect(admitProductListPage.browserPage.url(), 'URL 应为产品准入单列表').toContain('productOrder/list');
expect(admitProductListPage.browserPage.url(), '不应串到物料准入单').not.toContain('zrlist');
});
test('ADMIT-PLS-014 多条件组合查询(制单日期等6项)', async ({ admitProductListPage }) => {
await admitProductListPage.open();
......
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