Commit 1a4c1b8b authored by liguangyu06's avatar liguangyu06
Browse files

从 temp 选择性同步框架与用例到 main

合入自动化代码、用例 CSV/MD/spec/PO 及登录态修复;不含 knowledge-base、.cursor、data/scan 等 temp 全量备份内容。
parent f6eec849
/** 基础管理-流程设置:路径、菜单、CSV 反例 mock。正例登录走 auth.setup(test_1)。 */
export const flowData = {
defPath: '/spd/basic/flowSet/workflow/flowSet',
instPath: '/spd/basic/flowSet/workflow/flowCase',
menuBasic: '基础设置',
menuGroup: '流程设置',
menuDef: '审批流程设置',
menuInst: '审批流实例',
mockFlow: '__NOT_EXIST_FLOW__99999',
prefixType: {
PLAN: '采购计划',
DB: '二级库调拨',
GC: '产品信息变更',
BUY: '请领单',
} as const,
} as const;
/**
* 基础管理-手术套包设置:路径、菜单、URL 片段与 CSV 反例 mock。
* 正例登录走 auth.setup 写入的 storageState,用例内不重复登录、不写明文凭据。
* 严格对齐 testcase/基础管理-手术套包设置-*-UI自动化测试用例-01.csv。
*/
export const surgeryData = {
basePkgPath: '/spd/basic/operationSetting/baseMaintenance',
operTypePath: '/spd/basic/operationSetting/typeMaintenance',
stdPkgPath: '/spd/basic/operationSetting/standardMaintenance',
basePkgPathPart: '/spd/basic/operationSetting/baseMaintenance',
operTypePathPart: '/spd/basic/operationSetting/typeMaintenance',
stdPkgPathPart: '/spd/basic/operationSetting/standardMaintenance',
menuBasic: '基础设置',
menuSurgery: '手术套包设置',
menuBasePkg: '基础包维护',
menuOperType: '术种维护',
menuStdPkg: '标准套包维护',
// CSV 反例 mock(不依赖真实业务数据)
mockBaseKeyword: '__NOT_EXIST_BASE__99999',
mockOperKeyword: '__NOT_EXIST_OPER__99999',
mockStdKeyword: '__NOT_EXIST_STD__99999',
/** 术种维护状态 Tab(CSV SURG-TYPE-011/024/025) */
statusAll: '全部',
statusDisabled: '停用',
statusUnclassified: '未建分类',
} as const;
/** 唯一名称/编码,避免用例间数据污染(CSV 前置 ${uniqPkgName} 等)。 */
export function uniqStamp(prefix: string): string {
return `${prefix}-${Date.now()}-${Math.floor(Math.random() * 1000)}`;
}
export function uniqBasePkgName(): string {
return uniqStamp('AUTO-BASE');
}
export function uniqBasePkgCode(): string {
return uniqStamp('AB');
}
export function uniqOperName(): string {
return uniqStamp('AUTO-OPER');
}
export function uniqStdPkgName(): string {
return uniqStamp('AUTO-STD');
}
export function uniqCategoryName(): string {
return uniqStamp('AUTO-CATE');
}
/** 基础管理-用户管理:路径、菜单、CSV 反例 mock。正例登录走 auth.setup(test_1)。 */
export const userData = {
userPath: '/spd/basic/user/platform/person',
unionPath: '/spd/basic/user/platform/personCom',
rolePath: '/spd/basic/user/platform/role',
menuBasic: '基础设置',
menuUserGroup: '用户管理',
menuUser: '用户管理',
menuUnion: '医共体用户管理',
menuRole: '角色管理',
positiveUser: 'test_1',
mockUser: '__NOT_EXIST_USER__99999',
mockRole: '__NOT_EXIST_ROLE__99999',
/** 列设置 toggle 用非核心列(须当前表头可见)。 */
columnSettingsToggleCandidates: ['联系方式', '医院员工编号', '科室任职数'],
} as const;
export function uniqLogin(): string {
return `auto.usr.${Date.now()}`;
}
/** 基础管理-厂商管理:路径、菜单、CSV 反例 mock。正例登录走 auth.setup(test_1)。 */
export const vendorData = {
queryPath: '/spd/basic/mfrs/paltform/mfrsName/factory',
qualPath: '/spd/basic/mfrs/basic/qcManage',
authFirstPath: '/spd/basic/mfrs/paltform/mfrsName/authAudit',
authFinalPath: '/spd/basic/mfrs/paltform/mfrsName/authAuditFinal',
certFirstPath: '/spd/basic/mfrs/paltform/mfrsName/regCertAudit',
certFinalPath: '/spd/basic/mfrs/paltform/mfrsName/regCertAuditFinal',
certDiffPath: '/spd/basic/mfrs/scjs/yc/certCompare',
evalSchemePath: '/spd/basic/mfrs/paltform/mfrsName/evaluationPlan',
evalIndicatorPath: '/spd/basic/mfrs/paltform/mfrsName/indicators',
holidayPath: '/spd/basic/mfrs/basic/prov/holiday',
menuBasic: '基础设置',
menuVendor: '厂商管理',
menuQuery: '厂商查询',
menuQual: '供应商资质管理',
mockMfrsName: '__NOT_EXIST_MFRS__99999',
mockSupplier: '__NOT_EXIST_SUPPLIER__99999',
mockOrder: '__NOT_EXIST_ORDER__99999',
menuAuthFirst: '授权变更初审',
menuAuthFinal: '授权变更终审',
menuCertFirst: '注册证变更初审',
menuCertFinal: '注册证变更终审',
menuCertDiff: '注册证差异对比',
menuEvalScheme: '评价方案维护',
menuEvalIndicator: '评价指标维护',
menuHoliday: '供应商节假日管理',
} as const;
/** 基础管理-库房管理:路径、菜单、CSV 反例 mock。正例登录走 auth.setup(test_1)。 */
export const warehouseData = {
zonePath: '/spd/basic/storehouse/platform/storehouse/area',
probePath: '/spd/basic/storehouse/storehouse/probe',
menuBasic: '基础设置',
menuGroup: '库房管理',
menuZone: '库区设置',
menuProbe: '探针绑定',
mockZone: '__NOT_EXIST_ZONE__99999',
mockProbe: '__NOT_EXIST_PROBE__99999',
} as const;
......@@ -27,8 +27,9 @@
1. 跑 CI,报告写入 `reports/flake/history.jsonl`(gitignore)
2. `npm run flake:report` → `reports/flake/latest.md`
3. 同一用例**有过有挂**才算 flake;失败率 ≥ 0.3 且有效次数 ≥ 4 列为隔离候选
4. 只把 **transient / environment** 候选写入 `config/quarantine.json`:
3. **`npm run quarantine:sync`** → 把候选合并进 `config/quarantine.json`(预览);确认后 `npm run quarantine:apply` 落库
4. 同一用例**有过有挂**才算 flake;失败率 ≥ 0.3 且有效次数 ≥ 4 列为隔离候选
5. 只把 **transient / environment / script / data** 候选写入 `config/quarantine.json`:
```json
{
......@@ -41,12 +42,13 @@
}
```
- **自动同步(`scripts/sync-quarantine.mjs`)**:只隔离 `environment / transient / script / data`;`product`(断言/业务缺陷)**绝不自动隔离**;标题无 `STAT-` 编号的候选跳过;已有人工条目(带 ticket/owner)**保留且不被覆盖**;自动项带 `auto: true`,可重复执行(幂等)。
- `quarantine`:Jenkins / `CI=1` 默认 skip;本地仍跑
- `observe`:照常执行,只打标
- `npm run test:quarantine`:只跑清单项,验证是否还抖
- `QUARANTINE_POLICY=never`:主门禁也不 skip
出清单条件:根因已记、有 owner、不是断言失败。修稳后删条目。
出清单条件:根因已记、有 owner、不是断言失败。修稳后删条目(自动项重新同步会自动回收)。
## 自愈定位
......@@ -56,12 +58,70 @@
| 手段 | 默认 | 断言失败 |
|------|------|----------|
| `ACTION_RETRIES`(操作层) | 3 | 不重试 |
| `ACTION_RETRIES`(操作层,读操作) | 3 | 不重试 |
| 写操作(`{ operation: 'write' }`) | **1(不重试)** | 不重试 |
| `ACTION_RETRY_BUDGET_MS`(单次操作总预算) | 45_000 | — |
| `PW_RETRIES`(整条用例) | 0 | 会重试,不要开 |
| `testFlaky` / `PW_FLAKY_RETRIES` | 按需 1 | 不重试 |
### 写操作幂等安全
导出、提交、删除、审批、打印等**会产生副作用**的点击,重试前无法确认服务端是否已执行,盲目重试会重复触发。
因此 `BasePage.click` / `fill` 支持 `{ operation: 'write' }`:默认只尝试 1 次,且主定位失败后**不再尝试 fallback**。
```ts
// 查询/打开面板等读操作:默认走 3 次重试
await this.click(queryBtn, '点击查询');
// 导出/打印/提交等写操作:只点一次,不重试、不 fallback
await this.click(exportBtn, '点击导出', { operation: 'write' });
// 业务确认重复执行安全(如按单据号覆盖式保存)才显式声明幂等
await this.click(saveBtn, '保存', { operation: 'write', idempotent: true });
```
### 超时总预算
`retryAction` 的 `deadlineMs`(默认 `ACTION_RETRY_BUDGET_MS=45_000`)限制**单次操作**的总耗时,含首次尝试与退避等待;到点即停并抛出最后一次错误。
避免 `attempts × 单次超时` 在慢页面下叠成几十秒慢失败。
```ts
await retryAction(() => action(), { label: '慢接口', deadlineMs: 30_000 });
```
## 失败频次统计(经常失败的用例)
目标:回答「**哪些用例经常挂**」,而不是只看单次红绿。区别于 flake(有过有挂),此处**只累计失败**,跨构建持久化。
原理:
1. 每条用例 `afterEach` 按 `STAT-` 编号累加 `runs/passed/failed/consecutiveFails`,写入 `reports/flake/failure-counts.json`(跨构建累计,重启不清零)。
2. 失败次数 ≥ `FREQ_FAIL_MIN`(默认 2)**且** 失败率 ≥ `FREQ_FAIL_RATE`(默认 0.2)判定为「经常失败」。
3. 给用例追加 annotation,报告可检索、可筛:
| annotation | 含义 |
|------------|------|
| `failureRuns` | `失败/执行`,如 `4/5` |
| `failureRate` | 失败率,如 `0.80` |
| `consecutiveFails` | 连续失败次数(通过即归零) |
| `frequentFailure` | 达标标记(经常失败) |
4. 榜单与命令:
```bash
npm run failures:report # 表格:经常失败的用例 top30
node scripts/failure-report.mjs --json # 机器可读,供看板/流水线消费
node scripts/failure-report.mjs --min-fails=3 --rate=0.3 --top=50
```
- 累计与落盘在 worker 的 `afterEach`(`mergeAndSaveFailureStore` 多 worker 合并安全);`reporters/failure-trend.reporter.ts` 在主进程 `onEnd` 刷新榜单并在日志打印命中项。
- Jenkins 邮件摘要(`scripts/ci-email-summary.mjs`)自动追加「经常失败的用例」小节。
- 编号以外的用例(无 `STAT-`)不参与统计。
## 产物
- `reports/latest/flake/latest.md` — 趋势与候选
- `reports/latest/flake/run.json` — 当次分类计数
- `reports/latest/flake/history.jsonl` — 当次明细(并追加到 `reports/flake/history.jsonl`)
- `reports/flake/failure-counts.json` — 跨构建失败频次累计(经常失败判定依据)
- `reports/flake/frequent-failures.md` — 经常失败用例榜单(每次运行刷新)
......@@ -8,7 +8,7 @@
| 任务 | 套件 | 建议 cron | 说明 |
|------|------|-----------|------|
| `spd-ui-smoke` | `smoke` | `H 8-18 * * 1-5` | 工作时段冒烟:全部报表模块已打 `@smoke` 的用例 |
| `spd-ui-smoke` | `smoke` | `H 8-18 * * 1-5` | 工作时段冒烟:报表与基础设置中已打 `@smoke` 的用例(含厂商、准入、基础管理、手术、库房、流程) |
| `spd-ui-nightly` / `SPD` | `nightly` / `full` | `H 23 * * 1,3,5` | 周一、三、五 **23 点** 回归(Jenkins 任务 `SPD` 当前按此时段跑 full) |
| `spd-ui-full` | `full` | `H 3 * * 0` | 周末含全列排序,最慢 |
......@@ -155,6 +155,6 @@ HTML 报告链接只有 **能打开 Jenkins** 的人才能看。外网领导打
## Git 上线检查
- `.env`、`auth/*.json`、`.playwright-browsers/`、`reports/*`(除 README)已在 `.gitignore`
- 推送后 Jenkins 用凭据注入 `TEST_USER` / `TEST_PASS`
- 推送后 Jenkins 用凭据注入 `TEST_USER` / `TEST_PASS`;可选在任务环境变量增加 `TEST_USER_2` / `TEST_PASS_2`,worker 按序号在双账号间轮换
- GitLab:根目录 `.gitlab-ci.yml` 默认只跑 `typecheck`;冒烟需配置 Variables `TEST_USER` / `TEST_PASS`,在 Web/定时流水线手动触发
- GitHub Actions 默认跑 `smoke`,完整回归留给 Jenkins 夜间任务
import { test as base, expect, type TestDetails, type TestInfo } from '@playwright/test';
import { test as base, expect, type Route, type TestDetails, type TestInfo } from '@playwright/test';
import { retryAction } from '../core/action-retry';
import { applyCaseMeta } from '../core/case-meta';
......@@ -6,17 +6,42 @@ import { FLAKY_TAG, resolveFlakyRetries } from '../core/flaky';
import { annotateFailureClass, classifyFromTestInfo } from '../core/failure-classify';
import { applyQuarantine } from '../core/quarantine';
import { attachFailureDiagnostics } from '../core/diagnostics';
import { annotateFailureFrequencyForTest } from '../core/failure-tracker';
import {
attachRuntimeHealthGuard,
getRuntimeHealthGuard,
} from '../core/runtime-health.util';
import { failWhen, guardedSpecSkip } from '../pages/spd/helpers/guarded-skip.helper';
import { resolveAuthStorageStatePath } from '../utils/auth-pool.util';
import { buildPageFixtures, type AppPageFixtures } from './page-registry';
type AppFixtures = AppPageFixtures;
const BLOCKED_RESOURCE_TYPES = new Set(['image', 'media', 'font']);
const ANALYTICS_URL =
/(?:google-analytics|googletagmanager|hm\.baidu\.com|cnzz\.com|hotjar|sentry\.io|umeng\.com|growingio|sensorsdata|clarity\.ms)/i;
/** 拦截图片、字体、音视频和统计脚本。不拦截 document / xhr / fetch,报表查询仍走网络。 */
async function abortHeavyAssets(route: Route): Promise<void> {
const request = route.request();
if (BLOCKED_RESOURCE_TYPES.has(request.resourceType()) || ANALYTICS_URL.test(request.url())) {
await route.abort();
return;
}
await route.continue();
}
/**
* 业务 fixture:storageState 来自 auth.setup 写入的文件。
* 配置 TEST_USER_2/TEST_PASS_2 时按 parallelIndex 在双账号间轮换,减轻互踢会话。
* 每条用例仍新建独立 context + page;此处不持有跨用例 session。
*/
export const test = base.extend<AppFixtures>({
storageState: async ({}, use, workerInfo) => {
await use(resolveAuthStorageStatePath(workerInfo.parallelIndex));
},
page: async ({ page }, use) => {
await page.context().route('**/*', abortHeavyAssets);
attachRuntimeHealthGuard(page);
page.context().on('page', (popup) => {
attachRuntimeHealthGuard(popup);
......@@ -33,17 +58,27 @@ test.beforeEach(({}, testInfo) => {
test.afterEach(async ({ page }, testInfo) => {
const failed = testInfo.status !== testInfo.expectedStatus;
if (failed) {
if (page && !page.isClosed()) {
await attachFailureDiagnostics(page, testInfo, getRuntimeHealthGuard(page));
try {
if (failed) {
if (page && !page.isClosed()) {
await attachFailureDiagnostics(page, testInfo, getRuntimeHealthGuard(page));
} else {
annotateFailureClass(testInfo, classifyFromTestInfo(testInfo));
}
return;
}
annotateFailureClass(testInfo, classifyFromTestInfo(testInfo));
return;
}
const guard = page && !page.isClosed() ? getRuntimeHealthGuard(page) : undefined;
if (guard) {
await guard.assertClean(`用例收尾:${testInfo.title}`);
const guard = page && !page.isClosed() ? getRuntimeHealthGuard(page) : undefined;
if (guard) {
await guard.assertClean(`用例收尾:${testInfo.title}`);
}
} finally {
// 跨构建失败频次:reporter 负责累计落盘,这里基于最新统计打 annotation(只读),
// 保证诊断/健康检查异常也不影响打标;无编号用例自动跳过。
try {
annotateFailureFrequencyForTest(testInfo);
} catch {
// 统计不影响用例结果
}
}
});
......
......@@ -3,7 +3,37 @@ import type { Page } from '@playwright/test';
import { LoginPage } from '../pages/auth/login.page';
import { AnaChgPage } from '../pages/spd/ana-chg.page';
import { AnaDeptPage } from '../pages/spd/ana-dept.page';
import { AdmitMaterialListPage } from '../pages/spd/admit-material-list.page';
import { AdmitMaterialPage } from '../pages/spd/admit-material.page';
import { AdmitProductApplyPage } from '../pages/spd/admit-product-apply.page';
import { AdmitProductListPage } from '../pages/spd/admit-product-list.page';
import { AdmitSupplyPage } from '../pages/spd/admit-supply.page';
import { AdmitTaskPage } from '../pages/spd/admit-task.page';
import { AnaProvPage } from '../pages/spd/ana-prov.page';
import { BasicBranchPage } from '../pages/spd/basic-branch.page';
import { BasicFinanceMonthPage } from '../pages/spd/basic-finance-month.page';
import { BasicHospitalInfoPage } from '../pages/spd/basic-hospital-info.page';
import { BasicNoticePage } from '../pages/spd/basic-notice.page';
import { BasicRolePage } from '../pages/spd/basic-role.page';
import { BasicUserPage } from '../pages/spd/basic-user.page';
import { BasicUserUnionPage } from '../pages/spd/basic-user-union.page';
import { FlowDefPage } from '../pages/spd/flow-def.page';
import { FlowInstPage } from '../pages/spd/flow-inst.page';
import { WarehouseProbePage } from '../pages/spd/warehouse-probe.page';
import { WarehouseZonePage } from '../pages/spd/warehouse-zone.page';
import { SurgeryBasePkgPage } from '../pages/spd/surgery-base-pkg.page';
import { SurgeryOperTypePage } from '../pages/spd/surgery-oper-type.page';
import { SurgeryStdPkgPage } from '../pages/spd/surgery-std-pkg.page';
import { VendorAuthFinalPage } from '../pages/spd/vendor-auth-final.page';
import { VendorAuthFirstPage } from '../pages/spd/vendor-auth-first.page';
import { VendorCertDiffPage } from '../pages/spd/vendor-cert-diff.page';
import { VendorCertFinalPage } from '../pages/spd/vendor-cert-final.page';
import { VendorCertFirstPage } from '../pages/spd/vendor-cert-first.page';
import { VendorEvalIndicatorPage } from '../pages/spd/vendor-eval-indicator.page';
import { VendorEvalSchemePage } from '../pages/spd/vendor-eval-scheme.page';
import { VendorHolidayPage } from '../pages/spd/vendor-holiday.page';
import { VendorQualPage } from '../pages/spd/vendor-qual.page';
import { VendorQueryPage } from '../pages/spd/vendor-query.page';
import { CenterReportEntryPage } from '../pages/spd/center-report-entry.page';
import { CenterReportLmmPsiPage } from '../pages/spd/center-report-lmm-psi.page';
import { CenterReportLmmSheetPage } from '../pages/spd/center-report-lmm-sheet.page';
......@@ -120,6 +150,36 @@ export const PAGE_CTORS = {
anaChgPage: AnaChgPage,
anaDeptPage: AnaDeptPage,
anaProvPage: AnaProvPage,
admitMaterialPage: AdmitMaterialPage,
admitProductApplyPage: AdmitProductApplyPage,
admitMaterialListPage: AdmitMaterialListPage,
admitProductListPage: AdmitProductListPage,
admitSupplyPage: AdmitSupplyPage,
admitTaskPage: AdmitTaskPage,
basicNoticePage: BasicNoticePage,
basicHospitalInfoPage: BasicHospitalInfoPage,
basicFinanceMonthPage: BasicFinanceMonthPage,
basicBranchPage: BasicBranchPage,
basicUserPage: BasicUserPage,
basicUserUnionPage: BasicUserUnionPage,
basicRolePage: BasicRolePage,
warehouseZonePage: WarehouseZonePage,
warehouseProbePage: WarehouseProbePage,
flowDefPage: FlowDefPage,
flowInstPage: FlowInstPage,
surgeryBasePkgPage: SurgeryBasePkgPage,
surgeryOperTypePage: SurgeryOperTypePage,
surgeryStdPkgPage: SurgeryStdPkgPage,
vendorQueryPage: VendorQueryPage,
vendorQualPage: VendorQualPage,
vendorAuthFirstPage: VendorAuthFirstPage,
vendorAuthFinalPage: VendorAuthFinalPage,
vendorCertFirstPage: VendorCertFirstPage,
vendorCertFinalPage: VendorCertFinalPage,
vendorCertDiffPage: VendorCertDiffPage,
vendorEvalSchemePage: VendorEvalSchemePage,
vendorEvalIndicatorPage: VendorEvalIndicatorPage,
vendorHolidayPage: VendorHolidayPage,
reagEntryPage: ReagEntryPage,
reconDeptAccountPage: ReconDeptAccountPage,
reconDeptNetInPage: ReconDeptNetInPage,
......
# Auto-generated from admit-material-list-elements.json
# Source: http://spdtest.cmic.com.cn:8080/spd/basic/dataSync/dataSync/productOrder/zrlist
# Scanned: 2026-10-20T08:46:04.224Z
# Page: 基础设置 > 准入管理 > 物料准入单列表
# Note: filter dropdown was opened during scan
# Note: detail view was opened during scan
page: admit-material-list
module: spd-basic
url: http://spdtest.cmic.com.cn:8080/spd/basic/dataSync/dataSync/productOrder/zrlist
locators:
# --- main::main ---
menu_home:
description: "首页"
element_type: li
section: "main::main"
strategy: role
role: menuitem
name: "首页"
playwright: "page.getByRole('menuitem', { name: '首页' })"
menu_basicSetting:
description: "基础设置"
element_type: li
section: "main::main"
strategy: role
role: menuitem
name: "基础设置"
playwright: "page.getByRole('menuitem', { name: '基础设置' })"
menu_purchase:
description: "采购管理"
element_type: li
section: "main::main"
strategy: role
role: menuitem
name: "采购管理"
playwright: "page.getByRole('menuitem', { name: '采购管理' })"
menu_opening:
description: "期初管理"
element_type: li
section: "main::main"
strategy: role
role: menuitem
name: "期初管理"
playwright: "page.getByRole('menuitem', { name: '期初管理' })"
menu_centerStock:
description: "中心库管理"
element_type: li
section: "main::main"
strategy: role
role: menuitem
name: "中心库管理"
playwright: "page.getByRole('menuitem', { name: '中心库管理' })"
menu_deptBusiness:
description: "科室业务管理"
element_type: li
section: "main::main"
strategy: role
role: menuitem
name: "科室业务管理"
playwright: "page.getByRole('menuitem', { name: '科室业务管理' })"
menu_finance:
description: "财务管理"
element_type: li
section: "main::main"
strategy: role
role: menuitem
name: "财务管理"
playwright: "page.getByRole('menuitem', { name: '财务管理' })"
menu_orthoVolume:
description: "骨科带量"
element_type: li
section: "main::main"
strategy: role
role: menuitem
name: "骨科带量"
playwright: "page.getByRole('menuitem', { name: '骨科带量' })"
menu_general:
description: "综合管理"
element_type: li
section: "main::main"
strategy: role
role: menuitem
name: "综合管理"
playwright: "page.getByRole('menuitem', { name: '综合管理' })"
menu_yangcai:
description: "阳采管理"
element_type: li
section: "main::main"
strategy: role
role: menuitem
name: "阳采管理"
playwright: "page.getByRole('menuitem', { name: '阳采管理' })"
menu_processCenter:
description: "流程中心"
element_type: li
section: "main::main"
strategy: role
role: menuitem
name: "流程中心"
playwright: "page.getByRole('menuitem', { name: '流程中心' })"
menu_logistics:
description: "后勤物资管理"
element_type: li
section: "main::main"
strategy: role
role: menuitem
name: "后勤物资管理"
playwright: "page.getByRole('menuitem', { name: '后勤物资管理' })"
menu_statReport:
description: "统计报表"
element_type: li
section: "main::main"
strategy: role
role: menuitem
name: "统计报表"
playwright: "page.getByRole('menuitem', { name: '统计报表' })"
menu_dataViz:
description: "数据可视化"
element_type: li
section: "main::main"
strategy: role
role: menuitem
name: "数据可视化"
playwright: "page.getByRole('menuitem', { name: '数据可视化' })"
menu_admitMaterial:
description: "物料准入管理"
element_type: li
section: "main::main"
strategy: role
role: menuitem
name: "物料准入管理"
playwright: "page.getByRole('menuitem', { name: '物料准入管理' })"
menu_admitProductApply:
description: "产品准入申请"
element_type: li
section: "main::main"
strategy: role
role: menuitem
name: "产品准入申请"
playwright: "page.getByRole('menuitem', { name: '产品准入申请' })"
menu_admitMaterialList:
description: "物料准入单列表"
element_type: li
section: "main::main"
strategy: role
role: menuitem
name: "物料准入单列表"
playwright: "page.getByRole('menuitem', { name: '物料准入单列表' })"
menu_admitProductList:
description: "产品准入单列表"
element_type: li
section: "main::main"
strategy: role
role: menuitem
name: "产品准入单列表"
playwright: "page.getByRole('menuitem', { name: '产品准入单列表' })"
menu_admitSupply:
description: "供货目录申请"
element_type: li
section: "main::main"
strategy: role
role: menuitem
name: "供货目录申请"
playwright: "page.getByRole('menuitem', { name: '供货目录申请' })"
menu_admitTask:
description: "准入任务分配"
element_type: li
section: "main::main"
strategy: role
role: menuitem
name: "准入任务分配"
playwright: "page.getByRole('menuitem', { name: '准入任务分配' })"
admitMaterialList:
description: "物料准入单列表"
element_type: tab
section: "main::main"
strategy: text
value: "物料准入单列表"
playwright: "page.getByText('物料准入单列表')"
tab_admitMaterialList:
description: "物料准入单列表"
element_type: div
section: "main::main"
strategy: id
value: "rc-tabs-0-tab-/basic/dataSync/dataSync/productOrder/zrlist"
playwright: "page.locator('#rc-tabs-0-tab-/basic/dataSync/dataSync/productOrder/zrlist')"
operationDateDateRange:
description: "制单日期"
element_type: datepicker
section: "main::main"
strategy: label_text
value: "制单日期"
playwright: "page.getByText('制单日期', { exact: true })"
dateRange:
description: "开始日期"
element_type: text
section: "main::main"
strategy: id
value: "table_search_form_dateValue"
playwright: "page.locator('#table_search_form_dateValue')"
operationDateInput:
description: "结束日期"
element_type: text
section: "main::main"
strategy: placeholder
value: "结束日期"
playwright: "page.getByPlaceholder('结束日期')"
supplierName:
description: "请输入"
element_type: text
section: "main::main"
strategy: id
value: "table_search_form_provName"
playwright: "page.locator('#table_search_form_provName')"
consumableTypeSelect:
description: "请选择耗材类型"
element_type: select
section: "main::main"
strategy: label_text
value: "耗材类型"
playwright: "page.getByText('耗材类型', { exact: true })"
searchBtn:
description: "查 询"
element_type: button
section: "main::main"
strategy: role
role: button
name: "查 询"
playwright: "page.getByRole('button', { name: '查 询' })"
resetBtn:
description: "重 置"
element_type: button
section: "main::main"
strategy: role
role: button
name: "重 置"
playwright: "page.getByRole('button', { name: '重 置' })"
expandFilterBtn:
description: "展开"
element_type: button
section: "main::main"
strategy: role
role: button
name: "展开"
playwright: "page.getByRole('button', { name: '展开' })"
detailBtn:
description: "详情"
element_type: button
section: "main::main"
strategy: role
role: button
name: "详情"
playwright: "page.getByRole('button', { name: '详情' })"
editBtn:
description: "编辑"
element_type: button
section: "main::main"
strategy: role
role: button
name: "编辑"
playwright: "page.getByRole('button', { name: '编辑' })"
consumableTypeSelect_2:
description: "请选择耗材类型低值耗材高值耗材检验试剂"
element_type: select
section: "main::main"
strategy: label_text
value: "耗材类型"
playwright: "page.getByText('耗材类型', { exact: true })"
purchaseMode:
description: "耗材类型"
element_type: search
section: "main::main"
strategy: id
value: "table_search_form_purModes"
playwright: "page.locator('#table_search_form_purModes')"
# --- main::pagination ---
pagination_action:
description: "共 24 条150 条/页"
element_type: ul
section: "main::pagination"
strategy: label_text
value: "操作"
playwright: "page.getByText('操作', { exact: true })"
pagination_prevPageBtn:
description: "上一页"
element_type: li
section: "main::pagination"
strategy: label_text
value: "操作"
playwright: "page.getByText('操作', { exact: true })"
pagination_actionBtn:
description: "操作"
element_type: button
section: "main::pagination"
strategy: label_text
value: "操作"
playwright: "page.getByText('操作', { exact: true })"
pagination_page_1:
description: "1"
element_type: pagination_item
section: "main::pagination"
strategy: css
value: ".ant-pagination-item-active"
playwright: "page.locator('.ant-pagination-item-active')"
pagination_nextPageBtn:
description: "下一页"
element_type: li
section: "main::pagination"
strategy: label_text
value: "操作"
playwright: "page.getByText('操作', { exact: true })"
pagination_pageSizeSelect:
description: "50 条/页"
element_type: select
section: "main::pagination"
strategy: label_text
value: "操作"
playwright: "page.getByText('操作', { exact: true })"
# --- main::detail:产品准入单详情 ---
el_e5aea1e689b9e8bf9be7a88bBtn:
description: "审批进程"
element_type: button
section: "main::detail:产品准入单详情"
strategy: role
role: button
name: "审批进程"
playwright: "page.getByRole('button', { name: '审批进程' })"
el_e58d95e68daee697a5e5bf97Btn:
description: "单据日志"
element_type: button
section: "main::detail:产品准入单详情"
strategy: role
role: button
name: "单据日志"
playwright: "page.getByRole('button', { name: '单据日志' })"
backBtn:
description: "返 回"
element_type: button
section: "main::detail:产品准入单详情"
strategy: role
role: button
name: "返 回"
playwright: "page.getByRole('button', { name: '返 回' })"
el_e8afb7e8be93e585a5e789a9e69699e7bc96e7a081e38081e5908de7a7b0Input:
description: "请输入物料编码、名称"
element_type: text
section: "main::detail:产品准入单详情"
strategy: placeholder
value: "请输入物料编码、名称"
playwright: "page.getByPlaceholder('请输入物料编码、名称')"
exportBtn:
description: "导 出"
element_type: button
section: "main::detail:产品准入单详情"
strategy: role
role: button
name: "导 出"
playwright: "page.getByRole('button', { name: '导 出' })"
tableDensity:
description: "column-height"
element_type: span
section: "main::detail:产品准入单详情"
strategy: label
value: "column-height"
playwright: "page.getByLabel('column-height')"
el_e9809ae8bf87Btn:
description: "通过"
element_type: button
section: "main::detail:产品准入单详情"
strategy: role
role: switch
name: "通过"
playwright: "page.getByRole('switch', { name: '通过' })"
el_e99984e4bbb6Btn:
description: "附件"
element_type: button
section: "main::detail:产品准入单详情"
strategy: role
role: button
name: "附件"
playwright: "page.getByRole('button', { name: '附件' })"
el_e585b131e69da1313530e69da12fe9a1b5:
description: "共 1 条150 条/页"
element_type: ul
section: "main::detail:产品准入单详情"
strategy: text
value: "共 1 条150 条/页"
playwright: "page.getByText('共 1 条150 条/页')"
prevPageBtn:
description: "上一页"
element_type: li
section: "main::detail:产品准入单详情"
strategy: text
value: "上一页"
playwright: "page.getByText('上一页')"
page_1:
description: "1"
element_type: pagination_item
section: "main::detail:产品准入单详情"
strategy: css
value: ".ant-pagination-item-active"
playwright: "page.locator('.ant-pagination-item-active')"
nextPageBtn:
description: "下一页"
element_type: li
section: "main::detail:产品准入单详情"
strategy: text
value: "下一页"
playwright: "page.getByText('下一页')"
pageSizeSelect:
description: "50 条/页"
element_type: select
section: "main::detail:产品准入单详情"
strategy: text
value: "50 条/页"
playwright: "page.getByText('50 条/页')"
This diff is collapsed.
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