@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
|
@@ -1,69 +1,69 @@
|
|
|
1
|
-
{
|
|
2
|
-
"dataKey": {
|
|
3
|
-
"selectedField": "placename",
|
|
4
|
-
"generationStrategy": "timestamp_prefix"
|
|
5
|
-
},
|
|
6
|
-
"searchConditions": [
|
|
7
|
-
{
|
|
8
|
-
"field": "placename",
|
|
9
|
-
"component": "input",
|
|
10
|
-
"seedValue": "自动化测试名称",
|
|
11
|
-
"strategy": "timestamp_prefix",
|
|
12
|
-
"updatePrefix": "修改"
|
|
13
|
-
},
|
|
14
|
-
{
|
|
15
|
-
"field": "placecategory",
|
|
16
|
-
"component": "listbox",
|
|
17
|
-
"value": "条线大厅",
|
|
18
|
-
"createSelections": [
|
|
19
|
-
{
|
|
20
|
-
"field": "placecategory",
|
|
21
|
-
"value": "条线大厅"
|
|
22
|
-
}
|
|
23
|
-
]
|
|
24
|
-
},
|
|
25
|
-
{
|
|
26
|
-
"field": "status",
|
|
27
|
-
"component": "listbox",
|
|
28
|
-
"value": "在用",
|
|
29
|
-
"createSelections": [
|
|
30
|
-
{
|
|
31
|
-
"field": "placecategory",
|
|
32
|
-
"value": "条线大厅"
|
|
33
|
-
}
|
|
34
|
-
]
|
|
35
|
-
}
|
|
36
|
-
],
|
|
37
|
-
"buttonAliases": {
|
|
38
|
-
"create": ["新增场所信息管理", "新增"],
|
|
39
|
-
"saveAndClose": ["保存并关闭"],
|
|
40
|
-
"search": ["搜索", "查询"],
|
|
41
|
-
"edit": ["修改", "编辑"],
|
|
42
|
-
"delete": ["删除选定"],
|
|
43
|
-
"confirm": ["确定"]
|
|
44
|
-
},
|
|
45
|
-
"autofill": {
|
|
46
|
-
"overrideFields": [
|
|
47
|
-
{
|
|
48
|
-
"field": "placename",
|
|
49
|
-
"strategy": "timestamp_prefix",
|
|
50
|
-
"value": "自动化测试场所名称"
|
|
51
|
-
}
|
|
52
|
-
]
|
|
53
|
-
},
|
|
54
|
-
"deletePolicy": "blocked_by_business_rule",
|
|
55
|
-
"assertionPolicy": {
|
|
56
|
-
"afterCreate": "record_exists",
|
|
57
|
-
"afterUpdate": "record_exists",
|
|
58
|
-
"afterDelete": "toast_message"
|
|
59
|
-
},
|
|
60
|
-
"customWorkflows": [
|
|
61
|
-
{
|
|
62
|
-
"label": "同步窗口管理系统",
|
|
63
|
-
"actionAlias": ["同步窗口管理系统"],
|
|
64
|
-
"expectedIntent": "同步窗口管理系统数据",
|
|
65
|
-
"expectedAssertion": "同步完成",
|
|
66
|
-
"include": true
|
|
67
|
-
}
|
|
68
|
-
]
|
|
69
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"dataKey": {
|
|
3
|
+
"selectedField": "placename",
|
|
4
|
+
"generationStrategy": "timestamp_prefix"
|
|
5
|
+
},
|
|
6
|
+
"searchConditions": [
|
|
7
|
+
{
|
|
8
|
+
"field": "placename",
|
|
9
|
+
"component": "input",
|
|
10
|
+
"seedValue": "自动化测试名称",
|
|
11
|
+
"strategy": "timestamp_prefix",
|
|
12
|
+
"updatePrefix": "修改"
|
|
13
|
+
},
|
|
14
|
+
{
|
|
15
|
+
"field": "placecategory",
|
|
16
|
+
"component": "listbox",
|
|
17
|
+
"value": "条线大厅",
|
|
18
|
+
"createSelections": [
|
|
19
|
+
{
|
|
20
|
+
"field": "placecategory",
|
|
21
|
+
"value": "条线大厅"
|
|
22
|
+
}
|
|
23
|
+
]
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"field": "status",
|
|
27
|
+
"component": "listbox",
|
|
28
|
+
"value": "在用",
|
|
29
|
+
"createSelections": [
|
|
30
|
+
{
|
|
31
|
+
"field": "placecategory",
|
|
32
|
+
"value": "条线大厅"
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
],
|
|
37
|
+
"buttonAliases": {
|
|
38
|
+
"create": ["新增场所信息管理", "新增"],
|
|
39
|
+
"saveAndClose": ["保存并关闭"],
|
|
40
|
+
"search": ["搜索", "查询"],
|
|
41
|
+
"edit": ["修改", "编辑"],
|
|
42
|
+
"delete": ["删除选定"],
|
|
43
|
+
"confirm": ["确定"]
|
|
44
|
+
},
|
|
45
|
+
"autofill": {
|
|
46
|
+
"overrideFields": [
|
|
47
|
+
{
|
|
48
|
+
"field": "placename",
|
|
49
|
+
"strategy": "timestamp_prefix",
|
|
50
|
+
"value": "自动化测试场所名称"
|
|
51
|
+
}
|
|
52
|
+
]
|
|
53
|
+
},
|
|
54
|
+
"deletePolicy": "blocked_by_business_rule",
|
|
55
|
+
"assertionPolicy": {
|
|
56
|
+
"afterCreate": "record_exists",
|
|
57
|
+
"afterUpdate": "record_exists",
|
|
58
|
+
"afterDelete": "toast_message"
|
|
59
|
+
},
|
|
60
|
+
"customWorkflows": [
|
|
61
|
+
{
|
|
62
|
+
"label": "同步窗口管理系统",
|
|
63
|
+
"actionAlias": ["同步窗口管理系统"],
|
|
64
|
+
"expectedIntent": "同步窗口管理系统数据",
|
|
65
|
+
"expectedAssertion": "同步完成",
|
|
66
|
+
"include": true
|
|
67
|
+
}
|
|
68
|
+
]
|
|
69
|
+
}
|
|
@@ -1,37 +1,37 @@
|
|
|
1
|
-
# 场所窗口信息管理代码清单
|
|
2
|
-
|
|
3
|
-
> 此文件是 `knowledge-project/epoint-web-v9.5.2/_docs/code_list.md` 的**可写镜像**。
|
|
4
|
-
> 真实上游 `knowledge-project/.../_docs/code_list.md` 是当前唯一真相源;本文件只用于开发期模拟交互补齐后的回写结果,以及只读真相源无法落盘时的 e2e 入口验证。
|
|
5
|
-
|
|
6
|
-
knowledgeRoot: ../../../knowledge-project/epoint-web-v9.5.2
|
|
7
|
-
menu: 场所窗口信息管理>场所窗口信息列表
|
|
8
|
-
|
|
9
|
-
---
|
|
10
|
-
|
|
11
|
-
## 已补齐入口字段
|
|
12
|
-
|
|
13
|
-
- `knowledgeRoot`:用于把 `code_list.md` 中的相对物料路径统一解析到真实上游物料根目录。
|
|
14
|
-
- `menu`:用于默认入口直达菜单导航,避免链路停在菜单候选 gate。
|
|
15
|
-
- `./zwplace.module-hints.json`:用于补齐 `ModuleHints`,避免 custom workflow 只停留在 candidate。
|
|
16
|
-
|
|
17
|
-
## 文档物料(模拟交互补齐后的可消费 docsDir)
|
|
18
|
-
|
|
19
|
-
- `./zwplace-observable-docs/zwplace.context.md`
|
|
20
|
-
|
|
21
|
-
## 前端文件
|
|
22
|
-
|
|
23
|
-
- `src/main/webapp/perpage/zwplace/gxhzwplacelist.html`
|
|
24
|
-
- `src/main/webapp/perpage/zwplace/gxhzwplaceadd.html`
|
|
25
|
-
- `src/main/webapp/perpage/zwplace/gxhzwplaceedit.html`
|
|
26
|
-
- `src/main/webapp/perpage/zwplace/gxhzwplacedetail.html`
|
|
27
|
-
|
|
28
|
-
## 后端文件
|
|
29
|
-
|
|
30
|
-
- `src/main/java/com/epoint/zwplace/action/GxhZwPlaceListAction.java`
|
|
31
|
-
- `src/main/java/com/epoint/zwplace/action/GxhZwPlaceAddAction.java`
|
|
32
|
-
- `src/main/java/com/epoint/zwplace/action/GxhZwPlaceEditAction.java`
|
|
33
|
-
- `src/main/java/com/epoint/zwplace/action/GxhZwPlaceDetailAction.java`
|
|
34
|
-
|
|
35
|
-
## 提示物料
|
|
36
|
-
|
|
37
|
-
- `./zwplace.module-hints.json`
|
|
1
|
+
# 场所窗口信息管理代码清单
|
|
2
|
+
|
|
3
|
+
> 此文件是 `knowledge-project/epoint-web-v9.5.2/_docs/code_list.md` 的**可写镜像**。
|
|
4
|
+
> 真实上游 `knowledge-project/.../_docs/code_list.md` 是当前唯一真相源;本文件只用于开发期模拟交互补齐后的回写结果,以及只读真相源无法落盘时的 e2e 入口验证。
|
|
5
|
+
|
|
6
|
+
knowledgeRoot: ../../../knowledge-project/epoint-web-v9.5.2
|
|
7
|
+
menu: 场所窗口信息管理>场所窗口信息列表
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## 已补齐入口字段
|
|
12
|
+
|
|
13
|
+
- `knowledgeRoot`:用于把 `code_list.md` 中的相对物料路径统一解析到真实上游物料根目录。
|
|
14
|
+
- `menu`:用于默认入口直达菜单导航,避免链路停在菜单候选 gate。
|
|
15
|
+
- `./zwplace.module-hints.json`:用于补齐 `ModuleHints`,避免 custom workflow 只停留在 candidate。
|
|
16
|
+
|
|
17
|
+
## 文档物料(模拟交互补齐后的可消费 docsDir)
|
|
18
|
+
|
|
19
|
+
- `./zwplace-observable-docs/zwplace.context.md`
|
|
20
|
+
|
|
21
|
+
## 前端文件
|
|
22
|
+
|
|
23
|
+
- `src/main/webapp/perpage/zwplace/gxhzwplacelist.html`
|
|
24
|
+
- `src/main/webapp/perpage/zwplace/gxhzwplaceadd.html`
|
|
25
|
+
- `src/main/webapp/perpage/zwplace/gxhzwplaceedit.html`
|
|
26
|
+
- `src/main/webapp/perpage/zwplace/gxhzwplacedetail.html`
|
|
27
|
+
|
|
28
|
+
## 后端文件
|
|
29
|
+
|
|
30
|
+
- `src/main/java/com/epoint/zwplace/action/GxhZwPlaceListAction.java`
|
|
31
|
+
- `src/main/java/com/epoint/zwplace/action/GxhZwPlaceAddAction.java`
|
|
32
|
+
- `src/main/java/com/epoint/zwplace/action/GxhZwPlaceEditAction.java`
|
|
33
|
+
- `src/main/java/com/epoint/zwplace/action/GxhZwPlaceDetailAction.java`
|
|
34
|
+
|
|
35
|
+
## 提示物料
|
|
36
|
+
|
|
37
|
+
- `./zwplace.module-hints.json`
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@epoint-testtech/ep-stage-skill",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.5.0-alpha.2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "dist/src/index.js",
|
|
6
6
|
"types": "dist/src/index.d.ts",
|
|
@@ -9,7 +9,11 @@
|
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
11
|
"dist",
|
|
12
|
-
"codex-skill",
|
|
12
|
+
"codex-skill/ep-stage/glue-create-project",
|
|
13
|
+
"codex-skill/ep-stage/glue-generate-testcase",
|
|
14
|
+
"codex-skill/ep-stage/glue-run-test",
|
|
15
|
+
"codex-skill/ep-stage/recording-to-glue",
|
|
16
|
+
"codex-skill/ep-stage/scripts",
|
|
13
17
|
"docs",
|
|
14
18
|
"examples",
|
|
15
19
|
"SKILL.md"
|
|
@@ -19,6 +23,18 @@
|
|
|
19
23
|
"registry": "https://registry.npmjs.org/"
|
|
20
24
|
},
|
|
21
25
|
"description": "Agent skills and TypeScript tools for ep-stage glue-code workflows.",
|
|
26
|
+
"scripts": {
|
|
27
|
+
"typecheck": "tsc --noEmit",
|
|
28
|
+
"test": "vitest run",
|
|
29
|
+
"build": "tsc",
|
|
30
|
+
"prepare": "pnpm run build",
|
|
31
|
+
"prepack": "pnpm run build",
|
|
32
|
+
"generate:crud-contract": "node --import tsx src/cli/dev/extract-contract.ts",
|
|
33
|
+
"generate:playwright-tests": "node --import tsx src/cli/index.ts spec",
|
|
34
|
+
"run:gap-pipeline": "node --import tsx src/cli/run-gap-pipeline.ts",
|
|
35
|
+
"cli": "node --import tsx src/cli/index.ts",
|
|
36
|
+
"generate:testcase": "node --import tsx src/cli/index.ts testcase"
|
|
37
|
+
},
|
|
22
38
|
"dependencies": {
|
|
23
39
|
"cheerio": "^1.0.0",
|
|
24
40
|
"json5": "^2.2.3",
|
|
@@ -29,15 +45,5 @@
|
|
|
29
45
|
"tsx": "^4.20.0",
|
|
30
46
|
"typescript": "^6.0.3",
|
|
31
47
|
"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
48
|
}
|
|
43
|
-
}
|
|
49
|
+
}
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
<!--
|
|
2
|
-
testcase.md 渲染模板(AI 用)。
|
|
3
|
-
|
|
4
|
-
用法(由 glue-generate-testcase SKILL Step 6 驱动):
|
|
5
|
-
- 数据源:`<需求名>/testcase.json5`(cases 结构 = 事实源) + `<需求名>/.ep-stage/contract.json`(业务槽位)+ `<需求名>/credentials.json5`(环境信息)
|
|
6
|
-
- AI 按本模板渲染 testcase.md,重跑覆盖
|
|
7
|
-
- 忠实性约束:cases 数量 / caseId / title / reviewStatus / requiredRole 必须**逐字**取自 testcase.json5;步骤/断言/前置条件中的具体槽位标签(菜单/列表页/按钮文字/查询字段)必须**逐字**取自 contract.json,AI 只组织自然语言句式,不得新增/捏造 contract 没有的槽位
|
|
8
|
-
|
|
9
|
-
下面是格式骨架。`{{...}}` 是占位,AI 渲染时全部替换;不要把这段说明注释写进产出的 testcase.md。
|
|
10
|
-
-->
|
|
11
|
-
|
|
12
|
-
---
|
|
13
|
-
schemaVersion: {{testcase.schemaVersion}} # 取自 testcase.json5 顶层
|
|
14
|
-
scenario: {{testcase.scenario}} # 取自 testcase.json5 顶层
|
|
15
|
-
moduleId: {{testcase.moduleId}} # 取自 testcase.json5 顶层
|
|
16
|
-
contractVersion: {{contract.contractVersion}} # 取自 contract.json
|
|
17
|
-
---
|
|
18
|
-
|
|
19
|
-
# {{testcase.moduleName}}胶水测试用例
|
|
20
|
-
|
|
21
|
-
## 环境信息
|
|
22
|
-
|
|
23
|
-
- 登录地址:{{credentials[0].url}}
|
|
24
|
-
- 登录账号:{{credentials[0].username}}
|
|
25
|
-
- 登录密码:(已配置在 credentials.json5,不在此展示)
|
|
26
|
-
- 菜单路由:{{testcase.menuPath || "(未填写,待 testcase.json5 审阅补齐)"}}
|
|
27
|
-
- 列表页:{{contract.pages.list.title}}
|
|
28
|
-
- 查询字段:{{contract.flows.search.searchField.label}}
|
|
29
|
-
- 操作角色:{{testcase.requiredRoles[0].role}}
|
|
30
|
-
- 未解析槽位:{{contract.unresolvedSlots.length > 0 ? contract.unresolvedSlots.map(s => s.slotId).join("、") : "无"}}
|
|
31
|
-
|
|
32
|
-
## CRUD 冒烟用例
|
|
33
|
-
|
|
34
|
-
| 用例 ID | 标题 | 前置条件 | 步骤 | 断言 |
|
|
35
|
-
|---|---|---|---|---|
|
|
36
|
-
| {{case.caseId}} | {{case.title}} | {{case.preconditions}} | {{case.steps_joined}} | {{case.assertions_joined}} |
|
|
37
|
-
|
|
38
|
-
<!--
|
|
39
|
-
说明(仅供 AI 渲染时参考,不写入产出 md):
|
|
40
|
-
|
|
41
|
-
「步骤」「断言」「前置条件」列的填充规则——
|
|
42
|
-
|
|
43
|
-
cases[].steps / cases[].assertions 当前若为占位(如「执行 create 主流程(详见 .spec.ts)」、「验证 create 完成后业务断言」),
|
|
44
|
-
AI 应该**基于 contract.json 槽位组装具体的自然语言表述**:
|
|
45
|
-
|
|
46
|
-
- create 步骤(caseId 含 .create):`进入菜单 {testcase.menuPath || "<未填写菜单>"};确认列表页 {contract.pages.list.title};点击 {contract.flows.create.entryButton.label};填写 {contract.dataKey.label} 等字段;点击 {contract.flows.create.saveButton.label}`
|
|
47
|
-
- create 断言:`新增后可在 {contract.pages.list.title} 按 {contract.flows.search.searchField.label} 查询到记录`
|
|
48
|
-
- create 前置条件:`已登录并进入 {testcase.menuPath || "<未填写菜单>"}`
|
|
49
|
-
|
|
50
|
-
- read 步骤(caseId 含 .read):`进入菜单 {testcase.menuPath || "<未填写菜单>"};按查询字段:{contract.flows.search.searchField.label} 输入测试值;点击 {contract.flows.search.submitControl.label}`
|
|
51
|
-
- read 断言:`查询结果包含 {contract.flows.search.searchField.label} 对应的测试记录`
|
|
52
|
-
- read 前置条件:`已存在可查询测试数据`
|
|
53
|
-
|
|
54
|
-
- update 步骤(caseId 含 .update):`在 {contract.pages.list.title} 中定位测试记录;点击 {contract.flows.update.entryAction.label};修改 {contract.dataKey.label} 字段;点击 {contract.flows.update.saveButton.label}`
|
|
55
|
-
- update 断言:`修改后可按 {contract.flows.search.searchField.label} 查询到更新后的记录`
|
|
56
|
-
- update 前置条件:`已定位到待修改记录`
|
|
57
|
-
|
|
58
|
-
- delete 步骤(caseId 含 .delete):`在 {contract.pages.list.title} 中定位测试记录;点击 {contract.flows.delete.entryButton.label};按契约策略 {contract.flows.delete.expectedOutcome} 确认删除结果`
|
|
59
|
-
- delete 断言:`删除结果符合 contract.flows.delete.expectedOutcome={contract.flows.delete.expectedOutcome} 的预期`
|
|
60
|
-
- delete 前置条件:`已定位到待删除记录`
|
|
61
|
-
|
|
62
|
-
- 非 CRUD 用例(uncoveredCandidates 转入的 candidate / planned 用例):AI 按 testcase.json5 cases[].businessIntent + assertionExpectation 直译;缺时填「待人工补充」,不发挥
|
|
63
|
-
|
|
64
|
-
testcase.json5 的 cases[].evidence 不展示在 md 表格(人审不需要技术性溯源);
|
|
65
|
-
若 testcase.json5 cases 数量为 0,testcase.md 表格只保留表头,下方追加一段说明「本需求未生成任何用例,请审阅 .ep-stage/inference-trace.json 排查」。
|
|
66
|
-
-->
|
|
67
|
-
|
|
68
|
-
## 审阅引导
|
|
69
|
-
|
|
70
|
-
1. 表格中**步骤、断言列若仍含占位**(如「执行 X 主流程」),表示 contract.json 对应槽位证据不足;请检查 `.ep-stage/inference-trace.json` 的 `moduleHintsInference.unresolved` 与 `contract.json.unresolvedSlots`,补齐后重跑。
|
|
71
|
-
2. 如对推断步骤/断言不认可,**修改 `testcase.json5` 的 cases[] 字段**(非本 md 文件),重跑 `/glue-generate-testcase` 重新渲染本 md。
|
|
72
|
-
3. 凭据信息在 `credentials.json5`;登录密码不显示在本审阅视图。
|
|
@@ -1,233 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* extract-page-elements-eval.mjs
|
|
3
|
-
*
|
|
4
|
-
* 专供 playwright-cli eval 注入使用的浏览器端扫描内核。
|
|
5
|
-
* !!!此文件无任何 import/export,仅包含纯 JavaScript 函数。
|
|
6
|
-
* !!!不要在 Node.js 环境中 import 此文件,会报 SyntaxError。
|
|
7
|
-
*
|
|
8
|
-
* 用法(shell 注入):
|
|
9
|
-
* playwright-cli -s=$SESSION eval "< $(cat extract-page-elements-eval.mjs) ; print(JSON.stringify(extractPageElements()))"
|
|
10
|
-
*
|
|
11
|
-
* 函数签名:
|
|
12
|
-
* extractPageElements() 当前 frame 扫描,返回 { title, elements[] }
|
|
13
|
-
* extractAllFramesElementMap() 所有 frame(含子 iframe)聚合,返回 { [label]: { locator, controlType, visible } }
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
/* ─────────────────────────────────────────────────────────────────────────── */
|
|
17
|
-
|
|
18
|
-
function norm(s) { return (s || '').replace(/\s+/g, ' ').trim(); }
|
|
19
|
-
|
|
20
|
-
function isVisible(el) {
|
|
21
|
-
const st = window.getComputedStyle(el);
|
|
22
|
-
if (st.display === 'none' || st.visibility === 'hidden') return false;
|
|
23
|
-
const r = el.getBoundingClientRect();
|
|
24
|
-
return r.width > 0 && r.height > 0;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function isEnabled(el) {
|
|
28
|
-
const cls = el.className || '';
|
|
29
|
-
return !el.disabled
|
|
30
|
-
&& el.getAttribute('aria-disabled') !== 'true'
|
|
31
|
-
&& !String(cls).includes('mini-disabled')
|
|
32
|
-
&& !String(cls).includes('disabled');
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function fieldLabel(el) {
|
|
36
|
-
// 分页控件不在表单字段采集范围内
|
|
37
|
-
if (el.classList.contains('pagination-number')) return '';
|
|
38
|
-
if (el.closest('.mini-pagination, .pagination-pagerchange')) return '';
|
|
39
|
-
const wrapper = el.closest('[label]');
|
|
40
|
-
if (wrapper) return norm(wrapper.getAttribute('label'));
|
|
41
|
-
if (el.getAttribute('aria-label')) return norm(el.getAttribute('aria-label'));
|
|
42
|
-
if (el.id) {
|
|
43
|
-
const lab = document.querySelector('label[for="' + el.id + '"]');
|
|
44
|
-
if (lab) return norm(lab.innerText);
|
|
45
|
-
}
|
|
46
|
-
const containerLabel = el.closest('div, td').querySelector('label');
|
|
47
|
-
if (containerLabel) return norm(containerLabel.innerText).replace(/[::]$/, '');
|
|
48
|
-
const prev = el.closest('td, div').previousElementSibling;
|
|
49
|
-
if (prev && /label|td/.test(prev.tagName.toLowerCase())) return norm(prev.innerText).replace(/[::]$/, '');
|
|
50
|
-
return norm(el.placeholder || el.getAttribute('bind') || el.name || el.id || '').replace(/^[ ]+|[ ]+$/g, '') || '';
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function controlType(el) {
|
|
54
|
-
const cls = String(el.className || '');
|
|
55
|
-
const mini = cls.match(/mini-[a-z]+/);
|
|
56
|
-
if (mini) return mini[0];
|
|
57
|
-
if (el.tagName === 'SELECT') return 'html-select';
|
|
58
|
-
if (el.tagName === 'TEXTAREA') return 'html-textarea';
|
|
59
|
-
return 'html-' + (el.type || 'input');
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function hiddenInputLocator(el, label) {
|
|
63
|
-
const itemDiv = el.closest('.mini-radiobuttonlist-item, .mini-checkbox');
|
|
64
|
-
if (itemDiv && itemDiv.id) return '#' + itemDiv.id.replace(/[!\\#$%&'()*+,/:;<=>?@[\\]^`{|}~]/g, '\\$&');
|
|
65
|
-
if (label) {
|
|
66
|
-
const type = el.type === 'radio' ? 'mini-radiobuttonlist-item' : 'mini-checkbox';
|
|
67
|
-
return "xpath=//div[contains(@class,'" + type + "')][normalize-space()='" + label + "']";
|
|
68
|
-
}
|
|
69
|
-
return null;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function fieldLocator(el, label) {
|
|
73
|
-
const type = controlType(el);
|
|
74
|
-
const tag = el.tagName.toLowerCase();
|
|
75
|
-
if (label && type.startsWith('mini-')) {
|
|
76
|
-
const exact = "xpath=//div[@label='" + label + "']//" + tag + "[contains(@class,'" + type + "') or contains(@class,'" + type + "-input')]";
|
|
77
|
-
if (document.querySelector("div[label='" + label + "']")) return exact;
|
|
78
|
-
var bareLabel = label.replace(/\d+/g, '').replace(/\s+/g, ' ').trim();
|
|
79
|
-
return "xpath=//div[@label and contains(@label,'" + bareLabel.replace(/[^一-龥a-zA-Z]/g, '') + "')]//" + tag + "[contains(@class,'" + type + "') or contains(@class,'" + type + "-input')]";
|
|
80
|
-
}
|
|
81
|
-
if (el.id) {
|
|
82
|
-
if (el.id.includes('$')) {
|
|
83
|
-
var prev = hiddenInputLocator(el, label);
|
|
84
|
-
if (prev) return prev;
|
|
85
|
-
}
|
|
86
|
-
return '#' + el.id.replace(/[!\\#$%&'()*+,/:;<=>?@[\\]^`{|}~]/g, '\\$&');
|
|
87
|
-
}
|
|
88
|
-
if (el.name) return tag + '[name="' + el.name + '"]';
|
|
89
|
-
if (label) return "xpath=//div[@label='" + label + "']//" + tag;
|
|
90
|
-
return tag;
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function buttonLocator(el, label) {
|
|
94
|
-
var tag = el.tagName.toLowerCase();
|
|
95
|
-
var cls = String(el.className || '');
|
|
96
|
-
if (cls.includes('mini-button')) return 'xpath=//' + tag + "[contains(@class,'mini-button')][normalize-space()='" + label + "']";
|
|
97
|
-
if (cls.includes('cond-srh-btn')) return "xpath=//a[contains(@class,'cond-srh-btn')][normalize-space()='" + label + "']";
|
|
98
|
-
if (el.id) return '#' + el.id;
|
|
99
|
-
return 'role=button[name="' + label + '"]';
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
/* ─── extractPageElements() ─── */
|
|
103
|
-
|
|
104
|
-
function extractPageElements() {
|
|
105
|
-
var elements = [];
|
|
106
|
-
var seen = new Set();
|
|
107
|
-
function push(el) {
|
|
108
|
-
var key = el.type + '|' + el.label + '|' + el.locator;
|
|
109
|
-
if (seen.has(key)) return;
|
|
110
|
-
seen.add(key);
|
|
111
|
-
elements.push(el);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/* 按钮 */
|
|
115
|
-
document.querySelectorAll('a.mini-button, button, input[type=button], input[type=submit], input[type=reset], [role=button], a.cond-srh-btn, a.mini-tools, span.mini-tools').forEach(function(el) {
|
|
116
|
-
var label = norm(el.innerText || el.value || el.getAttribute('data-tooltip') || el.title || '');
|
|
117
|
-
if (!label) return;
|
|
118
|
-
push({ type: 'button', label: label, locator: buttonLocator(el, label), visible: isVisible(el), enabled: isEnabled(el) });
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
/* 表单字段 */
|
|
122
|
-
document.querySelectorAll('input:not([type=button]):not([type=submit]):not([type=reset]):not([type=hidden]), textarea, select').forEach(function(el) {
|
|
123
|
-
if (!isVisible(el) && el.closest('.mini-textbox, .mini-combobox, .mini-datepicker, .mini-spinner')) return;
|
|
124
|
-
if ((el.type === 'radio' || el.type === 'checkbox') && !isVisible(el)) return;
|
|
125
|
-
var label = fieldLabel(el);
|
|
126
|
-
if (!label) return;
|
|
127
|
-
push({ type: 'field', label: label, controlType: controlType(el), locator: fieldLocator(el, label), visible: isVisible(el), enabled: isEnabled(el) });
|
|
128
|
-
});
|
|
129
|
-
|
|
130
|
-
/* MiniUI RadioButtonList / CheckBox 独立控件 */
|
|
131
|
-
document.querySelectorAll('.mini-radiobuttonlist-item, .mini-checkbox').forEach(function(el) {
|
|
132
|
-
var input = el.querySelector('input[type=radio], input[type=checkbox]');
|
|
133
|
-
if (!input) return;
|
|
134
|
-
var label = norm(input.labels && input.labels[0] ? input.labels[0].innerText : el.innerText || '').replace(/[::]$/, '');
|
|
135
|
-
if (!label) return;
|
|
136
|
-
var numInput = el.querySelector('.mini-export-num');
|
|
137
|
-
if (numInput) label = label.replace(/\d+/g, '');
|
|
138
|
-
var isRadio = input.type === 'radio';
|
|
139
|
-
var itemId = el.id;
|
|
140
|
-
var locator = itemId ? ('#' + itemId.replace(/[!\\#$%&'()*+,/:;<=>?@[\\]^`{|}~]/g, '\\$&'))
|
|
141
|
-
: "xpath=//div[contains(@class,'" + (isRadio ? 'mini-radiobuttonlist-item' : 'mini-checkbox') + "')][contains(normalize-space(),'" + label + "')]";
|
|
142
|
-
push({ type: 'field', label: label, controlType: isRadio ? 'mini-radiobuttonlist' : 'mini-checkbox', locator: locator, visible: isVisible(el), enabled: isEnabled(el) });
|
|
143
|
-
});
|
|
144
|
-
|
|
145
|
-
/* 列表 */
|
|
146
|
-
document.querySelectorAll('.mini-datagrid').forEach(function(grid) {
|
|
147
|
-
var gridId = grid.id || 'datagrid';
|
|
148
|
-
var headers = [].map.call(grid.querySelectorAll('.mini-grid-headertxt, .mini-grid-headerText, th'), function(h) { return norm(h.innerText); }).filter(Boolean);
|
|
149
|
-
var rowActions = [].map.call(grid.querySelectorAll('[data-tooltip]'), function(a) { return norm(a.getAttribute('data-tooltip')); }).filter(Boolean);
|
|
150
|
-
push({ type: 'grid', label: gridId, locator: '#' + gridId, visible: isVisible(grid), enabled: true, columns: headers, rowActions: rowActions });
|
|
151
|
-
});
|
|
152
|
-
|
|
153
|
-
/* 标签页 */
|
|
154
|
-
document.querySelectorAll('.mini-tabs li, [role=tab]').forEach(function(el) {
|
|
155
|
-
var label = norm(el.innerText);
|
|
156
|
-
if (!label) return;
|
|
157
|
-
push({ type: 'tab', label: label, locator: 'role=tab[name="' + label + '"]', visible: isVisible(el), enabled: isEnabled(el) });
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
/* 普通链接 */
|
|
161
|
-
document.querySelectorAll('a[href]:not(.mini-button)').forEach(function(el) {
|
|
162
|
-
var label = norm(el.innerText);
|
|
163
|
-
if (!label || !isVisible(el)) return;
|
|
164
|
-
push({ type: 'link', label: label, locator: 'role=link[name="' + label + '"]', visible: true, enabled: isEnabled(el), href: el.getAttribute('href') });
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
/* MiniUI 分页控件 */
|
|
168
|
-
document.querySelectorAll('.mini-grid-pager').forEach(function(pager) {
|
|
169
|
-
var parentGrid = pager.closest('.mini-datagrid');
|
|
170
|
-
var gridId = (parentGrid && parentGrid.id) ? parentGrid.id : 'datagrid';
|
|
171
|
-
var prefix = parentGrid ? ('#' + gridId.replace(/[!\\#$%&'()*+,/:;<=>?@[\\]^`{|}~]/g, '\\$&')) : '';
|
|
172
|
-
|
|
173
|
-
/* 总条数文本 */
|
|
174
|
-
var pagerInfo = pager.querySelector('.pagination-pagerinfo');
|
|
175
|
-
if (pagerInfo) push({ type: 'pagination', label: norm(pagerInfo.innerText), locator: prefix + ' .pagination-pagerinfo', visible: true, enabled: true });
|
|
176
|
-
|
|
177
|
-
/* 分页按钮 */
|
|
178
|
-
var btns = pager.querySelectorAll('.pagination-buttons a');
|
|
179
|
-
btns.forEach(function(btn) {
|
|
180
|
-
var action = btn.getAttribute('action');
|
|
181
|
-
var isEllipsis = btn.classList.contains('pagination-button-ellipsis');
|
|
182
|
-
var label = norm(btn.innerText) || '';
|
|
183
|
-
var subType = '', suffix = '';
|
|
184
|
-
if (action === 'prev') { subType = 'pagination-prev'; label = '上一页'; suffix = '.pagination-buttons a[action=prev]'; }
|
|
185
|
-
else if (action === 'next') { subType = 'pagination-next'; label = '下一页'; suffix = '.pagination-buttons a[action=next]'; }
|
|
186
|
-
else if (isEllipsis || action === 'none') { subType = 'pagination-ellipsis'; label = '...'; suffix = '.pagination-button-ellipsis'; }
|
|
187
|
-
else { subType = 'pagination-page'; label = norm(btn.innerText); suffix = '.pagination-buttons a.pagination-button:not(.pagination-button-ellipsis)'; }
|
|
188
|
-
push({ type: 'pagination', subType: subType, label: label, locator: prefix + ' ' + suffix, visible: isVisible(btn), enabled: !btn.classList.contains('disabled') });
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
/* 页面尺寸下拉 */
|
|
192
|
-
var pageSizeBtn = pager.querySelector('.mini-pagination > span.mini-buttonedit');
|
|
193
|
-
if (pageSizeBtn && isVisible(pageSizeBtn)) {
|
|
194
|
-
var hiddenVal = pageSizeBtn.querySelector('input[type=hidden]');
|
|
195
|
-
push({ type: 'pagination', subType: 'pagination-size', label: '页面尺寸', locator: prefix + ' .mini-pagination > span.mini-buttonedit', value: hiddenVal ? hiddenVal.value : '', visible: true, enabled: true });
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/* 页码跳转输入框 */
|
|
199
|
-
var jumpInput = pager.querySelector('.pagination-pagerchange input.pagination-number');
|
|
200
|
-
if (jumpInput && isVisible(jumpInput)) {
|
|
201
|
-
push({ type: 'pagination', subType: 'pagination-jump', label: '跳至页', locator: prefix + ' .pagination-pagerchange input.pagination-number', visible: true, enabled: isEnabled(jumpInput) });
|
|
202
|
-
}
|
|
203
|
-
});
|
|
204
|
-
|
|
205
|
-
return { title: document.title, elements: elements };
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
/* ─── extractAllFramesElementMap() ─── */
|
|
209
|
-
|
|
210
|
-
function extractAllFramesElementMap() {
|
|
211
|
-
var result = {};
|
|
212
|
-
function merge(el) {
|
|
213
|
-
if (!el.label) return;
|
|
214
|
-
if (!result[el.label] || el.visible) result[el.label] = { locator: el.locator, controlType: el.controlType, visible: el.visible };
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
/* 主 frame */
|
|
218
|
-
try { extractPageElements().elements.forEach(merge); } catch(e) {}
|
|
219
|
-
|
|
220
|
-
/* 子 frame */
|
|
221
|
-
try {
|
|
222
|
-
for (var i = 0; i < window.frames.length; i++) {
|
|
223
|
-
try {
|
|
224
|
-
var fr = window.frames[i];
|
|
225
|
-
if (fr.extractPageElements) {
|
|
226
|
-
fr.extractPageElements().elements.forEach(merge);
|
|
227
|
-
}
|
|
228
|
-
} catch(e) {}
|
|
229
|
-
}
|
|
230
|
-
} catch(e) {}
|
|
231
|
-
|
|
232
|
-
return result;
|
|
233
|
-
}
|