@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.
@@ -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-17+delete-scope-fix
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":"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"}
@@ -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: rawKey,
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
- if (testCase.deleteVariant === 'draft_success') {
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[], conditionalDelete?: boolean): CrudCaseStage[];
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;AAwZF,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;IAC/B;;;;OAIG;IACH,aAAa,CAAC,EAAE,eAAe,GAAG,SAAS,CAAC;CAC7C,CAAC;AAEF;;;;;;;;;;;;;GAaG;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,EACzB,iBAAiB,UAAQ,GACxB,aAAa,EAAE,CAsBjB;AAmbD;;;;;GAKG;AACH,wBAAgB,gCAAgC,CAAC,KAAK,EAAE,aAAa,GAAG,cAAc,CA6LrF"}
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, addOrEditPage, conditionalDelete = false) {
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, conditionalDelete = false) {
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, deleteVariant) {
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
- // 断言文案必须与 skeletonSlots.assertions.deletePolicy 同源:
510
- // 业务规则阻断删除时若仍写「列表应无匹配记录」,用例描述会与脚本实际执行(断言阻断文案)相悖。
511
- // 条件性删除(如"草稿可删、在用不可删")按 deleteVariant 分叉:
512
- // draft_success = 草稿数据删除成功;blocked = 在用数据删除被阻断。
513
- if (deleteVariant === 'draft_success') {
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: `删除后再次按${queryKey}搜索该数据,列表应无匹配记录。`,
504
+ assertion: `删除结果按业务规则分流:若记录满足可删除条件,应删除成功,再次按${queryKey}搜索无匹配记录;若命中阻断规则,应提示『${blockMessage}』且记录仍存在。`,
519
505
  };
520
506
  }
521
- if (deleteVariant === 'blocked' || deleteOutcome?.deletePolicy === 'blocked_by_business_rule') {
522
- const blockMessage = deleteOutcome?.blockedDeleteMessage ?? '不允许删除';
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, conditionalDelete = false) {
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
- // 条件性删除的标题后缀:spec 生成器按此后缀识别删除变体(parseCrudCaseTitle 同步解析)。
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}${deleteTitleSuffix(deleteVariant)})`,
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, deleteVariant),
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, conditionalDelete).map((stage) => mk(stage.actionKind, stage.queryKeyLabel, 'covered', stage.deleteVariant));
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, addOrEditPage, conditionalDelete),
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
- }, conditionalDelete);
814
+ conditional: conditionalDelete,
815
+ });
828
816
  const childGridCases = childGridSlots ? buildChildGridCases(childGridSlots, firstRole, caseIdSequence) : [];
829
817
  cases.push(...childGridCases);
830
818
  const buttonInferences = inferButtonInteractions(codeList, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@epoint-testtech/ep-stage-skill",
3
- "version": "0.0.13",
3
+ "version": "0.0.15",
4
4
  "type": "module",
5
5
  "main": "dist/src/index.js",
6
6
  "types": "dist/src/index.d.ts",