@epoint-testtech/ep-stage-skill 0.0.9 → 0.5.0-alpha.2
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/SKILL.md +27 -27
- package/codex-skill/ep-stage/glue-create-project/SKILL.md +223 -223
- package/codex-skill/ep-stage/glue-generate-testcase/SKILL.md +275 -275
- package/codex-skill/ep-stage/glue-generate-testcase/examples/observable-testcase.json +40 -40
- package/codex-skill/ep-stage/glue-generate-testcase/references/testcase-schema.md +95 -95
- package/codex-skill/ep-stage/glue-run-test/SKILL.md +273 -296
- package/codex-skill/ep-stage/glue-run-test/references/ai-runtime-loop.md +30 -30
- package/codex-skill/ep-stage/glue-run-test/references/crud-pipeline.md +132 -132
- package/codex-skill/ep-stage/glue-run-test/references/gap-review-protocol.md +44 -44
- package/codex-skill/ep-stage/glue-run-test/references/harness-principles.md +46 -46
- package/codex-skill/ep-stage/glue-run-test/references/playwright-cli-odav.md +127 -166
- package/codex-skill/ep-stage/glue-run-test/scripts/generate-crud-spec.mjs +149 -149
- package/codex-skill/ep-stage/recording-to-glue/SKILL.md +28 -28
- package/codex-skill/ep-stage/scripts/validate-skill.mjs +95 -98
- package/dist/src/cli/generate-testcase.d.ts +0 -1
- package/dist/src/cli/generate-testcase.d.ts.map +1 -1
- package/dist/src/cli/generate-testcase.js +1 -6
- package/dist/src/cli/index.d.ts.map +1 -1
- package/dist/src/cli/index.js +14 -38
- package/dist/src/cli/run.d.ts +0 -2
- package/dist/src/cli/run.d.ts.map +1 -1
- package/dist/src/cli/run.js +21 -70
- package/dist/src/cli/spec.d.ts.map +1 -1
- package/dist/src/cli/spec.js +0 -9
- package/dist/src/contracts/glue-contract-v1.d.ts +0 -35
- package/dist/src/contracts/glue-contract-v1.d.ts.map +1 -1
- package/dist/src/extractors/code-list-json5.d.ts +1 -34
- package/dist/src/extractors/code-list-json5.d.ts.map +1 -1
- package/dist/src/extractors/code-list-json5.js +0 -1
- package/dist/src/extractors/html-page.d.ts +0 -9
- package/dist/src/extractors/html-page.d.ts.map +1 -1
- package/dist/src/extractors/html-page.js +2 -26
- package/dist/src/generators/agent-inferred-workflow-script.js +66 -66
- package/dist/src/generators/stage-skeleton-script.d.ts +0 -50
- package/dist/src/generators/stage-skeleton-script.d.ts.map +1 -1
- package/dist/src/generators/stage-skeleton-script.js +116 -449
- package/dist/src/runtime/ai-runtime-trace.d.ts.map +1 -1
- package/dist/src/runtime/ai-runtime-trace.js +0 -8
- package/dist/src/testcase/contract-testcase-assembler.d.ts +0 -28
- package/dist/src/testcase/contract-testcase-assembler.d.ts.map +1 -1
- package/dist/src/testcase/contract-testcase-assembler.js +75 -329
- package/dist/src/validation/contract.d.ts.map +1 -1
- package/dist/src/validation/contract.js +0 -29
- package/dist/test/cli/generate-testcase-json5.test.d.ts +2 -0
- package/dist/test/cli/generate-testcase-json5.test.d.ts.map +1 -0
- package/dist/test/cli/generate-testcase-json5.test.js +154 -0
- package/dist/test/cli/index.test.d.ts +2 -0
- package/dist/test/cli/index.test.d.ts.map +1 -0
- package/dist/test/cli/index.test.js +114 -0
- package/dist/test/cli/run.test.d.ts +2 -0
- package/dist/test/cli/run.test.d.ts.map +1 -0
- package/dist/test/cli/run.test.js +429 -0
- package/dist/test/cli/spec.test.d.ts +2 -0
- package/dist/test/cli/spec.test.d.ts.map +1 -0
- package/dist/test/cli/spec.test.js +112 -0
- package/dist/test/code-list-json5.test.d.ts +2 -0
- package/dist/test/code-list-json5.test.d.ts.map +1 -0
- package/dist/test/code-list-json5.test.js +84 -0
- package/dist/test/code-list.test.d.ts +2 -0
- package/dist/test/code-list.test.d.ts.map +1 -0
- package/dist/test/code-list.test.js +39 -0
- package/dist/test/gap-inference.test.d.ts +2 -0
- package/dist/test/gap-inference.test.d.ts.map +1 -0
- package/dist/test/gap-inference.test.js +597 -0
- package/dist/test/generalization.test.d.ts +2 -0
- package/dist/test/generalization.test.d.ts.map +1 -0
- package/dist/test/generalization.test.js +73 -0
- package/dist/test/material-inventory.test.d.ts +2 -0
- package/dist/test/material-inventory.test.d.ts.map +1 -0
- package/dist/test/material-inventory.test.js +155 -0
- package/dist/test/observable-chain.test.d.ts +2 -0
- package/dist/test/observable-chain.test.d.ts.map +1 -0
- package/dist/test/observable-chain.test.js +123 -0
- package/dist/test/page-structure.test.d.ts +2 -0
- package/dist/test/page-structure.test.d.ts.map +1 -0
- package/dist/test/page-structure.test.js +45 -0
- package/dist/test/runtime/ai-runtime-trace.test.d.ts +2 -0
- package/dist/test/runtime/ai-runtime-trace.test.d.ts.map +1 -0
- package/dist/test/runtime/ai-runtime-trace.test.js +78 -0
- package/dist/test/scenario-inference.test.d.ts +2 -0
- package/dist/test/scenario-inference.test.d.ts.map +1 -0
- package/dist/test/scenario-inference.test.js +73 -0
- package/dist/test/stage-context.test.d.ts +2 -0
- package/dist/test/stage-context.test.d.ts.map +1 -0
- package/dist/test/stage-context.test.js +395 -0
- package/dist/test/testcase/testcase-markdown.test.d.ts +2 -0
- package/dist/test/testcase/testcase-markdown.test.d.ts.map +1 -0
- package/dist/test/testcase/testcase-markdown.test.js +48 -0
- package/dist/test/testcase-generator.test.d.ts +2 -0
- package/dist/test/testcase-generator.test.d.ts.map +1 -0
- package/dist/test/testcase-generator.test.js +276 -0
- package/dist/test/testcase-skeleton.test.d.ts +2 -0
- package/dist/test/testcase-skeleton.test.d.ts.map +1 -0
- package/dist/test/testcase-skeleton.test.js +185 -0
- package/dist/test/testcase-spec-assembly.test.d.ts +2 -0
- package/dist/test/testcase-spec-assembly.test.d.ts.map +1 -0
- package/dist/test/testcase-spec-assembly.test.js +105 -0
- package/dist/test/util/credentials.test.d.ts +2 -0
- package/dist/test/util/credentials.test.d.ts.map +1 -0
- package/dist/test/util/credentials.test.js +64 -0
- package/dist/test/util/i18n-testcase.test.d.ts +2 -0
- package/dist/test/util/i18n-testcase.test.d.ts.map +1 -0
- package/dist/test/util/i18n-testcase.test.js +119 -0
- package/dist/test/util/softlink.test.d.ts +2 -0
- package/dist/test/util/softlink.test.d.ts.map +1 -0
- package/dist/test/util/softlink.test.js +82 -0
- package/dist/test/validation/contract.test.d.ts +2 -0
- package/dist/test/validation/contract.test.d.ts.map +1 -0
- package/dist/test/validation/contract.test.js +116 -0
- package/dist/test/validation/credentials.test.d.ts +2 -0
- package/dist/test/validation/credentials.test.d.ts.map +1 -0
- package/dist/test/validation/credentials.test.js +74 -0
- package/dist/test/validation/projects-index.test.d.ts +2 -0
- package/dist/test/validation/projects-index.test.d.ts.map +1 -0
- package/dist/test/validation/projects-index.test.js +48 -0
- package/dist/test/validation/testcase-v1-draft.test.d.ts +2 -0
- package/dist/test/validation/testcase-v1-draft.test.d.ts.map +1 -0
- package/dist/test/validation/testcase-v1-draft.test.js +173 -0
- package/dist/vitest.config.d.ts +3 -0
- package/dist/vitest.config.d.ts.map +1 -0
- package/dist/vitest.config.js +7 -0
- package/docs/README.md +124 -124
- package/examples/schemeresource-observable-docs/schemeresource.context.md +20 -20
- package/examples/schemeresource.module-hints.json +38 -38
- package/examples/schemeresource.observable.code_list.md +37 -37
- package/examples/zwplace-observable-docs/zwplace.context.md +16 -16
- package/examples/zwplace-placecategory-validation.json +29 -29
- package/examples/zwplace.module-hints.json +69 -69
- package/examples/zwplace.observable.code_list.md +37 -37
- package/package.json +19 -13
- package/codex-skill/ep-stage/glue-generate-testcase/references/testcase-tpl.md +0 -72
- package/codex-skill/ep-stage/glue-run-test/scripts/extract-page-elements-eval.mjs +0 -233
- package/codex-skill/ep-stage/page-element-scan/SKILL.md +0 -91
- package/codex-skill/ep-stage/page-element-scan/evals/evals.json +0 -37
- package/codex-skill/ep-stage/page-element-scan/evals/fixtures/sample-list-page.html +0 -85
- package/codex-skill/ep-stage/page-element-scan/references/pagination-controls.md +0 -153
- package/codex-skill/ep-stage/page-element-scan/scripts/extract-page-elements-eval.mjs +0 -233
- package/codex-skill/ep-stage/page-element-scan/scripts/extract-page-elements.mjs +0 -298
- package/codex-skill/ep-stage/page-element-scan/scripts/scan-page.mjs +0 -749
- package/dist/src/cli/probe.d.ts +0 -46
- package/dist/src/cli/probe.d.ts.map +0 -1
- package/dist/src/cli/probe.js +0 -447
- package/dist/src/cli/run-report.d.ts +0 -46
- package/dist/src/cli/run-report.d.ts.map +0 -1
- package/dist/src/cli/run-report.js +0 -316
- package/dist/src/extractors/prd-extractor.d.ts +0 -70
- package/dist/src/extractors/prd-extractor.d.ts.map +0 -1
- package/dist/src/extractors/prd-extractor.js +0 -248
- package/dist/src/testcase/button-interaction-inference.d.ts +0 -37
- package/dist/src/testcase/button-interaction-inference.d.ts.map +0 -1
- package/dist/src/testcase/button-interaction-inference.js +0 -385
- package/dist/src/testcase/prd-button-mapper.d.ts +0 -72
- package/dist/src/testcase/prd-button-mapper.d.ts.map +0 -1
- package/dist/src/testcase/prd-button-mapper.js +0 -148
- package/dist/src/testcase/testcase-v2.d.ts +0 -50
- package/dist/src/testcase/testcase-v2.d.ts.map +0 -1
- package/dist/src/testcase/testcase-v2.js +0 -1
- package/docs/mvp-usage-guide.md +0 -298
|
@@ -24,8 +24,6 @@ export type AssembleInput = {
|
|
|
24
24
|
codeList: ExtractedCodeListJson5;
|
|
25
25
|
/** 待测列表页直达地址,由 skill 交互或运行配置显式传入。 */
|
|
26
26
|
directUrl?: string;
|
|
27
|
-
/** 源码根目录覆盖(可选),用于解析 pages.list.path。 */
|
|
28
|
-
sourceRoot?: string;
|
|
29
27
|
};
|
|
30
28
|
/** assembler 输出:契约 + 用例 + 推理过程账本(后两者供 generate-testcase 落盘)。 */
|
|
31
29
|
export type AssembleResult = {
|
|
@@ -34,31 +32,6 @@ export type AssembleResult = {
|
|
|
34
32
|
/** inference-trace 的 reasoningSummary + skeletonSlots 默认策略来源账本。 */
|
|
35
33
|
inferenceTrace: Record<string, unknown>;
|
|
36
34
|
};
|
|
37
|
-
/** glue 用例动作类型。 */
|
|
38
|
-
type GlueActionKind = 'create' | 'read' | 'update' | 'delete' | 'export';
|
|
39
|
-
/**
|
|
40
|
-
* 单条 CRUD 用例的阶段计划(用例装配与 spec 生成共用的单一事实来源)。
|
|
41
|
-
*/
|
|
42
|
-
export type CrudCaseStage = {
|
|
43
|
-
actionKind: GlueActionKind;
|
|
44
|
-
queryKeyLabel: string;
|
|
45
|
-
component: 'input' | 'listbox';
|
|
46
|
-
};
|
|
47
|
-
/**
|
|
48
|
-
* 按查询字段推导 CRUD 用例阶段序列。
|
|
49
|
-
*
|
|
50
|
-
* 规则:在新增表单中出现的查询字段(可写)产出完整 create/read/update/delete 四条;
|
|
51
|
-
* 不在表单中的字段(如系统管理的状态)只产出 read 一条。
|
|
52
|
-
* export 不在此计划内,由装配器单独追加。
|
|
53
|
-
*
|
|
54
|
-
* @param searchFields - 列表页查询字段(有序)。
|
|
55
|
-
* @param formFieldLabels - 新增表单字段标签集合。
|
|
56
|
-
* @returns 有序阶段计划,顺序与 buildGlueCases 产出的用例顺序一一对应。
|
|
57
|
-
*/
|
|
58
|
-
export declare function planCrudCaseStages(searchFields: Array<{
|
|
59
|
-
label: string;
|
|
60
|
-
component: 'input' | 'listbox';
|
|
61
|
-
}>, formFieldLabels: string[]): CrudCaseStage[];
|
|
62
35
|
/**
|
|
63
36
|
* 装配 contract + testcase + inference-trace 账本。
|
|
64
37
|
*
|
|
@@ -66,5 +39,4 @@ export declare function planCrudCaseStages(searchFields: Array<{
|
|
|
66
39
|
* @returns contract / testcase / inferenceTrace。
|
|
67
40
|
*/
|
|
68
41
|
export declare function assembleJson5ContractAndTestcase(input: AssembleInput): AssembleResult;
|
|
69
|
-
export {};
|
|
70
42
|
//# sourceMappingURL=contract-testcase-assembler.d.ts.map
|
|
@@ -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,
|
|
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,EAAY,MAAM,kCAAkC,CAAC;AACzF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,kCAAkC,CAAC;AAC/E,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAGrE,oBAAoB;AACpB,MAAM,MAAM,aAAa,GAAG;IAC1B,iCAAiC;IACjC,QAAQ,EAAE,sBAAsB,CAAC;IACjC,qCAAqC;IACrC,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,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;AAofF;;;;;GAKG;AACH,wBAAgB,gCAAgC,CAAC,KAAK,EAAE,aAAa,GAAG,cAAc,CAkKrF"}
|
|
@@ -15,10 +15,6 @@
|
|
|
15
15
|
* - locators:用 014 草案已确认的框架默认(MiniUI 默认规则 + text=确定 等),不进 unresolvedSlots
|
|
16
16
|
* - assertions.deletePolicy='success_delete'(MiniUI 默认删除成功策略)
|
|
17
17
|
*/
|
|
18
|
-
import { extractHtmlPage } from '../extractors/html-page.js';
|
|
19
|
-
import { inferButtonInteractions, } from './button-interaction-inference.js';
|
|
20
|
-
import { existsSync } from 'node:fs';
|
|
21
|
-
import path from 'node:path';
|
|
22
18
|
/**
|
|
23
19
|
* 框架默认 locator 策略(014 草案已确认,交接文档 §4.2)。
|
|
24
20
|
* 这些值不进 unresolvedSlots,在 inference-trace 记账「框架默认」。
|
|
@@ -100,180 +96,6 @@ function resolveFrameKeywords(codeList) {
|
|
|
100
96
|
function isDataKeyField(codeList, field) {
|
|
101
97
|
return (codeList.entities ?? []).some((e) => (e.fields ?? []).some((f) => f.name === field && f.dataKey));
|
|
102
98
|
}
|
|
103
|
-
/**
|
|
104
|
-
* 选择并返回可用的源码根目录。
|
|
105
|
-
*
|
|
106
|
-
* 优先级:sourceRootOverride -> process.env.EP_STAGE_SOURCE_ROOT -> codeList.sourceRoot.root
|
|
107
|
-
*/
|
|
108
|
-
function resolveSourceRoot(codeList, sourceRootOverride) {
|
|
109
|
-
const candidates = [
|
|
110
|
-
sourceRootOverride,
|
|
111
|
-
process.env.EP_STAGE_SOURCE_ROOT,
|
|
112
|
-
codeList.sourceRoot?.root,
|
|
113
|
-
].filter((item) => Boolean(item));
|
|
114
|
-
return candidates[0];
|
|
115
|
-
}
|
|
116
|
-
/**
|
|
117
|
-
* 解析页面 HTML 文件绝对路径。
|
|
118
|
-
*/
|
|
119
|
-
function resolvePageHtmlPath(codeList, page, sourceRootOverride) {
|
|
120
|
-
const sourceRoot = resolveSourceRoot(codeList, sourceRootOverride);
|
|
121
|
-
if (!sourceRoot || !page.path)
|
|
122
|
-
return undefined;
|
|
123
|
-
return path.resolve(sourceRoot, page.path);
|
|
124
|
-
}
|
|
125
|
-
/**
|
|
126
|
-
* 解析 pages[*].childGrids 中的 same_as_pages.<role>.childGrids 引用。
|
|
127
|
-
*/
|
|
128
|
-
function resolveChildGrids(page, pages) {
|
|
129
|
-
const raw = page.childGrids;
|
|
130
|
-
if (!raw || !Array.isArray(raw) || raw.length === 0)
|
|
131
|
-
return undefined;
|
|
132
|
-
if (raw.every((item) => typeof item === 'string')) {
|
|
133
|
-
const resolved = [];
|
|
134
|
-
for (const ref of raw) {
|
|
135
|
-
const match = ref.match(/^same_as_pages\.([^.]+)\.childGrids$/);
|
|
136
|
-
if (!match)
|
|
137
|
-
continue;
|
|
138
|
-
const role = match[1];
|
|
139
|
-
const targetPage = pages.find((p) => p.role === role);
|
|
140
|
-
if (!targetPage)
|
|
141
|
-
continue;
|
|
142
|
-
const targetGrids = resolveChildGrids(targetPage, pages);
|
|
143
|
-
if (targetGrids)
|
|
144
|
-
resolved.push(...targetGrids);
|
|
145
|
-
}
|
|
146
|
-
return resolved.length > 0 ? resolved : undefined;
|
|
147
|
-
}
|
|
148
|
-
return raw;
|
|
149
|
-
}
|
|
150
|
-
/**
|
|
151
|
-
* 将 HTML 中提取的子表列 editor.type 映射为 skeleton 组件类型。
|
|
152
|
-
* 缺失或未知时默认 input,避免上游 HTML 不规范时直接阻塞整条产线。
|
|
153
|
-
*/
|
|
154
|
-
function mapEditorTypeToComponent(editorType) {
|
|
155
|
-
if (editorType === 'combobox')
|
|
156
|
-
return 'listbox';
|
|
157
|
-
return 'input';
|
|
158
|
-
}
|
|
159
|
-
/**
|
|
160
|
-
* 构建子表操作所需的定位表达式。
|
|
161
|
-
*/
|
|
162
|
-
function buildChildGridLocators(grid, addAction, deleteAction) {
|
|
163
|
-
const label = grid.label;
|
|
164
|
-
const gridId = grid.id;
|
|
165
|
-
const buttonLocator = (action, defaultLabel) => {
|
|
166
|
-
const buttonLabel = action?.label ?? defaultLabel;
|
|
167
|
-
return `xpath=//div[@label='${label}']//a[contains(@class,'mini-button')][normalize-space()='${buttonLabel}']`;
|
|
168
|
-
};
|
|
169
|
-
return {
|
|
170
|
-
container: `xpath=//div[@label='${label}']`,
|
|
171
|
-
addRowButton: buttonLocator(addAction, '添加'),
|
|
172
|
-
deleteSelectedButton: buttonLocator(deleteAction, '删除选中'),
|
|
173
|
-
grid: `#${gridId}`,
|
|
174
|
-
editWrapInput: '.mini-grid-editwrap input.mini-textbox-input',
|
|
175
|
-
editWrapListboxTrigger: '.mini-grid-editwrap .mini-buttonedit-trigger',
|
|
176
|
-
editWrapListboxInput: '.mini-grid-editwrap input.mini-buttonedit-input',
|
|
177
|
-
rowCheckboxByIndex: `xpath=//*[@id='${gridId}']//tr[contains(@class,'mini-grid-row')][{{rowIndex}}]//td[contains(@class,'checkcolumn')]//span`,
|
|
178
|
-
cellByIndexAndField: `xpath=//*[@id='${gridId}']//tr[contains(@class,'mini-grid-row')][{{rowIndex}}]//td[{{colIndex}}]`,
|
|
179
|
-
cellValueByIndexAndField: `xpath=//*[@id='${gridId}']//tr[contains(@class,'mini-grid-row')][{{rowIndex}}]//td[{{colIndex}}]//div[contains(@class,'mini-grid-cell-inner')]`,
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
/**
|
|
183
|
-
* 推断子表操作方式。
|
|
184
|
-
* 优先级:code_list 显式 editMode > HTML 信号(allowCellEdit / editor 列) > 默认 none。
|
|
185
|
-
*/
|
|
186
|
-
function inferChildGridEditMode(grid, htmlGrid) {
|
|
187
|
-
if (grid.editMode) {
|
|
188
|
-
return grid.editMode;
|
|
189
|
-
}
|
|
190
|
-
if (!htmlGrid) {
|
|
191
|
-
// HTML 不可用时,根据 code_list 已有信息保守回退:
|
|
192
|
-
// 只要声明了列组件或动作,就默认按 cell_edit 处理,保持与旧行为兼容。
|
|
193
|
-
const hasEditableColumn = grid.columns.some((c) => c.component === 'input' || c.component === 'listbox');
|
|
194
|
-
return hasEditableColumn || (grid.actions && grid.actions.length > 0) ? 'cell_edit' : 'none';
|
|
195
|
-
}
|
|
196
|
-
const hasCellEditSignal = htmlGrid.allowCellEdit === true ||
|
|
197
|
-
htmlGrid.cellEditAction != null ||
|
|
198
|
-
htmlGrid.allowCellSelect === true ||
|
|
199
|
-
htmlGrid.editNextOnEnterKey === true ||
|
|
200
|
-
htmlGrid.oncellbeginedit === true ||
|
|
201
|
-
htmlGrid.onrowdblclick === true;
|
|
202
|
-
const hasEditableColumn = htmlGrid.dataColumns.some((c) => Boolean(c.editorType));
|
|
203
|
-
if (hasCellEditSignal || hasEditableColumn) {
|
|
204
|
-
return 'cell_edit';
|
|
205
|
-
}
|
|
206
|
-
const hasRowEditSignal = htmlGrid.rowActions.some((a) => /openEdit|openDetail|openWindow|edit/i.test(a.onclick ?? a.label)) || htmlGrid.onrowdblclick === true;
|
|
207
|
-
if (hasRowEditSignal) {
|
|
208
|
-
return 'row_button_dialog';
|
|
209
|
-
}
|
|
210
|
-
return 'none';
|
|
211
|
-
}
|
|
212
|
-
/**
|
|
213
|
-
* 从 code_list 的 pages[].childGrids 构建 contract.skeletonSlots.childGrids。
|
|
214
|
-
*
|
|
215
|
-
* 设计取舍:
|
|
216
|
-
* - 优先使用 code_list 中已手写的 component;缺失时读取对应 HTML 的 editor.type 推导。
|
|
217
|
-
* - 对 same_as_pages.<role>.childGrids 引用进行解析。
|
|
218
|
-
* - 同一 gridId 在多个页面出现时只保留第一份(通常是 add 页)。
|
|
219
|
-
*/
|
|
220
|
-
function buildChildGridSlots(codeList, sourceRootOverride) {
|
|
221
|
-
const pagesWithChildGrids = codeList.pages.filter((p) => Boolean(p.childGrids));
|
|
222
|
-
if (pagesWithChildGrids.length === 0)
|
|
223
|
-
return undefined;
|
|
224
|
-
const slots = [];
|
|
225
|
-
const seenGridIds = new Set();
|
|
226
|
-
const extractedPageCache = new Map();
|
|
227
|
-
for (const page of pagesWithChildGrids) {
|
|
228
|
-
const childGrids = resolveChildGrids(page, codeList.pages);
|
|
229
|
-
if (!childGrids)
|
|
230
|
-
continue;
|
|
231
|
-
let htmlPage;
|
|
232
|
-
const htmlPath = resolvePageHtmlPath(codeList, page, sourceRootOverride);
|
|
233
|
-
if (htmlPath && existsSync(htmlPath)) {
|
|
234
|
-
try {
|
|
235
|
-
if (!extractedPageCache.has(htmlPath)) {
|
|
236
|
-
extractedPageCache.set(htmlPath, extractHtmlPage(htmlPath));
|
|
237
|
-
}
|
|
238
|
-
htmlPage = extractedPageCache.get(htmlPath);
|
|
239
|
-
}
|
|
240
|
-
catch {
|
|
241
|
-
htmlPage = undefined;
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
for (const grid of childGrids) {
|
|
245
|
-
if (seenGridIds.has(grid.id))
|
|
246
|
-
continue;
|
|
247
|
-
seenGridIds.add(grid.id);
|
|
248
|
-
const htmlGrid = htmlPage?.grids.find((g) => g.id === grid.id);
|
|
249
|
-
const columns = grid.columns.map((col) => {
|
|
250
|
-
const htmlColumn = htmlGrid?.dataColumns.find((c) => c.field === col.field);
|
|
251
|
-
const component = col.component ?? mapEditorTypeToComponent(htmlColumn?.editorType);
|
|
252
|
-
return {
|
|
253
|
-
field: col.field,
|
|
254
|
-
label: col.label,
|
|
255
|
-
component,
|
|
256
|
-
required: col.required ?? false,
|
|
257
|
-
modelRef: col.modelRef ?? htmlColumn?.editorAction,
|
|
258
|
-
};
|
|
259
|
-
});
|
|
260
|
-
const addAction = grid.actions?.find((a) => a.actionName === 'addWindow' || a.label.includes('添加')) ?? grid.actions?.[0];
|
|
261
|
-
const deleteAction = grid.actions?.find((a) => a.actionName === 'deleteSelectedWindow' || a.label.includes('删除')) ?? grid.actions?.[1];
|
|
262
|
-
const editMode = inferChildGridEditMode(grid, htmlGrid);
|
|
263
|
-
slots.push({
|
|
264
|
-
gridId: grid.id,
|
|
265
|
-
label: grid.label,
|
|
266
|
-
pageRole: page.role === 'edit' ? 'edit' : 'add',
|
|
267
|
-
editMode,
|
|
268
|
-
columns,
|
|
269
|
-
addRowButtonLabel: addAction?.label,
|
|
270
|
-
deleteSelectedButtonLabel: deleteAction?.label,
|
|
271
|
-
locators: buildChildGridLocators(grid, addAction, deleteAction),
|
|
272
|
-
});
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
return slots.length > 0 ? slots : undefined;
|
|
276
|
-
}
|
|
277
99
|
/**
|
|
278
100
|
* 把 list 页 queryFields 映射成 skeletonSlots.crud.searchConditions(input/listbox 二分)。
|
|
279
101
|
*
|
|
@@ -282,9 +104,7 @@ function buildChildGridSlots(codeList, sourceRootOverride) {
|
|
|
282
104
|
* @returns 对齐 StageSkeletonCrudSearchConditionSlot 的槽位数组。
|
|
283
105
|
*/
|
|
284
106
|
function buildSearchConditionSlots(codeList, listPage, addOrEditPage) {
|
|
285
|
-
const addFormLabels = new Set((addOrEditPage?.formFields ?? [])
|
|
286
|
-
.filter((f) => typeof f !== 'string')
|
|
287
|
-
.map((f) => f.label));
|
|
107
|
+
const addFormLabels = new Set((addOrEditPage?.formFields ?? []).map((f) => f.label));
|
|
288
108
|
return (listPage.queryFields ?? []).map((fieldInfo) => {
|
|
289
109
|
const field = mapQueryFieldToField(fieldInfo);
|
|
290
110
|
const component = normalizeQueryComponent(fieldInfo);
|
|
@@ -359,29 +179,6 @@ function normalizeQueryComponent(fieldInfo) {
|
|
|
359
179
|
return 'input';
|
|
360
180
|
return 'listbox';
|
|
361
181
|
}
|
|
362
|
-
/**
|
|
363
|
-
* 按查询字段推导 CRUD 用例阶段序列。
|
|
364
|
-
*
|
|
365
|
-
* 规则:在新增表单中出现的查询字段(可写)产出完整 create/read/update/delete 四条;
|
|
366
|
-
* 不在表单中的字段(如系统管理的状态)只产出 read 一条。
|
|
367
|
-
* export 不在此计划内,由装配器单独追加。
|
|
368
|
-
*
|
|
369
|
-
* @param searchFields - 列表页查询字段(有序)。
|
|
370
|
-
* @param formFieldLabels - 新增表单字段标签集合。
|
|
371
|
-
* @returns 有序阶段计划,顺序与 buildGlueCases 产出的用例顺序一一对应。
|
|
372
|
-
*/
|
|
373
|
-
export function planCrudCaseStages(searchFields, formFieldLabels) {
|
|
374
|
-
const stages = [];
|
|
375
|
-
for (const field of searchFields) {
|
|
376
|
-
if (formFieldLabels.includes(field.label)) {
|
|
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 });
|
|
378
|
-
}
|
|
379
|
-
else {
|
|
380
|
-
stages.push({ actionKind: 'read', queryKeyLabel: field.label, component: field.component });
|
|
381
|
-
}
|
|
382
|
-
}
|
|
383
|
-
return stages;
|
|
384
|
-
}
|
|
385
182
|
/**
|
|
386
183
|
* 生成正式用例编号。
|
|
387
184
|
*
|
|
@@ -507,8 +304,8 @@ function buildGlueExecutionFields(actionKind, queryKeyLabel, queryComponent, que
|
|
|
507
304
|
};
|
|
508
305
|
}
|
|
509
306
|
/**
|
|
510
|
-
* 装配 glue cases
|
|
511
|
-
*
|
|
307
|
+
* 装配 10 条 glue cases(placename/placecategory 各 CRUD + status.read + export.all),
|
|
308
|
+
* 与交接文档 §4.3 一致。
|
|
512
309
|
*
|
|
513
310
|
* @param moduleId - 模块标识(用作 caseId 前缀)。
|
|
514
311
|
* @param searchFieldLabels - 列表页查询字段标签集合。
|
|
@@ -529,110 +326,51 @@ function buildGlueCases(moduleName, searchFields, formFieldLabels, sequence, dat
|
|
|
529
326
|
reviewStatus: 'confirmed',
|
|
530
327
|
...buildGlueExecutionFields(actionKind, queryKeyLabel, componentForLabel(queryKeyLabel), queryKeyInForm(queryKeyLabel), searchFieldLabels, dataKeyLabel),
|
|
531
328
|
});
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
329
|
+
return [
|
|
330
|
+
mk('create', '场所名称', 'covered'),
|
|
331
|
+
mk('read', '场所名称', 'covered'),
|
|
332
|
+
mk('update', '场所名称', 'covered'),
|
|
333
|
+
mk('delete', '场所名称', 'covered'),
|
|
334
|
+
mk('create', '场所分类', 'covered'),
|
|
335
|
+
mk('read', '场所分类', 'partial'),
|
|
336
|
+
mk('update', '场所分类', 'covered'),
|
|
337
|
+
mk('delete', '场所分类', 'covered'),
|
|
338
|
+
mk('read', '状态', 'partial'),
|
|
339
|
+
mk('export', dataKeyLabel ?? searchFieldLabels[0] ?? '不适用', 'covered'),
|
|
340
|
+
];
|
|
537
341
|
}
|
|
538
342
|
/**
|
|
539
|
-
*
|
|
540
|
-
*
|
|
541
|
-
* 每个子表生成 4 条用例:新增行、编辑行、删除行、必填校验。
|
|
542
|
-
* 删除行标记为 gap:子表行删除通常依赖 confirm 弹窗与后端接口,运行态稳定性差,
|
|
543
|
-
* 需要真实环境验证后才能提升为 covered。
|
|
343
|
+
* 装配 uncoveredCandidates:统一 generationStrategy='ai';
|
|
344
|
+
* 运行态阻断用 coverageStatus='blocked' 表达。
|
|
544
345
|
*
|
|
545
|
-
* @param childGrids - 子表槽位清单。
|
|
546
|
-
* @param firstRole - 首个角色(用作前置条件)。
|
|
547
|
-
* @param sequence - 用例编号上下文。
|
|
548
|
-
* @returns cases 数组(generationStrategy 全 glue)。
|
|
549
|
-
*/
|
|
550
|
-
function buildChildGridCases(childGrids, firstRole, sequence) {
|
|
551
|
-
const smokeEntry = `1. 使用角色:${firstRole}。 2. 完成系统登录并进入目标新增页面。`;
|
|
552
|
-
return childGrids.flatMap((grid) => {
|
|
553
|
-
const label = grid.label;
|
|
554
|
-
const addRowButtonLabel = grid.addRowButtonLabel ?? '添加';
|
|
555
|
-
const deleteSelectedButtonLabel = grid.deleteSelectedButtonLabel ?? '删除选中';
|
|
556
|
-
const requiredColumns = grid.columns.filter((c) => c.required);
|
|
557
|
-
const requiredLabels = requiredColumns.map((c) => c.label).join('、') || '必填字段';
|
|
558
|
-
const allColumnLabels = grid.columns.map((c) => c.label).join('、');
|
|
559
|
-
const canAddRow = Boolean(grid.addRowButtonLabel);
|
|
560
|
-
const canDeleteRow = Boolean(grid.deleteSelectedButtonLabel);
|
|
561
|
-
const canEditCell = grid.editMode === 'cell_edit';
|
|
562
|
-
const hasRequiredColumn = requiredColumns.length > 0;
|
|
563
|
-
const mk = (title, coverageStatus, precondition, steps, assertion) => ({
|
|
564
|
-
caseId: nextCaseId(sequence),
|
|
565
|
-
title,
|
|
566
|
-
generationStrategy: 'glue',
|
|
567
|
-
coverageStatus,
|
|
568
|
-
reviewStatus: 'confirmed',
|
|
569
|
-
queryKey: '不适用',
|
|
570
|
-
precondition,
|
|
571
|
-
steps,
|
|
572
|
-
assertion,
|
|
573
|
-
});
|
|
574
|
-
const cases = [];
|
|
575
|
-
if (canAddRow) {
|
|
576
|
-
cases.push(mk(`${label}-新增行`, 'covered', smokeEntry, `1. 在新增页定位到「${label}」子表。 2. 点击子表工具栏的「${addRowButtonLabel}」按钮。 3. 在新增行中填写 ${allColumnLabels} 等字段。 4. 点击主表「保存并关闭」。 5. 观察保存结果。`, `保存后主表成功提交,子表新增数据随主表持久化;重新打开详情或编辑页可看到新增的行。`));
|
|
577
|
-
}
|
|
578
|
-
if (canEditCell) {
|
|
579
|
-
cases.push(mk(`${label}-编辑行`, 'covered', `${smokeEntry} 3. 子表中已存在至少一行数据。`, `1. 在新增页定位到「${label}」子表。 2. 选中或双击待编辑行进入编辑状态。 3. 修改 ${allColumnLabels} 等字段。 4. 点击主表「保存并关闭」。 5. 观察保存结果。`, `保存后子表修改内容持久化;重新打开详情或编辑页可看到更新后的值。`));
|
|
580
|
-
}
|
|
581
|
-
if (canDeleteRow) {
|
|
582
|
-
cases.push(mk(`${label}-删除行`, 'covered', `${smokeEntry} 3. 子表中已存在至少一行数据。`, `1. 在新增页定位到「${label}」子表。 2. 勾选要删除的数据行。 3. 点击子表工具栏的「${deleteSelectedButtonLabel}」按钮。 4. 在确认弹窗中点击「确定」。 5. 观察并记录最终反馈。`, `点击「${deleteSelectedButtonLabel}」并确认后,子表对应行应被删除;重新加载页面后该行数据不再存在。`));
|
|
583
|
-
}
|
|
584
|
-
if (canEditCell && hasRequiredColumn) {
|
|
585
|
-
cases.push(mk(`${label}-必填校验`, 'covered', smokeEntry, `1. 在新增页点击「${addRowButtonLabel}」新增一行。 2. 将 ${requiredLabels} 留空或选择空值。 3. 点击主表「保存并关闭」。 4. 观察校验提示。`, `保存时系统应阻断提交并提示「第N行${requiredLabels}不能为空!」等校验信息。`));
|
|
586
|
-
}
|
|
587
|
-
return cases;
|
|
588
|
-
});
|
|
589
|
-
}
|
|
590
|
-
/**
|
|
591
|
-
* 把 contract.scenario.gapCandidates 映射为 testcase.uncoveredCandidates。
|
|
592
|
-
* 当前仅处理控件类 custom 按钮,按 code_list 中真实的 label / customAction / backendMethod
|
|
593
|
-
* 生成精确步骤、前置条件与断言;若 button-interaction-inference 成功识别交互模式,
|
|
594
|
-
* 则使用对应的精确步骤/断言,否则回退到通用描述。
|
|
595
|
-
*
|
|
596
|
-
* @param gapCandidates - contract 中已推导的控件类 gap 候选。
|
|
597
346
|
* @param firstRole - 首个角色(用作 requiredRole)。
|
|
598
|
-
* @param sequence - 用例编号上下文。
|
|
599
|
-
* @param codeList - 解析后的 code_list,用于反查按钮原始元数据。
|
|
600
|
-
* @param buttonInferences - 按钮交互推理结果映射(label -> inference)。
|
|
601
347
|
*/
|
|
602
|
-
function buildUncoveredCandidates(
|
|
603
|
-
const
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
].join(' ');
|
|
620
|
-
const assertion = inference?.assertion
|
|
621
|
-
?? `点击「${label}」按钮后,页面应出现与该功能对应的可见响应(如提示信息、弹窗确认或数据刷新)。`;
|
|
622
|
-
return {
|
|
623
|
-
caseId: nextCaseId(sequence),
|
|
624
|
-
label,
|
|
625
|
-
generationStrategy: 'ai',
|
|
626
|
-
coverageStatus: 'gap',
|
|
627
|
-
requiredRole: firstRole,
|
|
628
|
-
businessIntent: `覆盖列表页「${label}」按钮的控件功能(customAction=${customAction})。`,
|
|
629
|
-
assertionExpectation: assertion,
|
|
630
|
-
queryKey: '不适用',
|
|
631
|
-
precondition: `1. 使用角色:${firstRole}。 2. 完成系统登录并进入目标待测页面。`,
|
|
632
|
-
steps,
|
|
633
|
-
assertion,
|
|
634
|
-
};
|
|
348
|
+
function buildUncoveredCandidates(firstRole, sequence) {
|
|
349
|
+
const mk = (label, coverageStatus, businessIntent, assertionExpectation) => ({
|
|
350
|
+
caseId: nextCaseId(sequence),
|
|
351
|
+
label,
|
|
352
|
+
generationStrategy: 'ai',
|
|
353
|
+
coverageStatus,
|
|
354
|
+
requiredRole: firstRole,
|
|
355
|
+
businessIntent,
|
|
356
|
+
assertionExpectation,
|
|
357
|
+
queryKey: '不适用',
|
|
358
|
+
precondition: `1. 使用角色:${firstRole}。 2. 完成系统登录并进入目标待测页面。`,
|
|
359
|
+
steps: [
|
|
360
|
+
'1. 由 AI Runtime Task 观察页面当前状态和可交互入口。',
|
|
361
|
+
`2. 围绕「${businessIntent}」做决策并执行页面动作。`,
|
|
362
|
+
'3. 记录观察、决策、行动和验证结果到 ai-runtime-trace.json5。',
|
|
363
|
+
].join(' '),
|
|
364
|
+
assertion: assertionExpectation,
|
|
635
365
|
});
|
|
366
|
+
return [
|
|
367
|
+
mk('状态流转(草稿→在用→归档)', 'gap', '覆盖在用不可删除等业务规则边界', '在用状态场所执行删除时应被业务规则阻断,提示拒绝信息'),
|
|
368
|
+
mk('同步窗口管理系统(custom 按钮)', 'gap', '覆盖 code_list.buttons 中 action=custom 的同步窗口操作', '点击同步窗口管理系统按钮后应触发后端 syncWindowSystem 并返回成功'),
|
|
369
|
+
mk('查看详情(rowAction=detail)', 'gap', '覆盖 rowActions 中查看详情入口', '点击查看按钮应打开只读详情页,展示窗口子表'),
|
|
370
|
+
mk('窗口子表嵌套 datagrid(add/edit 页内嵌)', 'gap', '覆盖 add/edit 页内嵌的窗口子表增删改', '窗口子表行应可增删改并随主表保存'),
|
|
371
|
+
mk('统计看板(无入口)', 'blocked', 'code_list.json5 标记 PRD 提到统计大屏,但本次出码未发现对应实现文件', '运行态推理若仍无入口则记录 blocked,不生成胶水骨架步骤'),
|
|
372
|
+
mk('移动端查询(无入口)', 'blocked', 'code_list.json5 标记 PRD 提到移动端查询,但本次出码未发现对应实现文件', '运行态推理若仍无入口则记录 blocked,不生成胶水骨架步骤'),
|
|
373
|
+
];
|
|
636
374
|
}
|
|
637
375
|
/**
|
|
638
376
|
* 按 code_list 里的系统与角色事实生成 testcase.testAccess 占位。
|
|
@@ -696,9 +434,7 @@ export function assembleJson5ContractAndTestcase(input) {
|
|
|
696
434
|
}));
|
|
697
435
|
const searchFieldLabels = searchFields.map((fieldInfo) => fieldInfo.label);
|
|
698
436
|
const addOrEditPage = codeList.pages.find((p) => p.role === 'add') ?? codeList.pages.find((p) => p.role === 'edit');
|
|
699
|
-
const formFieldLabels = (addOrEditPage?.formFields ?? [])
|
|
700
|
-
.filter((fieldInfo) => typeof fieldInfo !== 'string')
|
|
701
|
-
.map((fieldInfo) => fieldInfo.label);
|
|
437
|
+
const formFieldLabels = (addOrEditPage?.formFields ?? []).map((fieldInfo) => fieldInfo.label);
|
|
702
438
|
const dataKeyLabel = (listPage.queryFields ?? []).find((fieldInfo) => {
|
|
703
439
|
const field = mapQueryFieldToField(fieldInfo);
|
|
704
440
|
return isDataKeyField(codeList, field);
|
|
@@ -725,11 +461,21 @@ export function assembleJson5ContractAndTestcase(input) {
|
|
|
725
461
|
blockedDeleteMessage,
|
|
726
462
|
},
|
|
727
463
|
};
|
|
728
|
-
|
|
729
|
-
//
|
|
730
|
-
//
|
|
731
|
-
//
|
|
464
|
+
// 动态推导 gapCandidates(原为 014 专用硬编码 6 条),从 code_list 结构信号出发:
|
|
465
|
+
// - businessRules[scope 含 delete/create/update] → 状态流转类 gap
|
|
466
|
+
// - pages[list].buttons.action='custom' → 每个 custom 按钮独立 gap
|
|
467
|
+
// - rowActions 含 detail/openDetail 或 label 含"查看"/"详情" → 查看详情 gap
|
|
468
|
+
// - pages[add/edit].childGrids → 每个子表 gap
|
|
469
|
+
// 若 code_list 无对应信号则输出空数组。统计看板 / 移动端等 PRD-only 场景无稳定 code_list
|
|
470
|
+
// 提取路径,暂不进 gapCandidates(后续可通过 code_list.gapHints 让业务人工声明)。
|
|
732
471
|
const gapCandidates = [];
|
|
472
|
+
const stateRule = (codeList.businessRules ?? []).find((r) => {
|
|
473
|
+
const rec = r;
|
|
474
|
+
return Array.isArray(rec.scope) && rec.scope.some((s) => ['delete', 'create', 'update'].includes(s));
|
|
475
|
+
});
|
|
476
|
+
if (stateRule) {
|
|
477
|
+
gapCandidates.push({ gapId: 'businessState.transitions', label: '状态流转', generationStrategy: 'ai' });
|
|
478
|
+
}
|
|
733
479
|
(listPage.buttons ?? [])
|
|
734
480
|
.filter((b) => b.action === 'custom')
|
|
735
481
|
.forEach((btn) => {
|
|
@@ -740,6 +486,23 @@ export function assembleJson5ContractAndTestcase(input) {
|
|
|
740
486
|
generationStrategy: 'ai',
|
|
741
487
|
});
|
|
742
488
|
});
|
|
489
|
+
const detailRow = (listPage.rowActions ?? []).find((r) => {
|
|
490
|
+
const rec = r;
|
|
491
|
+
return rec.action === 'detail' || rec.actionName === 'openDetail'
|
|
492
|
+
|| (rec.label ?? '').includes('查看') || (rec.label ?? '').includes('详情');
|
|
493
|
+
});
|
|
494
|
+
if (detailRow) {
|
|
495
|
+
const anyRow = detailRow;
|
|
496
|
+
gapCandidates.push({ gapId: 'detail', label: anyRow.label ?? '查看详情', generationStrategy: 'ai' });
|
|
497
|
+
}
|
|
498
|
+
const childGrids = addOrEditPage?.childGrids ?? [];
|
|
499
|
+
childGrids.forEach((grid) => {
|
|
500
|
+
gapCandidates.push({
|
|
501
|
+
gapId: grid.id ?? grid.label ?? 'childGrid',
|
|
502
|
+
label: grid.label ? `${grid.label} 子表` : '子表',
|
|
503
|
+
generationStrategy: 'ai',
|
|
504
|
+
});
|
|
505
|
+
});
|
|
743
506
|
const contract = {
|
|
744
507
|
schemaVersion: 'ep-stage-contract/v1-draft',
|
|
745
508
|
contractId: `${module}-contract`,
|
|
@@ -758,7 +521,6 @@ export function assembleJson5ContractAndTestcase(input) {
|
|
|
758
521
|
skeletonSlots: {
|
|
759
522
|
crud: skeletonSlotsCrud,
|
|
760
523
|
export: { moduleId: module, label: '导出', mode: 'all' },
|
|
761
|
-
childGrids: childGridSlots,
|
|
762
524
|
},
|
|
763
525
|
gapCandidates,
|
|
764
526
|
},
|
|
@@ -766,12 +528,7 @@ export function assembleJson5ContractAndTestcase(input) {
|
|
|
766
528
|
};
|
|
767
529
|
const caseIdSequence = { pageIndex: 1, nextCaseIndex: 1 };
|
|
768
530
|
const cases = buildGlueCases(moduleName, searchFields, formFieldLabels, caseIdSequence, dataKeyLabel);
|
|
769
|
-
const
|
|
770
|
-
cases.push(...childGridCases);
|
|
771
|
-
const buttonInferences = inferButtonInteractions(codeList, {
|
|
772
|
-
sourceRootOverride: input.sourceRoot,
|
|
773
|
-
});
|
|
774
|
-
const uncoveredCandidates = buildUncoveredCandidates(gapCandidates, firstRole, caseIdSequence, codeList, buttonInferences);
|
|
531
|
+
const uncoveredCandidates = buildUncoveredCandidates(firstRole, caseIdSequence);
|
|
775
532
|
const testcase = {
|
|
776
533
|
schemaVersion: 'ep-stage-testcase/v1-draft',
|
|
777
534
|
testcaseId: `${module}-testcase`,
|
|
@@ -804,19 +561,8 @@ export function assembleJson5ContractAndTestcase(input) {
|
|
|
804
561
|
source: 'code-list-derived',
|
|
805
562
|
reason: 'placename 为 dataKey 用 timestamp_prefix;listbox 取首项作 value',
|
|
806
563
|
},
|
|
807
|
-
childGrids: {
|
|
808
|
-
source: 'html-derived',
|
|
809
|
-
reason: '从 add/edit 页 HTML 的 datagrid 列 editor.type 推导 component;locators 按 MiniUI 子表默认规则生成',
|
|
810
|
-
},
|
|
811
564
|
},
|
|
812
565
|
unresolvedSlotsCount: 0,
|
|
813
|
-
customButtonInferences: Array.from(buttonInferences.values()).map((inf) => ({
|
|
814
|
-
label: inf.label,
|
|
815
|
-
pattern: inf.pattern,
|
|
816
|
-
functionName: inf.functionName,
|
|
817
|
-
fallbackReason: inf.fallbackReason,
|
|
818
|
-
evidence: inf.evidence,
|
|
819
|
-
})),
|
|
820
566
|
};
|
|
821
567
|
return { contract, testcase, inferenceTrace };
|
|
822
568
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../../../src/validation/contract.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAsB,MAAM,kCAAkC,CAAC;AAcnG;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,GAAG,sBAAsB,
|
|
1
|
+
{"version":3,"file":"contract.d.ts","sourceRoot":"","sources":["../../../src/validation/contract.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,sBAAsB,EAAsB,MAAM,kCAAkC,CAAC;AAcnG;;;;;;;;;;GAUG;AACH,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,OAAO,GAAG,sBAAsB,CAuFrE"}
|
|
@@ -80,39 +80,10 @@ export function validateContract(obj) {
|
|
|
80
80
|
if (!slotsRec.export || typeof slotsRec.export !== 'object') {
|
|
81
81
|
throw new Error('contract.json5 缺 scenario.skeletonSlots.export');
|
|
82
82
|
}
|
|
83
|
-
if (!slotsRec.export || typeof slotsRec.export !== 'object') {
|
|
84
|
-
throw new Error('contract.json5 缺 scenario.skeletonSlots.export');
|
|
85
|
-
}
|
|
86
83
|
const exportSlot = slotsRec.export;
|
|
87
84
|
requireString(exportSlot.moduleId, 'scenario.skeletonSlots.export.moduleId');
|
|
88
85
|
requireString(exportSlot.label, 'scenario.skeletonSlots.export.label');
|
|
89
86
|
requireString(exportSlot.mode, 'scenario.skeletonSlots.export.mode');
|
|
90
|
-
// scenario.skeletonSlots.childGrids(可选)
|
|
91
|
-
if (slotsRec.childGrids !== undefined) {
|
|
92
|
-
if (!Array.isArray(slotsRec.childGrids)) {
|
|
93
|
-
throw new Error('contract.json5 scenario.skeletonSlots.childGrids 必须是数组');
|
|
94
|
-
}
|
|
95
|
-
for (const [i, grid] of slotsRec.childGrids.entries()) {
|
|
96
|
-
if (!grid || typeof grid !== 'object') {
|
|
97
|
-
throw new Error(`contract.json5 scenario.skeletonSlots.childGrids[${i}] 必须是对象`);
|
|
98
|
-
}
|
|
99
|
-
requireString(grid.gridId, `scenario.skeletonSlots.childGrids[${i}].gridId`);
|
|
100
|
-
requireString(grid.label, `scenario.skeletonSlots.childGrids[${i}].label`);
|
|
101
|
-
if (!Array.isArray(grid.columns)) {
|
|
102
|
-
throw new Error(`contract.json5 scenario.skeletonSlots.childGrids[${i}].columns 必须是数组`);
|
|
103
|
-
}
|
|
104
|
-
for (const [j, col] of grid.columns.entries()) {
|
|
105
|
-
if (!col || typeof col !== 'object') {
|
|
106
|
-
throw new Error(`contract.json5 scenario.skeletonSlots.childGrids[${i}].columns[${j}] 必须是对象`);
|
|
107
|
-
}
|
|
108
|
-
requireString(col.field, `scenario.skeletonSlots.childGrids[${i}].columns[${j}].field`);
|
|
109
|
-
requireString(col.label, `scenario.skeletonSlots.childGrids[${i}].columns[${j}].label`);
|
|
110
|
-
if (col.component !== 'input' && col.component !== 'listbox') {
|
|
111
|
-
throw new Error(`contract.json5 scenario.skeletonSlots.childGrids[${i}].columns[${j}].component 必须为 input 或 listbox`);
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
87
|
// scenario.gapCandidates[]
|
|
117
88
|
if (!Array.isArray(sc.gapCandidates)) {
|
|
118
89
|
throw new Error('contract.json5 缺 scenario.gapCandidates[]');
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate-testcase-json5.test.d.ts","sourceRoot":"","sources":["../../../test/cli/generate-testcase-json5.test.ts"],"names":[],"mappings":""}
|