@epoint-testtech/ep-stage-skill 0.0.10 → 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 -331
- 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,298 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* extract-page-elements.mjs
|
|
3
|
-
*
|
|
4
|
-
* 可在浏览器上下文直接运行的 DOM 扫描内核(无外部依赖)。
|
|
5
|
-
* 用途:
|
|
6
|
-
* 1. 被 scan-page.mjs import 复用扫描逻辑
|
|
7
|
-
* 2. 被 playwright-cli eval "<call>" 注入到已有 session 中实时采集 element map
|
|
8
|
-
*
|
|
9
|
-
* playwright-cli eval 使用示例(主 frame):
|
|
10
|
-
* playwright-cli -s=$SESSION eval "< $(cat extract-page-elements.mjs) ; print(JSON.stringify(extractPageElements()))"
|
|
11
|
-
*
|
|
12
|
-
* playwright-cli eval 使用示例(遍历所有 frame):
|
|
13
|
-
* playwright-cli -s=$SESSION eval "< $(cat extract-page-elements.mjs) ; print(JSON.stringify(extractAllFramesElementMap()))"
|
|
14
|
-
*
|
|
15
|
-
* 导出函数签名:
|
|
16
|
-
* extractPageElements() -> { title, elements[] } 当前 frame 扫描
|
|
17
|
-
* extractAllFramesElementMap() -> { [label]: { locator, controlType, visible } } 所有 frame 聚合
|
|
18
|
-
*/
|
|
19
|
-
|
|
20
|
-
/**
|
|
21
|
-
* 单 frame 扫描内核(与 scan-page.mjs 内部逻辑保持同步)。
|
|
22
|
-
*/
|
|
23
|
-
function extractPageElements() {
|
|
24
|
-
const norm = (s) => (s || '').replace(/\s+/g, ' ').trim();
|
|
25
|
-
|
|
26
|
-
function isVisible(el) {
|
|
27
|
-
const style = window.getComputedStyle(el);
|
|
28
|
-
if (style.display === 'none' || style.visibility === 'hidden') return false;
|
|
29
|
-
const rect = el.getBoundingClientRect();
|
|
30
|
-
return rect.width > 0 && rect.height > 0;
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
function isEnabled(el) {
|
|
34
|
-
const cls = el.className || '';
|
|
35
|
-
return !el.disabled
|
|
36
|
-
&& el.getAttribute('aria-disabled') !== 'true'
|
|
37
|
-
&& !String(cls).includes('mini-disabled')
|
|
38
|
-
&& !String(cls).includes('disabled');
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
function fieldLabel(el) {
|
|
42
|
-
// 分页控件(页码输入框、页面尺寸下拉框):不在表单字段采集范围内
|
|
43
|
-
if (el.classList.contains('pagination-number')) return '';
|
|
44
|
-
if (el.closest('.mini-pagination, .pagination-pagerchange')) return '';
|
|
45
|
-
const wrapper = el.closest('[label]');
|
|
46
|
-
if (wrapper) return norm(wrapper.getAttribute('label'));
|
|
47
|
-
if (el.getAttribute('aria-label')) return norm(el.getAttribute('aria-label'));
|
|
48
|
-
if (el.id) {
|
|
49
|
-
const lab = document.querySelector(`label[for="${el.id}"]`);
|
|
50
|
-
if (lab) return norm(lab.innerText);
|
|
51
|
-
}
|
|
52
|
-
const containerLabel = el.closest('div, td')?.querySelector('label');
|
|
53
|
-
if (containerLabel) return norm(containerLabel.innerText).replace(/[::]$/, '');
|
|
54
|
-
const prev = el.closest('td, div')?.previousElementSibling;
|
|
55
|
-
if (prev && /label|td/.test(prev.tagName.toLowerCase())) return norm(prev.innerText).replace(/[::]$/, '');
|
|
56
|
-
return norm(el.placeholder || el.getAttribute('bind') || el.name || el.id || '').replace(/^[ ]+|[ ]+$/g, '') || '';
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
function controlType(el) {
|
|
60
|
-
const cls = String(el.className || '');
|
|
61
|
-
const mini = cls.match(/mini-[a-z]+/);
|
|
62
|
-
if (mini) return mini[0];
|
|
63
|
-
if (el.tagName === 'SELECT') return 'html-select';
|
|
64
|
-
if (el.tagName === 'TEXTAREA') return 'html-textarea';
|
|
65
|
-
return `html-${el.type || 'input'}`;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
function hiddenInputLocator(el, label) {
|
|
69
|
-
const itemDiv = el.closest('.mini-radiobuttonlist-item, .mini-checkbox');
|
|
70
|
-
if (itemDiv && itemDiv.id) return `#${CSS.escape(itemDiv.id)}`;
|
|
71
|
-
if (label) {
|
|
72
|
-
const type = el.type === 'radio' ? 'mini-radiobuttonlist-item' : 'mini-checkbox';
|
|
73
|
-
return `xpath=//div[contains(@class,'${type}')][normalize-space()='${label}']`;
|
|
74
|
-
}
|
|
75
|
-
return null;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function fieldLocator(el, label) {
|
|
79
|
-
const type = controlType(el);
|
|
80
|
-
const tag = el.tagName.toLowerCase();
|
|
81
|
-
if (label && type.startsWith('mini-')) {
|
|
82
|
-
const exact = `xpath=//div[@label='${label}']//${tag}[contains(@class,'${type}') or contains(@class,'${type}-input')]`;
|
|
83
|
-
if (document.querySelector(`div[label='${label}']`)) return exact;
|
|
84
|
-
const bareLabel = label.replace(/\d+/g, '').replace(/\s+/g, ' ').trim();
|
|
85
|
-
return `xpath=//div[@label and contains(@label,'${bareLabel.replace(/[^一-龥a-zA-Z]/g, '')}')]//${tag}[contains(@class,'${type}') or contains(@class,'${type}-input')]`;
|
|
86
|
-
}
|
|
87
|
-
if (el.id) {
|
|
88
|
-
if (el.id.includes('$')) {
|
|
89
|
-
const prev = hiddenInputLocator(el, label);
|
|
90
|
-
if (prev) return prev;
|
|
91
|
-
}
|
|
92
|
-
return `#${CSS.escape(el.id)}`;
|
|
93
|
-
}
|
|
94
|
-
if (el.name) return `${tag}[name="${el.name}"]`;
|
|
95
|
-
if (label) return `xpath=//div[@label='${label}']//${tag}`;
|
|
96
|
-
return tag;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
function buttonLocator(el, label) {
|
|
100
|
-
const tag = el.tagName.toLowerCase();
|
|
101
|
-
const cls = String(el.className || '');
|
|
102
|
-
if (cls.includes('mini-button')) return `xpath=//${tag}[contains(@class,'mini-button')][normalize-space()='${label}']`;
|
|
103
|
-
if (cls.includes('cond-srh-btn')) return `xpath=//a[contains(@class,'cond-srh-btn')][normalize-space()='${label}']`;
|
|
104
|
-
if (el.id) return `#${el.id}`;
|
|
105
|
-
return `role=button[name="${label}"]`;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const elements = [];
|
|
109
|
-
const seen = new Set();
|
|
110
|
-
function push(el) {
|
|
111
|
-
const key = `${el.type}|${el.label}|${el.locator}`;
|
|
112
|
-
if (seen.has(key)) return;
|
|
113
|
-
seen.add(key);
|
|
114
|
-
elements.push(el);
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
// 按钮
|
|
118
|
-
document.querySelectorAll(
|
|
119
|
-
'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',
|
|
120
|
-
).forEach((el) => {
|
|
121
|
-
const label = norm(el.innerText || el.value || el.getAttribute('data-tooltip') || el.title || '');
|
|
122
|
-
if (!label) return;
|
|
123
|
-
push({ type: 'button', label, locator: buttonLocator(el, label), visible: isVisible(el), enabled: isEnabled(el) });
|
|
124
|
-
});
|
|
125
|
-
|
|
126
|
-
// 表单字段
|
|
127
|
-
document.querySelectorAll(
|
|
128
|
-
'input:not([type=button]):not([type=submit]):not([type=reset]):not([type=hidden]), textarea, select',
|
|
129
|
-
).forEach((el) => {
|
|
130
|
-
if (!isVisible(el) && el.closest('.mini-textbox, .mini-combobox, .mini-datepicker, .mini-spinner')) return;
|
|
131
|
-
if ((el.type === 'radio' || el.type === 'checkbox') && !isVisible(el)) return;
|
|
132
|
-
const label = fieldLabel(el);
|
|
133
|
-
if (!label) return;
|
|
134
|
-
push({
|
|
135
|
-
type: 'field', label, controlType: controlType(el),
|
|
136
|
-
locator: fieldLocator(el, label),
|
|
137
|
-
visible: isVisible(el), enabled: isEnabled(el),
|
|
138
|
-
required: el.required || el.getAttribute('required') === 'true' || !!el.closest('.starred, [starred]'),
|
|
139
|
-
});
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
// MiniUI RadioButtonList / CheckBox 独立控件(父 div 可点击)
|
|
143
|
-
document.querySelectorAll('.mini-radiobuttonlist-item, .mini-checkbox').forEach((el) => {
|
|
144
|
-
const input = el.querySelector('input[type=radio], input[type=checkbox]');
|
|
145
|
-
if (!input) return;
|
|
146
|
-
let label = norm(input.labels?.[0]?.innerText || el.innerText || '').replace(/[::]$/, '');
|
|
147
|
-
if (!label) return;
|
|
148
|
-
const numInput = el.querySelector('.mini-export-num');
|
|
149
|
-
if (numInput) label = label.replace(/\d+/g, '');
|
|
150
|
-
const isRadio = input.type === 'radio';
|
|
151
|
-
const itemId = el.id;
|
|
152
|
-
const locator = itemId ? `#${CSS.escape(itemId)}`
|
|
153
|
-
: `xpath=//div[contains(@class,'${isRadio ? 'mini-radiobuttonlist-item' : 'mini-checkbox'})][contains(normalize-space(),'${label}')]`;
|
|
154
|
-
push({ type: 'field', label, controlType: isRadio ? 'mini-radiobuttonlist' : 'mini-checkbox', locator, visible: isVisible(el), enabled: isEnabled(el) });
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
// 列表
|
|
158
|
-
document.querySelectorAll('.mini-datagrid').forEach((grid) => {
|
|
159
|
-
const gridId = grid.id || 'datagrid';
|
|
160
|
-
const headers = [...grid.querySelectorAll('.mini-grid-headertxt, .mini-grid-headerText, th')].map((h) => norm(h.innerText)).filter(Boolean);
|
|
161
|
-
const rowActions = [...grid.querySelectorAll('[data-tooltip]')].map((a) => norm(a.getAttribute('data-tooltip'))).filter(Boolean);
|
|
162
|
-
push({ type: 'grid', label: gridId, locator: `#${gridId}`, visible: isVisible(grid), enabled: true, columns: headers, rowActions: [...new Set(rowActions)] });
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
// 标签页
|
|
166
|
-
document.querySelectorAll('.mini-tabs li, [role=tab]').forEach((el) => {
|
|
167
|
-
const label = norm(el.innerText);
|
|
168
|
-
if (!label) return;
|
|
169
|
-
push({ type: 'tab', label, locator: `role=tab[name="${label}"]`, visible: isVisible(el), enabled: isEnabled(el) });
|
|
170
|
-
});
|
|
171
|
-
|
|
172
|
-
// 普通链接
|
|
173
|
-
document.querySelectorAll('a[href]:not(.mini-button)').forEach((el) => {
|
|
174
|
-
const label = norm(el.innerText);
|
|
175
|
-
if (!label || !isVisible(el)) return;
|
|
176
|
-
push({ type: 'link', label, locator: `role=link[name="${label}"]`, visible: true, enabled: isEnabled(el), href: el.getAttribute('href') });
|
|
177
|
-
});
|
|
178
|
-
|
|
179
|
-
// MiniUI 分页控件
|
|
180
|
-
document.querySelectorAll('.mini-grid-pager').forEach((pager) => {
|
|
181
|
-
// 确定所属 grid 的 id
|
|
182
|
-
const parentGrid = pager.closest('.mini-datagrid');
|
|
183
|
-
const gridId = parentGrid?.id || 'datagrid';
|
|
184
|
-
const prefix = parentGrid ? `#${CSS.escape(gridId)}` : '';
|
|
185
|
-
|
|
186
|
-
// 总条数文本
|
|
187
|
-
const pagerInfo = pager.querySelector('.pagination-pagerinfo');
|
|
188
|
-
if (pagerInfo) {
|
|
189
|
-
const label = norm(pagerInfo.innerText); // 如 "共536条"
|
|
190
|
-
push({ type: 'pagination', label, locator: `${prefix} .pagination-pagerinfo`, visible: true, enabled: true });
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
// 分页按钮组
|
|
194
|
-
const btns = pager.querySelectorAll('.pagination-buttons a');
|
|
195
|
-
btns.forEach((btn) => {
|
|
196
|
-
const action = btn.getAttribute('action');
|
|
197
|
-
const isEllipsis = btn.classList.contains('pagination-button-ellipsis');
|
|
198
|
-
let label = norm(btn.innerText) || '';
|
|
199
|
-
let subType = '';
|
|
200
|
-
let locatorSuffix = '';
|
|
201
|
-
|
|
202
|
-
if (action === 'prev') {
|
|
203
|
-
subType = 'pagination-prev';
|
|
204
|
-
label = '上一页';
|
|
205
|
-
locatorSuffix = `.pagination-buttons a[action=prev]`;
|
|
206
|
-
} else if (action === 'next') {
|
|
207
|
-
subType = 'pagination-next';
|
|
208
|
-
label = '下一页';
|
|
209
|
-
locatorSuffix = `.pagination-buttons a[action=next]`;
|
|
210
|
-
} else if (action === 'none' || isEllipsis) {
|
|
211
|
-
subType = 'pagination-ellipsis';
|
|
212
|
-
label = '...';
|
|
213
|
-
locatorSuffix = `.pagination-button-ellipsis`;
|
|
214
|
-
} else {
|
|
215
|
-
// 页码数字按钮
|
|
216
|
-
subType = 'pagination-page';
|
|
217
|
-
label = norm(btn.innerText); // "1", "2", "49"
|
|
218
|
-
locatorSuffix = `.pagination-buttons a.pagination-button:not(.pagination-button-ellipsis)`;
|
|
219
|
-
}
|
|
220
|
-
push({
|
|
221
|
-
type: 'pagination',
|
|
222
|
-
subType,
|
|
223
|
-
label,
|
|
224
|
-
locator: `${prefix} ${locatorSuffix}`,
|
|
225
|
-
visible: isVisible(btn),
|
|
226
|
-
enabled: !btn.classList.contains('disabled'),
|
|
227
|
-
});
|
|
228
|
-
});
|
|
229
|
-
|
|
230
|
-
// 页面尺寸下拉(只采集可见的)
|
|
231
|
-
const pageSizeBtn = pager.querySelector('.mini-pagination > span.mini-buttonedit');
|
|
232
|
-
if (pageSizeBtn && isVisible(pageSizeBtn)) {
|
|
233
|
-
const hiddenVal = pageSizeBtn.querySelector('input[type=hidden]');
|
|
234
|
-
push({
|
|
235
|
-
type: 'pagination',
|
|
236
|
-
subType: 'pagination-size',
|
|
237
|
-
label: '页面尺寸',
|
|
238
|
-
locator: `${prefix} .mini-pagination > span.mini-buttonedit`,
|
|
239
|
-
value: hiddenVal?.value || '',
|
|
240
|
-
visible: true,
|
|
241
|
-
enabled: true,
|
|
242
|
-
});
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
// 页码跳转输入框
|
|
246
|
-
const jumpInput = pager.querySelector('.pagination-pagerchange input.pagination-number');
|
|
247
|
-
if (jumpInput && isVisible(jumpInput)) {
|
|
248
|
-
push({
|
|
249
|
-
type: 'pagination',
|
|
250
|
-
subType: 'pagination-jump',
|
|
251
|
-
label: '跳至页',
|
|
252
|
-
locator: `${prefix} .pagination-pagerchange input.pagination-number`,
|
|
253
|
-
visible: true,
|
|
254
|
-
enabled: isEnabled(jumpInput),
|
|
255
|
-
});
|
|
256
|
-
}
|
|
257
|
-
});
|
|
258
|
-
|
|
259
|
-
return { title: document.title, elements };
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
* 遍历所有 frame(含子 iframe),对每个 frame 执行 extractPageElements(),
|
|
264
|
-
* 聚合所有 elements,去重后以 { label → { locator, controlType, visible } } 返回。
|
|
265
|
-
* 专供 playwright-cli eval 在 O-D-A-V Act 之后使用:捕获弹窗/子表等动态新增元素。
|
|
266
|
-
*/
|
|
267
|
-
function extractAllFramesElementMap() {
|
|
268
|
-
const result = {};
|
|
269
|
-
// 主 frame
|
|
270
|
-
try {
|
|
271
|
-
const main = extractPageElements();
|
|
272
|
-
for (const el of main.elements) {
|
|
273
|
-
if (!result[el.label] || el.visible) result[el.label] = { locator: el.locator, controlType: el.controlType, visible: el.visible };
|
|
274
|
-
}
|
|
275
|
-
} catch (_) {}
|
|
276
|
-
|
|
277
|
-
// 子 frame(通过 window.frames 枚举)
|
|
278
|
-
try {
|
|
279
|
-
for (let i = 0; i < window.frames.length; i++) {
|
|
280
|
-
const fr = window.frames[i];
|
|
281
|
-
try {
|
|
282
|
-
const r = fr.extractPageElements ? fr.extractPageElements() : null;
|
|
283
|
-
if (!r) continue;
|
|
284
|
-
for (const el of r.elements) {
|
|
285
|
-
if (!result[el.label] || el.visible) result[el.label] = { locator: el.locator, controlType: el.controlType, visible: el.visible };
|
|
286
|
-
}
|
|
287
|
-
} catch (_) { /* 跨域 frame 跳过 */ }
|
|
288
|
-
}
|
|
289
|
-
} catch (_) {}
|
|
290
|
-
|
|
291
|
-
return result;
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
// Node.js 环境:CommonJS 导出(被 scan-page.mjs import)
|
|
295
|
-
// 浏览器 / playwright-cli eval 环境:全局函数直接可用
|
|
296
|
-
if (typeof module !== 'undefined' && module.exports) {
|
|
297
|
-
module.exports = { extractPageElements, extractAllFramesElementMap };
|
|
298
|
-
}
|