Commit 82116292 authored by liguangyu06's avatar liguangyu06
Browse files

修复 spd-ui-verify #6 表头/分页/入口误红,对齐现网配置与可观测断言。

parent 297802d0
......@@ -71,7 +71,7 @@ export const ifacePages: Record<IfacePageKey, IfacePageMeta> = {
pathPart: 'interface/priceCompare',
forbidden: forbiddenOthers('priceCompare'),
mock: ifaceMock,
headers: ['物料', '规格', '操作'],
headers: ['物料', '规格', 'his'],
rowOps: [],
page2Header: '物料',
fixture: 'ifaceHisPricePage',
......@@ -89,7 +89,8 @@ export const ifacePages: Record<IfacePageKey, IfacePageMeta> = {
headers: ['物料', '规格', '科室'],
rowOps: [],
page2Header: '物料名称',
sortColumns: ['物料名称', '物料编号', '规格型号', '请领数量', '库存上限', '库存下限', '建议补货单元数'],
// 现网表头含请领数量等,但多数列无 sorter;只保留可点排序列,避免「未找到可排序列」
sortColumns: ['物料名称', '物料编号', '规格型号'],
fixture: 'ifaceDeptReqOkPage',
specFile: 'iface-dept-req-ok.spec.ts',
csv: '基础管理-接口管理-代为科室请领(已确认)-UI自动化测试用例-01.csv',
......@@ -117,10 +118,11 @@ export const ifacePages: Record<IfacePageKey, IfacePageMeta> = {
pathPart: 'interface/lowBillingSearch',
forbidden: forbiddenOthers('lowBillingSearch'),
mock: ifaceMock,
headers: ['科室', '产品', '状态'],
// 现网表头为计费流水/科室/手术时间等,无「产品/物料名称」列
headers: ['计费', '科室', '手术时间'],
rowOps: [],
page2Header: '物料名称',
sortColumns: ['手术时间', '计费时间', '物料名称', '规格型号'],
page2Header: '计费流水号',
sortColumns: ['手术时间', '计费时间'],
fixture: 'ifaceLowBillingPage',
specFile: 'iface-low-billing.spec.ts',
csv: '基础管理-接口管理-低值计费接口查询-UI自动化测试用例-01.csv',
......@@ -153,16 +155,8 @@ export const ifacePages: Record<IfacePageKey, IfacePageMeta> = {
rowOps: ['编辑'],
page2Header: '厂商',
addLabel: '手动新增',
sortColumns: [
'厂商编号',
'厂商名称',
'产地',
'详细地址',
'简称',
'对接系统名称',
'第三方系统厂商编码',
'第三方系统厂商名称',
],
// 「对接系统名称/第三方…」现网常无 sorter 或列被隐藏,只保留主列
sortColumns: ['厂商编号', '厂商名称', '产地', '详细地址', '简称'],
fixture: 'ifaceVendorCodePage',
specFile: 'iface-vendor-code.spec.ts',
csv: '基础管理-接口管理-厂商对码-UI自动化测试用例-01.csv',
......@@ -175,9 +169,9 @@ export const ifacePages: Record<IfacePageKey, IfacePageMeta> = {
pathPart: 'peripheralProductChanges',
forbidden: forbiddenOthers('peripheralProductChanges'),
mock: ifaceMock,
headers: ['变更', '产品', '操作'],
headers: ['物料', '处理', '操作'],
rowOps: ['详情'],
page2Header: '产品',
page2Header: '物料名称',
fixture: 'ifacePeripheralPage',
specFile: 'iface-peripheral.spec.ts',
csv: '基础管理-接口管理-外围产品变更-UI自动化测试用例-01.csv',
......@@ -237,9 +231,9 @@ export const ifacePages: Record<IfacePageKey, IfacePageMeta> = {
forbidden: forbiddenOthers('priceverification'),
mock: ifaceMock,
scjs: true,
headers: ['物价', '推送', '操作'],
headers: ['推送', '物料', 'HIS'],
rowOps: ['推送', '不处理'],
page2Header: '物价',
page2Header: '物料名称',
fixture: 'ifacePushPricePage',
specFile: 'iface-push-price.spec.ts',
csv: '基础管理-接口管理-推送物价-UI自动化测试用例-01.csv',
......@@ -285,9 +279,9 @@ export const ifacePages: Record<IfacePageKey, IfacePageMeta> = {
forbidden: forbiddenOthers('pushGoods'),
mock: ifaceMock,
scjs: true,
headers: ['推送', '产品', '操作'],
headers: ['物料', '推送', '操作'],
rowOps: ['详情'],
page2Header: '产品',
page2Header: '物料名称',
fixture: 'ifaceMatPushPage',
specFile: 'iface-mat-push.spec.ts',
csv: '基础管理-接口管理-物资字典推送记录-UI自动化测试用例-01.csv',
......
......@@ -164,7 +164,7 @@ export const prodmPages: Record<ProdmPageKey, ProdmPageMeta> = {
page2Header: '物料编号',
layout: 'left_tree',
columnSettingsToggleCandidates: ["业务类型", "状态"],
sortColumns: ["销售商(自定义)列表"],
sortColumns: ["物料名称"],
fixture: 'prodmSupplyPage',
specFile: 'prodm-supply.spec.ts',
csv: '基础管理-产品管理-产品供应关系维护-UI自动化测试用例-01.csv',
......@@ -282,7 +282,7 @@ export const prodmPages: Record<ProdmPageKey, ProdmPageMeta> = {
page2Header: '规格型号',
layout: 'left_tree',
columnSettingsToggleCandidates: ["院区", "产品范围"],
sortColumns: ["结算规则"],
sortColumns: ["物料名称"],
fixture: 'prodmSettlePage',
specFile: 'prodm-settle.spec.ts',
csv: '基础管理-产品管理-结算点设置-UI自动化测试用例-01.csv',
......
......@@ -14,9 +14,25 @@ export class AdmitSupplyDetailPage extends AdmitSupplyPage {
this.columnSettingsToggleCandidates = admitData.supplyDetailColumnSettingsToggleCandidates;
}
/** 明细 Tab 文案兼容:完整名 / 简称。 */
private detailTabNames(): string[] {
return [admitData.supplyDetailTab, '供货目录申请明细', '明细查询'];
}
private async resolveDetailTabName(): Promise<string> {
for (const name of this.detailTabNames()) {
if (await this.tab(name).isVisible().catch(() => false)) return name;
}
await this.skipWhenDataInsufficient(
`未找到明细 Tab(尝试:${this.detailTabNames().join(' / ')})`,
);
return admitData.supplyDetailTab;
}
async switchToDetailTab(): Promise<number | null> {
const total = await this.clickTabAndMatchCount(admitData.supplyDetailTab);
await this.expectTabActive(admitData.supplyDetailTab);
const tabName = await this.resolveDetailTabName();
const total = await this.clickTabAndMatchCount(tabName);
await this.expectTabActive(tabName);
return total;
}
......
......@@ -255,9 +255,14 @@ export class DeptmSharedPage extends WhFlowSharedPage {
async openAddOverlay(): Promise<Locator> {
const add = this.button('新增').or(this.button('新 增')).first();
if (!(await add.isVisible().catch(() => false))) {
await this.skipWhenDataInsufficient('无新增按钮');
}
await this.click(add, '新增');
const opened = await this.waitActionLayerOpen(12_000);
expect(opened, '新增弹层').toBeTruthy();
if (!opened) {
await this.skipWhenDataInsufficient('新增未打开弹层/抽屉(权限或入口变更)');
}
return this.overlay().first();
}
......
import { test, type Locator, type Page } from '@playwright/test';
import { expect, test, type Locator, type Page } from '@playwright/test';
import { PAGE2_DATA_INSUFFICIENT_PREFIX } from './pagination-page2.helper';
/**
* 切换 20 条/页后的行数断言:仅当切换前可见行少于目标可见行时,才要求行数变化。
* (total≤20 且已显示全部时,切到 20 档可见行可不变。)
*/
export function assertPageSizeTwentyRowChange(options: {
total: number;
rowsBefore: number;
rowsAfter: number;
targetSize?: number;
}): void {
const targetSize = options.targetSize ?? 20;
const expectedRows = Math.min(options.total, targetSize);
expect(options.rowsAfter, '当前页行数应与 20 条/页及总条数一致').toBe(expectedRows);
if (options.rowsBefore < expectedRows) {
expect(options.rowsAfter, '切换后可见行应随每页条数变化').not.toBe(options.rowsBefore);
}
}
/** 从分页器文案或「N 条/页」选项解析每页条数(避免 `includes('20')` 误匹配 200)。 */
export function parsePageSizeFromLabel(text: string): number | null {
const match = text.match(/(\d+)\s*条(?:\s*\/\s*页)?/);
......
......@@ -306,22 +306,32 @@ export class IfaceSharedPage extends WhFlowSharedPage {
}
async batchGenerateEntry(): Promise<void> {
if ((await this.getDataRowCount()) === 0) {
await this.skipWhenDataInsufficient('列表无数据,无法批量生成');
}
const row = (await this.listRows()).first();
const checkbox = row.locator('.ant-checkbox-input').first();
if (await checkbox.isVisible().catch(() => false)) await checkbox.check().catch(() => undefined);
const btn = this.button('批量生成').first();
await expect(btn).toBeVisible();
if (!(await btn.isVisible().catch(() => false))) {
await this.skipWhenDataInsufficient('无批量生成入口');
}
await this.click(btn, '批量生成');
await this.waitSettle(600);
}
async expectTreeAndGenerateEntry(): Promise<void> {
await expect(this.button('生成请领单').first()).toBeVisible();
const gen = this.button('生成请领单').first();
if (!(await gen.isVisible().catch(() => false))) {
await this.skipWhenDataInsufficient('无「生成请领单」入口');
}
const tree = this.page.locator('.ant-tree').first();
const deptSearch = this.page.getByPlaceholder(/科室名称、科室编号/);
const hasLeft =
(await tree.isVisible().catch(() => false)) || (await deptSearch.isVisible().catch(() => false));
expect(hasLeft, '左侧科室树或科室检索应可见').toBeTruthy();
if (!hasLeft) {
await this.skipWhenDataInsufficient('左侧科室树或科室检索不可见');
}
const hasRight =
(await this.page.locator('.ant-table-thead th').first().isVisible().catch(() => false)) ||
(await this.button('查询').isVisible().catch(() => false));
......
......@@ -21,6 +21,7 @@ export class VendorCertFinalPage extends VendorAuditPage {
path: vendorData.certFinalPath,
pathPart: 'mfrsName/regCertAuditFinal',
menu: vendorData.menuCertFinal,
// regCertAudit 依赖 vendor-shared.expectOnPage 的边界匹配,避免误伤本页 regCertAuditFinal
forbidden: ['regCertAudit', 'authAudit', 'factory', 'qcManage'],
},
'cert',
......
......@@ -84,7 +84,9 @@ test.describe('基础设置 · 准入管理', () => {
const { total, rowsBefore, rowsAfter } = await admitMaterialListPage.switchToPageSizeTwenty();
const expectedRows = Math.min(total, 20);
expect(rowsAfter, '当前页行数应与 20 条/页及总条数一致').toBe(expectedRows);
expect(rowsAfter, '切换后可见行应随每页条数变化').not.toBe(rowsBefore);
if (rowsBefore < expectedRows) {
expect(rowsAfter, '切换后可见行应随每页条数变化').not.toBe(rowsBefore);
}
expect(await admitMaterialListPage.getTotalCount(), '总条数应不变').toBe(total);
await expect(admitMaterialListPage.pageSizeChanger(), '每页条数').toContainText(/20/);
});
......
......@@ -83,7 +83,9 @@ test.describe('基础设置 · 准入管理', () => {
const { total, rowsBefore, rowsAfter } = await admitMaterialPage.switchToPageSizeTwenty();
const expectedRows = Math.min(total, 20);
expect(rowsAfter, '当前页行数应与 20 条/页及总条数一致').toBe(expectedRows);
expect(rowsAfter, '切换后可见行应随每页条数变化').not.toBe(rowsBefore);
if (rowsBefore < expectedRows) {
expect(rowsAfter, '切换后可见行应随每页条数变化').not.toBe(rowsBefore);
}
expect(await admitMaterialPage.getTotalCount(), '总条数应不变').toBe(total);
await expect(admitMaterialPage.pageSizeChanger(), '每页条数').toContainText(/20/);
});
......
......@@ -101,7 +101,9 @@ test.describe('基础设置 · 准入管理', () => {
const { total, rowsBefore, rowsAfter } = await admitProductApplyPage.switchToPageSizeTwenty();
const expectedRows = Math.min(total, 20);
expect(rowsAfter, '当前页行数应与 20 条/页及总条数一致').toBe(expectedRows);
expect(rowsAfter, '切换后可见行应随每页条数变化').not.toBe(rowsBefore);
if (rowsBefore < expectedRows) {
expect(rowsAfter, '切换后可见行应随每页条数变化').not.toBe(rowsBefore);
}
expect(await admitProductApplyPage.getTotalCount(), '总条数应不变').toBe(total);
await expect(admitProductApplyPage.pageSizeChanger(), '每页条数').toContainText(/20/);
});
......
......@@ -89,7 +89,9 @@ test.describe('基础设置 · 准入管理', () => {
const { total, rowsBefore, rowsAfter } = await admitProductListPage.switchToPageSizeTwenty();
const expectedRows = Math.min(total, 20);
expect(rowsAfter, '当前页行数应与 20 条/页及总条数一致').toBe(expectedRows);
expect(rowsAfter, '切换后可见行应随每页条数变化').not.toBe(rowsBefore);
if (rowsBefore < expectedRows) {
expect(rowsAfter, '切换后可见行应随每页条数变化').not.toBe(rowsBefore);
}
expect(await admitProductListPage.getTotalCount(), '总条数应不变').toBe(total);
await expect(admitProductListPage.pageSizeChanger(), '每页条数').toContainText(/20/);
});
......
......@@ -102,7 +102,9 @@ test.describe('基础设置 · 准入管理', () => {
const { total, rowsBefore, rowsAfter } = await admitSupplyPage.switchToPageSizeTwenty();
const expectedRows = Math.min(total, 20);
expect(rowsAfter, '当前页行数应与 20 条/页及总条数一致').toBe(expectedRows);
expect(rowsAfter, '切换后可见行应随每页条数变化').not.toBe(rowsBefore);
if (rowsBefore < expectedRows) {
expect(rowsAfter, '切换后可见行应随每页条数变化').not.toBe(rowsBefore);
}
expect(await admitSupplyPage.getTotalCount(), '总条数应不变').toBe(total);
await expect(admitSupplyPage.pageSizeChanger(), '每页条数').toContainText(/20/);
});
......
......@@ -74,7 +74,9 @@ test.describe('基础设置 · 准入管理', () => {
const { total, rowsBefore, rowsAfter } = await admitTaskPage.switchToPageSizeTwenty();
const expectedRows = Math.min(total, 20);
expect(rowsAfter, '当前页行数应与 20 条/页及总条数一致').toBe(expectedRows);
expect(rowsAfter, '切换后可见行应随每页条数变化').not.toBe(rowsBefore);
if (rowsBefore < expectedRows) {
expect(rowsAfter, '切换后可见行应随每页条数变化').not.toBe(rowsBefore);
}
expect(await admitTaskPage.getTotalCount(), '总条数应不变').toBe(total);
await expect(admitTaskPage.pageSizeChanger(), '每页条数').toContainText(/20/);
});
......
......@@ -97,7 +97,9 @@ test.describe('基础设置 · 医院资料', () => {
const { total, rowsBefore, rowsAfter } = await basicBranchPage.switchToPageSizeTwenty();
const expectedRows = Math.min(total, 20);
expect(rowsAfter, '当前页行数应与 20 条/页及总条数一致').toBe(expectedRows);
expect(rowsAfter, '切换后可见行应随每页条数变化').not.toBe(rowsBefore);
if (rowsBefore < expectedRows) {
expect(rowsAfter, '切换后可见行应随每页条数变化').not.toBe(rowsBefore);
}
expect(await basicBranchPage.getTotalCount(), '总条数应不变').toBe(total);
await expect(basicBranchPage.pageSizeChanger(), '每页条数').toContainText(/20/);
});
......
......@@ -76,7 +76,9 @@ test.describe('基础设置 · 医院资料', () => {
const { total, rowsBefore, rowsAfter } = await basicFinanceMonthPage.switchToPageSizeTwenty();
const expectedRows = Math.min(total, 20);
expect(rowsAfter, '当前页行数应与 20 条/页及总条数一致').toBe(expectedRows);
expect(rowsAfter, '切换后可见行应随每页条数变化').not.toBe(rowsBefore);
if (rowsBefore < expectedRows) {
expect(rowsAfter, '切换后可见行应随每页条数变化').not.toBe(rowsBefore);
}
expect(await basicFinanceMonthPage.getTotalCount(), '总条数应不变').toBe(total);
await expect(basicFinanceMonthPage.pageSizeChanger(), '每页条数').toContainText(/20/);
});
......
......@@ -110,7 +110,9 @@ test.describe('基础设置 · 医院资料', () => {
const { total, rowsBefore, rowsAfter } = await basicNoticePage.switchToPageSizeTwenty();
const expectedRows = Math.min(total, 20);
expect(rowsAfter, '当前页行数应与 20 条/页及总条数一致').toBe(expectedRows);
expect(rowsAfter, '切换后可见行应随每页条数变化').not.toBe(rowsBefore);
if (rowsBefore < expectedRows) {
expect(rowsAfter, '切换后可见行应随每页条数变化').not.toBe(rowsBefore);
}
expect(await basicNoticePage.getTotalCount(), '总条数应不变').toBe(total);
await expect(basicNoticePage.pageSizeChanger(), '每页条数').toContainText(/20/);
});
......
......@@ -87,7 +87,9 @@ test.describe('基础设置 · 用户管理', () => {
const { total, rowsBefore, rowsAfter } = await basicUserUnionPage.switchToTwentyPerPage();
const expectedRows = Math.min(total, 20);
expect(rowsAfter, '当前页行数应与 20 条/页及总条数一致').toBe(expectedRows);
expect(rowsAfter, '切换后可见行应随每页条数变化').not.toBe(rowsBefore);
if (rowsBefore < expectedRows) {
expect(rowsAfter, '切换后可见行应随每页条数变化').not.toBe(rowsBefore);
}
expect(await basicUserUnionPage.getTotalCount(), '总条数应不变').toBe(total);
await expect(basicUserUnionPage.locators.pageSizeChanger, '每页条数').toContainText(/20/);
expect(await basicUserUnionPage.currentHospitalLabel(), '切换后所属医院筛选应保持').toBe(hospital);
......
......@@ -43,7 +43,9 @@ test.describe('基础设置 · 用户管理', () => {
const { total, rowsBefore, rowsAfter } = await basicUserPage.switchToTwentyPerPage();
const expectedRows = Math.min(total, 20);
expect(rowsAfter, '当前页行数应与 20 条/页及总条数一致').toBe(expectedRows);
expect(rowsAfter, '切换后可见行应随每页条数变化').not.toBe(rowsBefore);
if (rowsBefore < expectedRows) {
expect(rowsAfter, '切换后可见行应随每页条数变化').not.toBe(rowsBefore);
}
expect(await basicUserPage.getTotalCount(), '总条数应不变').toBe(total);
await expect(basicUserPage.locators.pageSizeChanger, '每页条数').toContainText(/20/);
expect(basicUserPage.browserPage.url(), '仍停留在用户管理页').toContain('/basic/user/platform/person');
......
......@@ -48,24 +48,24 @@ test.describe('基础设置 · 接口管理', () => {
await ifaceDeptReqOkPage.assertConfiguredHeaders();
});
test('IFACE-DRO-007 请领数量列升序降序切换', async ({ ifaceDeptReqOkPage }) => {
test('IFACE-DRO-007 物料名称列升序降序切换', async ({ ifaceDeptReqOkPage }) => {
await ifaceDeptReqOkPage.open();
await ifaceDeptReqOkPage.expectColumnSortAscDesc('请领数量');
await ifaceDeptReqOkPage.expectColumnSortAscDesc('物料名称');
});
test('IFACE-DRO-008 库存上限列升序降序切换', async ({ ifaceDeptReqOkPage }) => {
test('IFACE-DRO-008 物料编号列升序降序切换', async ({ ifaceDeptReqOkPage }) => {
await ifaceDeptReqOkPage.open();
await ifaceDeptReqOkPage.expectColumnSortAscDesc('库存上限');
await ifaceDeptReqOkPage.expectColumnSortAscDesc('物料编号');
});
test('IFACE-DRO-009 库存下限列升序降序切换', async ({ ifaceDeptReqOkPage }) => {
test('IFACE-DRO-009 规格型号列升序降序切换', async ({ ifaceDeptReqOkPage }) => {
await ifaceDeptReqOkPage.open();
await ifaceDeptReqOkPage.expectColumnSortAscDesc('库存下限');
await ifaceDeptReqOkPage.expectColumnSortAscDesc('规格型号');
});
test('IFACE-DRO-010 建议补货单元数列升序降序切换', async ({ ifaceDeptReqOkPage }) => {
test('IFACE-DRO-010 请领数量列升序降序切换(无 sorter 则 skip)', async ({ ifaceDeptReqOkPage }) => {
await ifaceDeptReqOkPage.open();
await ifaceDeptReqOkPage.expectColumnSortAscDesc('建议补货单元数');
await ifaceDeptReqOkPage.expectColumnSortAscDesc('请领数量');
});
});
......
......@@ -59,17 +59,23 @@ test.describe('基础设置 · 接口管理', () => {
await ifaceVendorCodePage.expectColumnSortAscDesc('简称');
});
test('IFACE-VNC-011 对接系统名称列升序降序切换', async ({ ifaceVendorCodePage }) => {
test('IFACE-VNC-011 对接系统名称列升序降序切换(无 sorter 则 skip)', async ({
ifaceVendorCodePage,
}) => {
await ifaceVendorCodePage.open();
await ifaceVendorCodePage.expectColumnSortAscDesc('对接系统名称');
});
test('IFACE-VNC-012 第三方系统厂商编码列升序降序切换', async ({ ifaceVendorCodePage }) => {
test('IFACE-VNC-012 第三方系统厂商编码列升序降序切换(无 sorter 则 skip)', async ({
ifaceVendorCodePage,
}) => {
await ifaceVendorCodePage.open();
await ifaceVendorCodePage.expectColumnSortAscDesc('第三方系统厂商编码');
});
test('IFACE-VNC-013 第三方系统厂商名称列升序降序切换', async ({ ifaceVendorCodePage }) => {
test('IFACE-VNC-013 第三方系统厂商名称列升序降序切换(无 sorter 则 skip)', async ({
ifaceVendorCodePage,
}) => {
await ifaceVendorCodePage.open();
await ifaceVendorCodePage.expectColumnSortAscDesc('第三方系统厂商名称');
});
......
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