@epoint-testtech/ep-stage-skill 0.0.13 → 0.0.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/codex-skill/ep-stage/code-list-generate/SKILL.md +3 -1
- package/codex-skill/ep-stage/code-list-generate/assets/code_list.template.json5 +3 -0
- package/codex-skill/ep-stage/code-list-generate/references/field-contract.md +1 -0
- package/dist/src/generators/stage-skeleton-script.d.ts +2 -4
- package/dist/src/generators/stage-skeleton-script.d.ts.map +1 -1
- package/dist/src/generators/stage-skeleton-script.js +6 -42
- package/dist/src/testcase/contract-testcase-assembler.d.ts +1 -10
- package/dist/src/testcase/contract-testcase-assembler.d.ts.map +1 -1
- package/dist/src/testcase/contract-testcase-assembler.js +25 -37
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: code-list-generate
|
|
3
3
|
description: 使用时机:当用户(上游研发/出码方)要「生成 / 编写 / 产出 code_list.json5(出码总结、测试输入物料清单)」、交付需求给下游胶水测试流程、或显式触发 `/code-list-generate <源码根路径>` 时使用。即使用户只说「帮我把这个需求的源码整理成下游测试要的输入文件」「code_list 怎么写」,也应主动使用本 skill。本 skill 引导从 epoint 系源码(perpage/*.html + *Action.java + Entity.java)和 PRD 中提取事实,产出符合下游契约的 code_list.json5。
|
|
4
|
-
version: 2026-08-
|
|
4
|
+
version: 2026-08-18+deletable-condition
|
|
5
5
|
argument-hint: "<源码根路径>(可选;不给则打印输入引导逐项询问)"
|
|
6
6
|
---
|
|
7
7
|
|
|
@@ -121,6 +121,8 @@ node <skill目录>/scripts/validate-code-list.mjs <需求目录>/_docs/code_list
|
|
|
121
121
|
|
|
122
122
|
**`scope: ['delete']` 的纪律**:只有「记录状态/条件阻断删除」的规则(如"在用数据不可删除")才配 `scope: ['delete']` + `assertionHint`。「未勾选拦截」(如"请选择要删除的数据!")、「二次确认文案」这类**操作前置提示不标 delete scope**(可标 `scope: ['ui']` 或并入其他规则的 description)——下游把 scope 含 delete 的规则当作删除断言策略来源(装配器虽有"优先阻断语义文案"的自愈,但只是兜底,不应依赖)。若确有多条 delete scope 规则,记录级阻断规则必须排最前。
|
|
123
123
|
|
|
124
|
+
**条件性删除必须标 `deletableCondition`**:阻断不是无条件成立、而是按记录状态/条件分流的(如"草稿可删、在用不可删"),这是业务常态。这类规则除了 `scope: ['delete']` + `assertionHint`(阻断文案),还必须补 `deletableCondition`(可删条件的文字描述,如 `'记录为草稿(DRAFT)状态'`)——下游删除用例保持单条,断言据此写**双预期**(满足可删条件→删除成功查无记录;命中阻断规则→提示且记录仍存在),骨架运行时分流断言;缺了这个字段,下游按无条件阻断处理(断言只写阻断单预期),可删路径在断言层面丢失。无条件阻断的规则(如"已归档数据一律不可删")不写该字段。
|
|
125
|
+
|
|
124
126
|
## 查询字段与数据主键
|
|
125
127
|
|
|
126
128
|
- `pages[role=list].queryFields[]`:`component: 'mini-textbox'` → 输入框;`'mini-combobox'` → 下拉。带 `bind: 'dataBean.xxx'` 时下游能推出字段名。
|
|
@@ -293,6 +293,9 @@
|
|
|
293
293
|
scope: ['delete'],
|
|
294
294
|
sourceKind: 'static_scan',
|
|
295
295
|
assertionHint: '<阻断提示原文,如 在用数据不可删除!>',
|
|
296
|
+
// 条件性删除(草稿可删/在用不可删这类按状态分流的规则)必须补:
|
|
297
|
+
// deletableCondition: '记录为草稿(DRAFT)状态',
|
|
298
|
+
// 无条件阻断规则不写。缺字段下游按无条件阻断处理(断言只写阻断单预期)。
|
|
296
299
|
evidence: ['src/main/java/com/epoint/<module>/action/<XxxListAction>.java#<deleteSelect>'],
|
|
297
300
|
},
|
|
298
301
|
],
|
|
@@ -113,6 +113,7 @@
|
|
|
113
113
|
| `scope` | **关键**。作用域数组:`create`/`update`/`delete`/`ui`/子表 id 等。**含 `delete` 的规则会把下游删除用例断言改为「阻断」,下游取阻断语义最明确的一条作为策略来源(装配器优先选 assertionHint 含"不可删除"类文案的规则,兜底取第一条)**——因此只有「记录状态/条件阻断删除」的规则才标 `delete`;「未勾选拦截」「二次确认」等操作前置提示标 `ui`,不要混入 delete scope |
|
|
114
114
|
| `sourceKind` | 通常 `static_scan` |
|
|
115
115
|
| `assertionHint` | 阻断/校验提示**主文案**(如"在用数据不可删除!"),下游直接用作预期断言值。**一条规则只放一个主文案**(下游只消费单值);同规则的其余文案写进 `description`,不要塞数组 |
|
|
116
|
+
| `deletableCondition` | **可选,仅 delete scope 规则用**。条件性删除(如"草稿可删、在用不可删")时填可删条件的文字描述(如 `'记录为草稿(DRAFT)状态'`);下游删除用例保持单条、断言写双预期(条件满足→删除成功查无记录 + 条件不满足→阻断提示且记录仍存在),骨架运行时分流断言。无条件阻断规则不写;缺字段时下游按无条件阻断处理(断言只写阻断单预期) |
|
|
116
117
|
| `evidence[]` | 源码相对路径#方法锚点 |
|
|
117
118
|
|
|
118
119
|
## 留空与省略约定
|
|
@@ -92,10 +92,6 @@ export type StageSkeletonCrudFlowSlots = {
|
|
|
92
92
|
createButton: string;
|
|
93
93
|
addAutofillTrigger?: string;
|
|
94
94
|
addSaveButton: string;
|
|
95
|
-
/** 新增页「保存」按钮(草稿保存,弹窗不关闭);条件性删除的草稿用例需要 */
|
|
96
|
-
addDraftSaveButton?: string;
|
|
97
|
-
/** 新增页「关闭」按钮;草稿保存后弹窗不关闭,需显式关闭回列表页 */
|
|
98
|
-
addDialogCloseButton?: string;
|
|
99
95
|
listSearchSubmit: string;
|
|
100
96
|
listResultValueLocatorTemplate: string;
|
|
101
97
|
listEditActionLocatorTemplate: string;
|
|
@@ -107,6 +103,8 @@ export type StageSkeletonCrudFlowSlots = {
|
|
|
107
103
|
assertions: {
|
|
108
104
|
deletePolicy: StageSkeletonCrudDeletePolicy;
|
|
109
105
|
blockedDeleteMessage?: string;
|
|
106
|
+
/** 条件性删除(如"草稿可删、在用不可删"):骨架 runDeleteWorkflow 运行时按记录是否仍存在分流断言 */
|
|
107
|
+
conditionalDelete?: boolean;
|
|
110
108
|
};
|
|
111
109
|
};
|
|
112
110
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stage-skeleton-script.d.ts","sourceRoot":"","sources":["../../../src/generators/stage-skeleton-script.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"stage-skeleton-script.d.ts","sourceRoot":"","sources":["../../../src/generators/stage-skeleton-script.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,0BAA0B,EAAiF,MAAM,sCAAsC,CAAC;AAEtK,MAAM,MAAM,6BAA6B,GAAG,gBAAgB,GAAG,0BAA0B,CAAC;AAC1F,MAAM,MAAM,kCAAkC,GAAG,kBAAkB,GAAG,aAAa,CAAC;AAEpF,MAAM,MAAM,qCAAqC,GAAG,UAAU,GAAG,uBAAuB,CAAC;AACzF,MAAM,MAAM,uCAAuC,GAAG,qBAAqB,GAAG,MAAM,CAAC;AAErF,MAAM,MAAM,qCAAqC,GAAG;IAClD,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,qCAAqC,CAAC;IACtD,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,yCAAyC,GAAG;IACtD,SAAS,EAAE,OAAO,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,kCAAkC,CAAC;IAC9C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAEF,MAAM,MAAM,2CAA2C,GAAG;IACxD,SAAS,EAAE,SAAS,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,qCAAqC,CAAC;IACtD,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,eAAe,CAAC,EAAE,uCAAuC,CAAC;IAC1D,gBAAgB,CAAC,EAAE,qCAAqC,EAAE,CAAC;CAC5D,CAAC;AAEF,MAAM,MAAM,oCAAoC,GAC5C,yCAAyC,GACzC,2CAA2C,CAAC;AAEhD;;GAEG;AACH,MAAM,MAAM,gCAAgC,GAAG;IAC7C,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,OAAO,GAAG,SAAS,CAAC;IAC/B,QAAQ,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB,CAAC;AAEF;;GAEG;AACH,MAAM,MAAM,+BAA+B,GAAG;IAC5C,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,KAAK,GAAG,MAAM,CAAC;IACzB,QAAQ,EAAE,WAAW,GAAG,mBAAmB,GAAG,MAAM,CAAC;IACrD,OAAO,EAAE,gCAAgC,EAAE,CAAC;IAC5C,QAAQ,EAAE;QACR,SAAS,EAAE,MAAM,CAAC;QAClB,YAAY,EAAE,MAAM,CAAC;QACrB,oBAAoB,EAAE,MAAM,CAAC;QAC7B,IAAI,EAAE,MAAM,CAAC;QACb,aAAa,EAAE,MAAM,CAAC;QACtB,sBAAsB,EAAE,MAAM,CAAC;QAC/B,oBAAoB,EAAE,MAAM,CAAC;QAC7B,kBAAkB,EAAE,MAAM,CAAC;QAC3B,mBAAmB,EAAE,MAAM,CAAC;QAC5B,wBAAwB,EAAE,MAAM,CAAC;KAClC,CAAC;CACH,CAAC;AAEF;;;;;GAKG;AACH,MAAM,MAAM,0BAA0B,GAAG;IACvC,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,oCAAoC,EAAE,CAAC;IACzD,MAAM,EAAE;QACN,oBAAoB,EAAE,MAAM,CAAC;QAC7B,mBAAmB,EAAE,MAAM,CAAC;QAC5B,oBAAoB,EAAE,MAAM,CAAC;KAC9B,CAAC;IACF,QAAQ,EAAE;QACR,YAAY,EAAE,MAAM,CAAC;QACrB,kBAAkB,CAAC,EAAE,MAAM,CAAC;QAC5B,aAAa,EAAE,MAAM,CAAC;QACtB,gBAAgB,EAAE,MAAM,CAAC;QACzB,8BAA8B,EAAE,MAAM,CAAC;QACvC,6BAA6B,EAAE,MAAM,CAAC;QACtC,cAAc,EAAE,MAAM,CAAC;QACvB,4BAA4B,EAAE,MAAM,CAAC;QACrC,gBAAgB,EAAE,MAAM,CAAC;QACzB,mBAAmB,CAAC,EAAE,MAAM,CAAC;KAC9B,CAAC;IACF,UAAU,EAAE;QACV,YAAY,EAAE,6BAA6B,CAAC;QAC5C,oBAAoB,CAAC,EAAE,MAAM,CAAC;QAC9B,gEAAgE;QAChE,iBAAiB,CAAC,EAAE,OAAO,CAAC;KAC7B,CAAC;CACH,CAAC;AAoSF;;;;;;GAMG;AACH,wBAAgB,mCAAmC,CAAC,QAAQ,EAAE,0BAA0B,GAAG,0BAA0B,CA0CpH;AAyFD;;;;;GAKG;AACH,wBAAgB,4BAA4B,CAAC,KAAK,EAAE,0BAA0B,GAAG,MAAM,CAyBtF;AAuCD;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,+BAA+B,EAAE,GAAG,MAAM,CAarF;AAsSD,+CAA+C;AAC/C,MAAM,MAAM,eAAe,GAAG;IAC5B,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAkLF;;;;;;;GAOG;AACH,MAAM,MAAM,6CAA6C,GAAG;IAC1D,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,EAAE,0BAA0B,CAAC;IACtC,UAAU,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9D,cAAc,CAAC,EAAE,+BAA+B,EAAE,CAAC;IACnD,iEAAiE;IACjE,SAAS,CAAC,EAAE,eAAe,EAAE,CAAC;CAC/B,CAAC;AAEF;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,sCAAsC,CACpD,OAAO,EAAE,6CAA6C,GACrD,MAAM,CAwIR;AAED;;;;;GAKG"}
|
|
@@ -423,8 +423,6 @@ ${renderSearchConditions(slots.searchConditions, 8)}
|
|
|
423
423
|
createButton: ${locatorLiteral(slots.locators.createButton)},
|
|
424
424
|
${renderOptionalStringProperty('addAutofillTrigger', slots.locators.addAutofillTrigger, 12)}
|
|
425
425
|
addSaveButton: ${locatorLiteral(slots.locators.addSaveButton)},
|
|
426
|
-
${renderOptionalStringProperty('addDraftSaveButton', slots.locators.addDraftSaveButton, 12)}
|
|
427
|
-
${renderOptionalStringProperty('addDialogCloseButton', slots.locators.addDialogCloseButton, 12)}
|
|
428
426
|
listSearchSubmit: ${locatorLiteral(slots.locators.listSearchSubmit)},
|
|
429
427
|
listResultValueLocatorTemplate: ${locatorLiteral(slots.locators.listResultValueLocatorTemplate)},
|
|
430
428
|
listEditActionLocatorTemplate: ${locatorLiteral(slots.locators.listEditActionLocatorTemplate)},
|
|
@@ -434,7 +432,8 @@ ${renderOptionalStringProperty('addDialogCloseButton', slots.locators.addDialogC
|
|
|
434
432
|
${renderOptionalStringProperty('deleteConfirmButton', slots.locators.deleteConfirmButton, 12)} },
|
|
435
433
|
assertions: {
|
|
436
434
|
deletePolicy: ${singleQuoted(slots.assertions.deletePolicy)},
|
|
437
|
-
${renderOptionalStringProperty('blockedDeleteMessage', slots.assertions.blockedDeleteMessage, 12)}
|
|
435
|
+
${renderOptionalStringProperty('blockedDeleteMessage', slots.assertions.blockedDeleteMessage, 12)}
|
|
436
|
+
${slots.assertions.conditionalDelete ? ' conditionalDelete: true,\n' : ''} },
|
|
438
437
|
}`;
|
|
439
438
|
}
|
|
440
439
|
function renderChildGridColumns(columns, level) {
|
|
@@ -742,28 +741,14 @@ function renderChildGridTests(slots, crudSlots, moduleLabel, moduleId, glueCases
|
|
|
742
741
|
})
|
|
743
742
|
.join('\n\n');
|
|
744
743
|
}
|
|
745
|
-
/** 解析 glue CRUD 用例标题:`${moduleName} ${actionKind}(${queryKey})`。
|
|
746
|
-
* 条件性删除用例标题带变体后缀:`delete(${queryKey}-草稿)` / `delete(${queryKey}-在用)`,
|
|
747
|
-
* 解析时剥离后缀还原 base queryKey(保证数据分组不受后缀影响),并返回 deleteVariant。 */
|
|
744
|
+
/** 解析 glue CRUD 用例标题:`${moduleName} ${actionKind}(${queryKey})`。 */
|
|
748
745
|
function parseCrudCaseTitle(title) {
|
|
749
746
|
const match = title.match(/\s(create|read|update|delete)((.+))\s*$/);
|
|
750
747
|
if (!match)
|
|
751
748
|
return null;
|
|
752
|
-
const actionKind = match[1];
|
|
753
|
-
const rawKey = match[2];
|
|
754
|
-
if (actionKind === 'delete') {
|
|
755
|
-
const variantMatch = rawKey.match(/^(.*)-(?:草稿|在用)$/);
|
|
756
|
-
if (variantMatch) {
|
|
757
|
-
return {
|
|
758
|
-
actionKind,
|
|
759
|
-
queryKeyLabel: variantMatch[1],
|
|
760
|
-
deleteVariant: rawKey.endsWith('-草稿') ? 'draft_success' : 'blocked',
|
|
761
|
-
};
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
749
|
return {
|
|
765
|
-
actionKind,
|
|
766
|
-
queryKeyLabel:
|
|
750
|
+
actionKind: match[1],
|
|
751
|
+
queryKeyLabel: match[2],
|
|
767
752
|
};
|
|
768
753
|
}
|
|
769
754
|
/** 把用例 steps 文本("1. …。 2. …。")拆成 elementActions 数组。 */
|
|
@@ -828,34 +813,13 @@ ${componentForLabel(key) === 'listbox'
|
|
|
828
813
|
: ` await crudPage.runUpdateWorkflow(data);`}`;
|
|
829
814
|
break;
|
|
830
815
|
case 'delete':
|
|
831
|
-
|
|
832
|
-
// 条件性删除-可删用例:自建草稿数据(saveMode='draft')再删除,预期成功。
|
|
833
|
-
// 不复用 create 组的在用数据(deleteVariant='failed' 的用例依赖它保持未被删除)。
|
|
834
|
-
body = ` await openModule(page);
|
|
835
|
-
const crudPage = new CrudPage(page, crudSlots);
|
|
836
|
-
const data = crudPage.prepareWorkflowData();
|
|
837
|
-
await crudPage.runCreateWorkflow(data, { saveMode: 'draft' });
|
|
838
|
-
await crudPage.runDeleteWorkflow(data, { expectation: 'success' });`;
|
|
839
|
-
}
|
|
840
|
-
else if (testCase.deleteVariant === 'blocked') {
|
|
841
|
-
// 条件性删除-阻断用例:复用 create 组的在用数据,预期删除失败。
|
|
842
|
-
body = ` await openModule(page);
|
|
843
|
-
const data = workflowDataByKey[${singleQuoted(dataKey)}];
|
|
844
|
-
if (!data) {
|
|
845
|
-
throw new Error(${singleQuoted(`${testCase.title} 依赖 create(${dataKey})用例先行生成 workflowData`)});
|
|
846
|
-
}
|
|
847
|
-
const crudPage = new CrudPage(page, crudSlots);
|
|
848
|
-
await crudPage.runDeleteWorkflow(data, { expectation: 'failed' });`;
|
|
849
|
-
}
|
|
850
|
-
else {
|
|
851
|
-
body = ` await openModule(page);
|
|
816
|
+
body = ` await openModule(page);
|
|
852
817
|
const data = workflowDataByKey[${singleQuoted(dataKey)}];
|
|
853
818
|
if (!data) {
|
|
854
819
|
throw new Error(${singleQuoted(`${testCase.title} 依赖 create(${dataKey})用例先行生成 workflowData`)});
|
|
855
820
|
}
|
|
856
821
|
const crudPage = new CrudPage(page, crudSlots);
|
|
857
822
|
await crudPage.runDeleteWorkflow(data);`;
|
|
858
|
-
}
|
|
859
823
|
break;
|
|
860
824
|
}
|
|
861
825
|
return ` test(${singleQuoted(fullTitle)}, async ({ page }) => {
|
|
@@ -43,12 +43,6 @@ export type CrudCaseStage = {
|
|
|
43
43
|
actionKind: GlueActionKind;
|
|
44
44
|
queryKeyLabel: string;
|
|
45
45
|
component: 'input' | 'listbox';
|
|
46
|
-
/**
|
|
47
|
-
* 条件性删除(如"草稿可删、在用不可删")时的删除用例变体:
|
|
48
|
-
* draft_success = 满足可删条件(草稿)删除成功;blocked = 不满足条件(在用)被业务规则阻断。
|
|
49
|
-
* 仅 actionKind='delete' 且条件性规则时出现;无条件阻断/正常可删不设置(保持原有用例形态)。
|
|
50
|
-
*/
|
|
51
|
-
deleteVariant?: 'draft_success' | 'blocked';
|
|
52
46
|
};
|
|
53
47
|
/**
|
|
54
48
|
* 按查询字段推导 CRUD 用例阶段序列。
|
|
@@ -56,18 +50,15 @@ export type CrudCaseStage = {
|
|
|
56
50
|
* 规则:在新增表单中出现的查询字段(可写)产出完整 create/read/update/delete 四条;
|
|
57
51
|
* 不在表单中的字段(如系统管理的状态)只产出 read 一条。
|
|
58
52
|
* export 不在此计划内,由装配器单独追加。
|
|
59
|
-
* 条件性删除规则(conditionalDelete=true)时,可写键的 delete 由一条变两条:
|
|
60
|
-
* 草稿删除成功(draft_success)在前、在用删除阻断(blocked)在后。
|
|
61
53
|
*
|
|
62
54
|
* @param searchFields - 列表页查询字段(有序)。
|
|
63
55
|
* @param formFieldLabels - 新增表单字段标签集合。
|
|
64
|
-
* @param conditionalDelete - 删除是否为条件性(businessRules 的 delete 规则带 deletableCondition)。
|
|
65
56
|
* @returns 有序阶段计划,顺序与 buildGlueCases 产出的用例顺序一一对应。
|
|
66
57
|
*/
|
|
67
58
|
export declare function planCrudCaseStages(searchFields: Array<{
|
|
68
59
|
label: string;
|
|
69
60
|
component: 'input' | 'listbox';
|
|
70
|
-
}>, formFieldLabels: string[]
|
|
61
|
+
}>, formFieldLabels: string[]): CrudCaseStage[];
|
|
71
62
|
/**
|
|
72
63
|
* 装配 contract + testcase + inference-trace 账本。
|
|
73
64
|
*
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract-testcase-assembler.d.ts","sourceRoot":"","sources":["../../../src/testcase/contract-testcase-assembler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,sBAAsB,EAA2E,MAAM,kCAAkC,CAAC;AACxJ,OAAO,KAAK,EAAE,sBAAsB,EAAqB,MAAM,kCAAkC,CAAC;AAElG,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AASrE,oBAAoB;AACpB,MAAM,MAAM,aAAa,GAAG;IAC1B,iCAAiC;IACjC,QAAQ,EAAE,sBAAsB,CAAC;IACjC,qCAAqC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wCAAwC;IACxC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,gEAAgE;AAChE,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,EAAE,sBAAsB,CAAC;IACjC,QAAQ,EAAE,sBAAsB,CAAC;IACjC,mEAAmE;IACnE,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACzC,CAAC;
|
|
1
|
+
{"version":3,"file":"contract-testcase-assembler.d.ts","sourceRoot":"","sources":["../../../src/testcase/contract-testcase-assembler.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,OAAO,KAAK,EAAE,sBAAsB,EAA2E,MAAM,kCAAkC,CAAC;AACxJ,OAAO,KAAK,EAAE,sBAAsB,EAAqB,MAAM,kCAAkC,CAAC;AAElG,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AASrE,oBAAoB;AACpB,MAAM,MAAM,aAAa,GAAG;IAC1B,iCAAiC;IACjC,QAAQ,EAAE,sBAAsB,CAAC;IACjC,qCAAqC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wCAAwC;IACxC,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,CAAC;AAEF,gEAAgE;AAChE,MAAM,MAAM,cAAc,GAAG;IAC3B,QAAQ,EAAE,sBAAsB,CAAC;IACjC,QAAQ,EAAE,sBAAsB,CAAC;IACjC,mEAAmE;IACnE,cAAc,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACzC,CAAC;AAkZF,mBAAmB;AACnB,KAAK,cAAc,GAAG,QAAQ,GAAG,MAAM,GAAG,QAAQ,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAEzE;;GAEG;AACH,MAAM,MAAM,aAAa,GAAG;IAC1B,UAAU,EAAE,cAAc,CAAC;IAC3B,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,OAAO,GAAG,SAAS,CAAC;CAChC,CAAC;AAEF;;;;;;;;;;GAUG;AACH,wBAAgB,kBAAkB,CAChC,YAAY,EAAE,KAAK,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,GAAG,SAAS,CAAA;CAAE,CAAC,EACtE,eAAe,EAAE,MAAM,EAAE,GACxB,aAAa,EAAE,CAejB;AAsbD;;;;;GAKG;AACH,wBAAgB,gCAAgC,CAAC,KAAK,EAAE,aAAa,GAAG,cAAc,CAyLrF"}
|
|
@@ -49,7 +49,7 @@ const DEFAULT_LOCATORS = {
|
|
|
49
49
|
* @param listPage - code_list.pages[role=list]。
|
|
50
50
|
* @returns 覆盖 StageSkeletonCrudFlowSlots.locators 全部字段的定位表达式。
|
|
51
51
|
*/
|
|
52
|
-
function buildCrudLocators(listPage
|
|
52
|
+
function buildCrudLocators(listPage) {
|
|
53
53
|
const gridId = 'datagrid';
|
|
54
54
|
const createLabel = listPage.buttons?.find((b) => b.action === 'create')?.label ?? '新增';
|
|
55
55
|
const deleteLabel = listPage.buttons?.find((b) => b.action === 'delete')?.label ?? '删除选定';
|
|
@@ -59,10 +59,6 @@ function buildCrudLocators(listPage, addOrEditPage, conditionalDelete = false) {
|
|
|
59
59
|
})?.label ?? '修改';
|
|
60
60
|
const miniButton = (label) => `xpath=//a[contains(@class,'mini-button')][normalize-space()='${label}']`;
|
|
61
61
|
const gridCell = `xpath=//*[@id='${gridId}']//div[text()='{{value}}']`;
|
|
62
|
-
// 条件性删除需要「草稿删除成功」用例:新增页保存草稿按钮(action=save,如「保存」)
|
|
63
|
-
// 与关闭按钮(action=cancel,草稿保存后弹窗不关闭,需显式关闭回列表页)。
|
|
64
|
-
const draftSaveLabel = addOrEditPage?.buttons?.find((b) => b.action === 'save')?.label;
|
|
65
|
-
const dialogCloseLabel = addOrEditPage?.buttons?.find((b) => b.action === 'cancel')?.label;
|
|
66
62
|
return {
|
|
67
63
|
createButton: miniButton(createLabel),
|
|
68
64
|
addAutofillTrigger: `xpath=//*[@title='默认填充']`,
|
|
@@ -74,8 +70,6 @@ function buildCrudLocators(listPage, addOrEditPage, conditionalDelete = false) {
|
|
|
74
70
|
listRowSelectLocatorTemplate: gridCell,
|
|
75
71
|
listDeleteButton: miniButton(deleteLabel),
|
|
76
72
|
deleteConfirmButton: 'text=确定',
|
|
77
|
-
...(conditionalDelete && draftSaveLabel ? { addDraftSaveButton: miniButton(draftSaveLabel) } : {}),
|
|
78
|
-
...(conditionalDelete && dialogCloseLabel ? { addDialogCloseButton: miniButton(dialogCloseLabel) } : {}),
|
|
79
73
|
};
|
|
80
74
|
}
|
|
81
75
|
/**
|
|
@@ -371,25 +365,16 @@ function normalizeQueryComponent(fieldInfo) {
|
|
|
371
365
|
* 规则:在新增表单中出现的查询字段(可写)产出完整 create/read/update/delete 四条;
|
|
372
366
|
* 不在表单中的字段(如系统管理的状态)只产出 read 一条。
|
|
373
367
|
* export 不在此计划内,由装配器单独追加。
|
|
374
|
-
* 条件性删除规则(conditionalDelete=true)时,可写键的 delete 由一条变两条:
|
|
375
|
-
* 草稿删除成功(draft_success)在前、在用删除阻断(blocked)在后。
|
|
376
368
|
*
|
|
377
369
|
* @param searchFields - 列表页查询字段(有序)。
|
|
378
370
|
* @param formFieldLabels - 新增表单字段标签集合。
|
|
379
|
-
* @param conditionalDelete - 删除是否为条件性(businessRules 的 delete 规则带 deletableCondition)。
|
|
380
371
|
* @returns 有序阶段计划,顺序与 buildGlueCases 产出的用例顺序一一对应。
|
|
381
372
|
*/
|
|
382
|
-
export function planCrudCaseStages(searchFields, formFieldLabels
|
|
373
|
+
export function planCrudCaseStages(searchFields, formFieldLabels) {
|
|
383
374
|
const stages = [];
|
|
384
375
|
for (const field of searchFields) {
|
|
385
376
|
if (formFieldLabels.includes(field.label)) {
|
|
386
|
-
stages.push({ actionKind: 'create', queryKeyLabel: field.label, component: field.component }, { actionKind: 'read', queryKeyLabel: field.label, component: field.component }, { actionKind: 'update', queryKeyLabel: field.label, component: field.component });
|
|
387
|
-
if (conditionalDelete) {
|
|
388
|
-
stages.push({ actionKind: 'delete', queryKeyLabel: field.label, component: field.component, deleteVariant: 'draft_success' }, { actionKind: 'delete', queryKeyLabel: field.label, component: field.component, deleteVariant: 'blocked' });
|
|
389
|
-
}
|
|
390
|
-
else {
|
|
391
|
-
stages.push({ actionKind: 'delete', queryKeyLabel: field.label, component: field.component });
|
|
392
|
-
}
|
|
377
|
+
stages.push({ actionKind: 'create', queryKeyLabel: field.label, component: field.component }, { actionKind: 'read', queryKeyLabel: field.label, component: field.component }, { actionKind: 'update', queryKeyLabel: field.label, component: field.component }, { actionKind: 'delete', queryKeyLabel: field.label, component: field.component });
|
|
393
378
|
}
|
|
394
379
|
else {
|
|
395
380
|
stages.push({ actionKind: 'read', queryKeyLabel: field.label, component: field.component });
|
|
@@ -440,7 +425,7 @@ function pickQueryKeyLabel(label, searchFieldLabels, dataKeyLabel) {
|
|
|
440
425
|
* 仅 delete 用例消费;缺省按 success_delete 出文案。
|
|
441
426
|
* @returns testcase 执行字段。
|
|
442
427
|
*/
|
|
443
|
-
function buildGlueExecutionFields(actionKind, queryKeyLabel, queryComponent, queryKeyInForm, searchFieldLabels, dataKeyLabel, deleteOutcome
|
|
428
|
+
function buildGlueExecutionFields(actionKind, queryKeyLabel, queryComponent, queryKeyInForm, searchFieldLabels, dataKeyLabel, deleteOutcome) {
|
|
444
429
|
const queryKey = actionKind === 'export'
|
|
445
430
|
? pickQueryKeyLabel(dataKeyLabel ?? searchFieldLabels[0] ?? '不适用', searchFieldLabels, dataKeyLabel)
|
|
446
431
|
: pickQueryKeyLabel(queryKeyLabel, searchFieldLabels, dataKeyLabel);
|
|
@@ -506,20 +491,21 @@ function buildGlueExecutionFields(actionKind, queryKeyLabel, queryComponent, que
|
|
|
506
491
|
'3. 点击{{删除按钮}}。',
|
|
507
492
|
'4. 在确认弹窗中点击{{确认按钮}}。',
|
|
508
493
|
].join(' ');
|
|
509
|
-
//
|
|
510
|
-
//
|
|
511
|
-
//
|
|
512
|
-
//
|
|
513
|
-
if (
|
|
494
|
+
// 断言文案必须与骨架 runDeleteWorkflow 的实际执行保持一致:
|
|
495
|
+
// - 条件性删除(如"草稿可删、在用不可删"):单用例双预期,骨架运行时按记录是否仍存在分流断言;
|
|
496
|
+
// - 无条件阻断:断言阻断文案 + 记录仍存在;
|
|
497
|
+
// - 无删除规则:断言删后查无记录。
|
|
498
|
+
if (deleteOutcome?.deletePolicy === 'blocked_by_business_rule' && deleteOutcome.conditional) {
|
|
499
|
+
const blockMessage = deleteOutcome.blockedDeleteMessage ?? '不允许删除';
|
|
514
500
|
return {
|
|
515
501
|
queryKey,
|
|
516
|
-
precondition: `${smokeEntry} 2. 已基于${queryKey}
|
|
502
|
+
precondition: `${smokeEntry} 2. 已基于${queryKey}完成一条测试数据新增。`,
|
|
517
503
|
steps: deleteSteps,
|
|
518
|
-
assertion:
|
|
504
|
+
assertion: `删除结果按业务规则分流:若记录满足可删除条件,应删除成功,再次按${queryKey}搜索无匹配记录;若命中阻断规则,应提示『${blockMessage}』且记录仍存在。`,
|
|
519
505
|
};
|
|
520
506
|
}
|
|
521
|
-
if (
|
|
522
|
-
const blockMessage = deleteOutcome
|
|
507
|
+
if (deleteOutcome?.deletePolicy === 'blocked_by_business_rule') {
|
|
508
|
+
const blockMessage = deleteOutcome.blockedDeleteMessage ?? '不允许删除';
|
|
523
509
|
return {
|
|
524
510
|
queryKey,
|
|
525
511
|
precondition: `${smokeEntry} 2. 已基于${queryKey}完成一条测试数据新增(保存并关闭,数据为在用状态)。`,
|
|
@@ -554,25 +540,23 @@ function buildGlueExecutionFields(actionKind, queryKeyLabel, queryComponent, que
|
|
|
554
540
|
* @param dataKeyLabel - dataKey 对应的中文标签。
|
|
555
541
|
* @returns cases 数组(generationStrategy 全 glue)。
|
|
556
542
|
*/
|
|
557
|
-
function buildGlueCases(moduleName, searchFields, formFieldLabels, sequence, dataKeyLabel, deleteOutcome
|
|
543
|
+
function buildGlueCases(moduleName, searchFields, formFieldLabels, sequence, dataKeyLabel, deleteOutcome) {
|
|
558
544
|
const searchFieldLabels = searchFields.map((fieldInfo) => fieldInfo.label);
|
|
559
545
|
const componentForLabel = (label) => searchFields.find((fieldInfo) => fieldInfo.label === label)?.component ?? 'input';
|
|
560
546
|
const queryKeyInForm = (label) => formFieldLabels.includes(label);
|
|
561
|
-
|
|
562
|
-
const deleteTitleSuffix = (variant) => variant === 'draft_success' ? '-草稿' : variant === 'blocked' ? '-在用' : '';
|
|
563
|
-
const mk = (actionKind, queryKeyLabel, coverageStatus, deleteVariant) => ({
|
|
547
|
+
const mk = (actionKind, queryKeyLabel, coverageStatus) => ({
|
|
564
548
|
caseId: nextCaseId(sequence),
|
|
565
549
|
title: actionKind === 'export'
|
|
566
550
|
? `${moduleName} export`
|
|
567
|
-
: `${moduleName} ${actionKind}(${queryKeyLabel}
|
|
551
|
+
: `${moduleName} ${actionKind}(${queryKeyLabel})`,
|
|
568
552
|
generationStrategy: 'glue',
|
|
569
553
|
coverageStatus,
|
|
570
554
|
reviewStatus: 'confirmed',
|
|
571
|
-
...buildGlueExecutionFields(actionKind, queryKeyLabel, componentForLabel(queryKeyLabel), queryKeyInForm(queryKeyLabel), searchFieldLabels, dataKeyLabel, deleteOutcome
|
|
555
|
+
...buildGlueExecutionFields(actionKind, queryKeyLabel, componentForLabel(queryKeyLabel), queryKeyInForm(queryKeyLabel), searchFieldLabels, dataKeyLabel, deleteOutcome),
|
|
572
556
|
});
|
|
573
557
|
// 计划驱动:保证用例条数/顺序与 spec 生成器逐条渲染的 test 一一对应。
|
|
574
558
|
// listbox 键的 read 已由骨架强制断言行锚点命中,不再是 partial。
|
|
575
|
-
const cases = planCrudCaseStages(searchFields, formFieldLabels
|
|
559
|
+
const cases = planCrudCaseStages(searchFields, formFieldLabels).map((stage) => mk(stage.actionKind, stage.queryKeyLabel, 'covered'));
|
|
576
560
|
cases.push(mk('export', dataKeyLabel ?? searchFieldLabels[0] ?? '不适用', 'covered'));
|
|
577
561
|
return cases;
|
|
578
562
|
}
|
|
@@ -775,10 +759,13 @@ export function assembleJson5ContractAndTestcase(input) {
|
|
|
775
759
|
moduleId: module,
|
|
776
760
|
searchConditions: buildSearchConditionSlots(codeList, listPage, addOrEditPage),
|
|
777
761
|
frames: resolveFrameKeywords(codeList),
|
|
778
|
-
locators: buildCrudLocators(listPage
|
|
762
|
+
locators: buildCrudLocators(listPage),
|
|
779
763
|
assertions: {
|
|
780
764
|
deletePolicy,
|
|
781
765
|
blockedDeleteMessage,
|
|
766
|
+
// 条件性删除标记:骨架 runDeleteWorkflow 据此在运行时按「记录是否仍存在」分流断言
|
|
767
|
+
// (存在 → 断言阻断文案;不存在 → 断言删除成功),而非 slots 级单一预期。
|
|
768
|
+
...(conditionalDelete ? { conditionalDelete: true } : {}),
|
|
782
769
|
},
|
|
783
770
|
};
|
|
784
771
|
const childGridSlots = buildChildGridSlots(codeList, input.sourceRoot);
|
|
@@ -824,7 +811,8 @@ export function assembleJson5ContractAndTestcase(input) {
|
|
|
824
811
|
const cases = buildGlueCases(moduleName, searchFields, formFieldLabels, caseIdSequence, dataKeyLabel, {
|
|
825
812
|
deletePolicy,
|
|
826
813
|
blockedDeleteMessage,
|
|
827
|
-
|
|
814
|
+
conditional: conditionalDelete,
|
|
815
|
+
});
|
|
828
816
|
const childGridCases = childGridSlots ? buildChildGridCases(childGridSlots, firstRole, caseIdSequence) : [];
|
|
829
817
|
cases.push(...childGridCases);
|
|
830
818
|
const buttonInferences = inferButtonInteractions(codeList, {
|