@epoint-testtech/ep-stage-skill 0.0.9 → 0.0.11

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.
File without changes
@@ -1 +1 @@
1
- {"version":3,"file":"run-report.d.ts","sourceRoot":"","sources":["../../../src/cli/run-report.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,OAAO,GAAG,SAAS,CAAC;IAClD,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,iBAAiB,EAAE,CAAC;IAC3B,mDAAmD;IACnD,OAAO,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAClD,wBAAwB;IACxB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAkDF;;GAEG;AACH,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,GAAG;IAC5D,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,iBAAiB,EAAE,CAAC;CAC5B,CA0CA;AA4ED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,CAgL/D"}
1
+ {"version":3,"file":"run-report.d.ts","sourceRoot":"","sources":["../../../src/cli/run-report.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,MAAM,iBAAiB,GAAG;IAC9B,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,MAAM,EAAE,QAAQ,GAAG,QAAQ,GAAG,OAAO,GAAG,SAAS,CAAC;IAClD,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,iBAAiB,EAAE,CAAC;IAC3B,mDAAmD;IACnD,OAAO,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAClD,wBAAwB;IACxB,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,eAAe,CAAC,EAAE,MAAM,CAAC;CAC1B,CAAC;AAkDF;;GAEG;AACH,wBAAgB,0BAA0B,CAAC,QAAQ,EAAE,MAAM,GAAG;IAC5D,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,iBAAiB,EAAE,CAAC;CAC5B,CA0CA;AA4ED;;GAEG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,aAAa,GAAG,MAAM,CA4L/D"}
@@ -149,6 +149,21 @@ function renderTestCard(item, strategy, caseId) {
149
149
  export function renderRunReportHtml(data) {
150
150
  const count = (s) => data.items.filter((i) => i.status === s).length;
151
151
  const agentCount = data.items.filter((i) => resolveStrategy(i, data.aiTasks) === 'agent_inferred').length;
152
+ // 卡片按用例ID 数字序排列(TC01001→TC01015),与 testcase.json5 顺序一致,便于人工对照;
153
+ // Playwright JSON 结果树的原始顺序是「外层 suite 直接 specs 在前、子 describe 在后」,不符合审阅习惯。
154
+ const sortedItems = [...data.items].sort((a, b) => {
155
+ const idA = resolveCaseId(a, data.aiTasks);
156
+ const idB = resolveCaseId(b, data.aiTasks);
157
+ const numA = idA ? Number(idA.replace(/^\D+/, '')) : Number.NaN;
158
+ const numB = idB ? Number(idB.replace(/^\D+/, '')) : Number.NaN;
159
+ if (Number.isNaN(numA) && Number.isNaN(numB))
160
+ return 0;
161
+ if (Number.isNaN(numA))
162
+ return 1;
163
+ if (Number.isNaN(numB))
164
+ return -1;
165
+ return numA - numB;
166
+ });
152
167
  const overviewRows = [
153
168
  ['测试套件', data.suiteTitle],
154
169
  ['执行时间', formatDisplayTimestamp(data.startedAt)],
@@ -176,8 +191,8 @@ export function renderRunReportHtml(data) {
176
191
  <div class="summary-card__label">${escapeHtml(label)}</div>
177
192
  </div>`)
178
193
  .join('');
179
- const workflowsHtml = data.items.length
180
- ? data.items
194
+ const workflowsHtml = sortedItems.length
195
+ ? sortedItems
181
196
  .map((item) => renderTestCard(item, resolveStrategy(item, data.aiTasks), resolveCaseId(item, data.aiTasks)))
182
197
  .join('\n')
183
198
  : '<p class="muted">(本次运行未发现任何用例结果)</p>';
@@ -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;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;AAiZD;;;;;GAKG;AACH,wBAAgB,gCAAgC,CAAC,KAAK,EAAE,aAAa,GAAG,cAAc,CAiKrF"}
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;AAkaD;;;;;GAKG;AACH,wBAAgB,gCAAgC,CAAC,KAAK,EAAE,aAAa,GAAG,cAAc,CA8KrF"}
@@ -421,9 +421,11 @@ function pickQueryKeyLabel(label, searchFieldLabels, dataKeyLabel) {
421
421
  * @param queryKey - 当前用例查询主键。
422
422
  * @param searchFieldLabels - 列表页查询字段标签集合。
423
423
  * @param dataKeyLabel - dataKey 对应的中文标签。
424
+ * @param deleteOutcome - 删除策略(与 skeletonSlots.assertions.deletePolicy 同源),
425
+ * 仅 delete 用例消费;缺省按 success_delete 出文案。
424
426
  * @returns testcase 执行字段。
425
427
  */
426
- function buildGlueExecutionFields(actionKind, queryKeyLabel, queryComponent, queryKeyInForm, searchFieldLabels, dataKeyLabel) {
428
+ function buildGlueExecutionFields(actionKind, queryKeyLabel, queryComponent, queryKeyInForm, searchFieldLabels, dataKeyLabel, deleteOutcome) {
427
429
  const queryKey = actionKind === 'export'
428
430
  ? pickQueryKeyLabel(dataKeyLabel ?? searchFieldLabels[0] ?? '不适用', searchFieldLabels, dataKeyLabel)
429
431
  : pickQueryKeyLabel(queryKeyLabel, searchFieldLabels, dataKeyLabel);
@@ -483,15 +485,27 @@ function buildGlueExecutionFields(actionKind, queryKeyLabel, queryComponent, que
483
485
  };
484
486
  }
485
487
  if (actionKind === 'delete') {
488
+ const deleteSteps = [
489
+ `1. 在列表页按${queryKey}搜索前置数据。`,
490
+ '2. 勾选搜索结果中的目标记录。',
491
+ '3. 点击{{删除按钮}}。',
492
+ '4. 在确认弹窗中点击{{确认按钮}}。',
493
+ ].join(' ');
494
+ // 断言文案必须与 skeletonSlots.assertions.deletePolicy 同源:
495
+ // 业务规则阻断删除时若仍写「列表应无匹配记录」,用例描述会与脚本实际执行(断言阻断文案)相悖。
496
+ if (deleteOutcome?.deletePolicy === 'blocked_by_business_rule') {
497
+ const blockMessage = deleteOutcome.blockedDeleteMessage ?? '不允许删除';
498
+ return {
499
+ queryKey,
500
+ precondition: `${smokeEntry} 2. 已基于${queryKey}完成一条测试数据新增(保存并关闭,数据为在用状态)。`,
501
+ steps: deleteSteps,
502
+ assertion: `系统应阻断删除并提示『${blockMessage}』;再次按${queryKey}搜索该数据,记录仍存在。`,
503
+ };
504
+ }
486
505
  return {
487
506
  queryKey,
488
507
  precondition: `${smokeEntry} 2. 已基于${queryKey}完成一条测试数据新增。`,
489
- steps: [
490
- `1. 在列表页按${queryKey}搜索前置数据。`,
491
- '2. 勾选搜索结果中的目标记录。',
492
- '3. 点击{{删除按钮}}。',
493
- '4. 在确认弹窗中点击{{确认按钮}}。',
494
- ].join(' '),
508
+ steps: deleteSteps,
495
509
  assertion: `删除后再次按${queryKey}搜索该数据,列表应无匹配记录。`,
496
510
  };
497
511
  }
@@ -515,7 +529,7 @@ function buildGlueExecutionFields(actionKind, queryKeyLabel, queryComponent, que
515
529
  * @param dataKeyLabel - dataKey 对应的中文标签。
516
530
  * @returns cases 数组(generationStrategy 全 glue)。
517
531
  */
518
- function buildGlueCases(moduleName, searchFields, formFieldLabels, sequence, dataKeyLabel) {
532
+ function buildGlueCases(moduleName, searchFields, formFieldLabels, sequence, dataKeyLabel, deleteOutcome) {
519
533
  const searchFieldLabels = searchFields.map((fieldInfo) => fieldInfo.label);
520
534
  const componentForLabel = (label) => searchFields.find((fieldInfo) => fieldInfo.label === label)?.component ?? 'input';
521
535
  const queryKeyInForm = (label) => formFieldLabels.includes(label);
@@ -527,7 +541,7 @@ function buildGlueCases(moduleName, searchFields, formFieldLabels, sequence, dat
527
541
  generationStrategy: 'glue',
528
542
  coverageStatus,
529
543
  reviewStatus: 'confirmed',
530
- ...buildGlueExecutionFields(actionKind, queryKeyLabel, componentForLabel(queryKeyLabel), queryKeyInForm(queryKeyLabel), searchFieldLabels, dataKeyLabel),
544
+ ...buildGlueExecutionFields(actionKind, queryKeyLabel, componentForLabel(queryKeyLabel), queryKeyInForm(queryKeyLabel), searchFieldLabels, dataKeyLabel, deleteOutcome),
531
545
  });
532
546
  // 计划驱动:保证用例条数/顺序与 spec 生成器逐条渲染的 test 一一对应。
533
547
  // listbox 键的 read 已由骨架强制断言行锚点命中,不再是 partial。
@@ -706,15 +720,24 @@ export function assembleJson5ContractAndTestcase(input) {
706
720
  // 从 code_list.businessRules 里找 scope 含 'delete' 的规则(如 delete.onlyDraftAllowed
707
721
  // "非 DRAFT 状态删除时被阻断"),命中则映射为 blocked_by_business_rule;否则 success_delete。
708
722
  // assertionHint 直接作为 skeleton 的阻断消息断言值。
709
- const deleteRule = (codeList.businessRules ?? []).find((r) => {
723
+ const deleteScopedRules = (codeList.businessRules ?? []).filter((r) => {
710
724
  const anyRule = r;
711
725
  return Array.isArray(anyRule.scope) && anyRule.scope.includes('delete');
712
726
  });
727
+ // 自愈加固:delete scope 可能混入「未勾选拦截」等操作前置规则(语义上不是记录级删除阻断),
728
+ // 不能简单取第一条。优先级:assertionHint 含阻断语义关键词(不可删除/不允许删除等)者 >
729
+ // 带 assertionHint 者 > 第一条。上游 code-list 规范仍要求阻断规则排第一,此处只是兜底。
730
+ const BLOCK_HINT_PATTERN = /不可删除|不允许删除|无法删除|不能删除|禁止删除/;
731
+ const readHint = (r) => {
732
+ const hint = r.assertionHint
733
+ ?? r.assertion;
734
+ return typeof hint === 'string' && hint.trim().length > 0 ? hint : undefined;
735
+ };
736
+ const deleteRule = deleteScopedRules.find((r) => BLOCK_HINT_PATTERN.test(readHint(r) ?? ''))
737
+ ?? deleteScopedRules.find((r) => readHint(r) !== undefined)
738
+ ?? deleteScopedRules[0];
713
739
  const deletePolicy = deleteRule ? 'blocked_by_business_rule' : 'success_delete';
714
- const blockedDeleteMessage = deleteRule
715
- ? (deleteRule.assertionHint
716
- ?? deleteRule.assertion)
717
- : undefined;
740
+ const blockedDeleteMessage = deleteRule ? readHint(deleteRule) : undefined;
718
741
  const skeletonSlotsCrud = {
719
742
  moduleId: module,
720
743
  searchConditions: buildSearchConditionSlots(codeList, listPage, addOrEditPage),
@@ -765,7 +788,10 @@ export function assembleJson5ContractAndTestcase(input) {
765
788
  unresolvedSlots: [], // 框架默认策略都已确认,不进 unresolvedSlots
766
789
  };
767
790
  const caseIdSequence = { pageIndex: 1, nextCaseIndex: 1 };
768
- const cases = buildGlueCases(moduleName, searchFields, formFieldLabels, caseIdSequence, dataKeyLabel);
791
+ const cases = buildGlueCases(moduleName, searchFields, formFieldLabels, caseIdSequence, dataKeyLabel, {
792
+ deletePolicy,
793
+ blockedDeleteMessage,
794
+ });
769
795
  const childGridCases = childGridSlots ? buildChildGridCases(childGridSlots, firstRole, caseIdSequence) : [];
770
796
  cases.push(...childGridCases);
771
797
  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.9",
3
+ "version": "0.0.11",
4
4
  "type": "module",
5
5
  "main": "dist/src/index.js",
6
6
  "types": "dist/src/index.d.ts",
@@ -19,6 +19,18 @@
19
19
  "registry": "https://registry.npmjs.org/"
20
20
  },
21
21
  "description": "Agent skills and TypeScript tools for ep-stage glue-code workflows.",
22
+ "scripts": {
23
+ "typecheck": "tsc --noEmit",
24
+ "test": "vitest run",
25
+ "build": "tsc -p tsconfig.build.json",
26
+ "prepare": "pnpm run build",
27
+ "prepack": "pnpm run build",
28
+ "generate:crud-contract": "node --import tsx src/cli/dev/extract-contract.ts",
29
+ "generate:playwright-tests": "node --import tsx src/cli/index.ts spec",
30
+ "run:gap-pipeline": "node --import tsx src/cli/run-gap-pipeline.ts",
31
+ "cli": "node --import tsx src/cli/index.ts",
32
+ "generate:testcase": "node --import tsx src/cli/index.ts testcase"
33
+ },
22
34
  "dependencies": {
23
35
  "cheerio": "^1.0.0",
24
36
  "json5": "^2.2.3",
@@ -29,15 +41,5 @@
29
41
  "tsx": "^4.20.0",
30
42
  "typescript": "^6.0.3",
31
43
  "vitest": "^4.0.0"
32
- },
33
- "scripts": {
34
- "typecheck": "tsc --noEmit",
35
- "test": "vitest run",
36
- "build": "tsc -p tsconfig.build.json",
37
- "generate:crud-contract": "node --import tsx src/cli/dev/extract-contract.ts",
38
- "generate:playwright-tests": "node --import tsx src/cli/index.ts spec",
39
- "run:gap-pipeline": "node --import tsx src/cli/run-gap-pipeline.ts",
40
- "cli": "node --import tsx src/cli/index.ts",
41
- "generate:testcase": "node --import tsx src/cli/index.ts testcase"
42
44
  }
43
- }
45
+ }