@epoint-testtech/ep-stage-skill 0.0.4-alpha.1 → 0.0.5
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 -206
- package/codex-skill/ep-stage/glue-generate-testcase/SKILL.md +275 -274
- 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 -112
- package/codex-skill/ep-stage/glue-generate-testcase/references/testcase-tpl.md +72 -72
- package/codex-skill/ep-stage/glue-run-test/SKILL.md +292 -289
- package/codex-skill/ep-stage/glue-run-test/references/ai-runtime-loop.md +30 -0
- package/codex-skill/ep-stage/glue-run-test/references/crud-pipeline.md +132 -145
- package/codex-skill/ep-stage/glue-run-test/references/gap-review-protocol.md +44 -43
- 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 +166 -0
- package/codex-skill/ep-stage/glue-run-test/scripts/extract-page-elements-eval.mjs +233 -0
- package/codex-skill/ep-stage/glue-run-test/scripts/generate-crud-spec.mjs +149 -149
- package/codex-skill/ep-stage/page-element-scan/SKILL.md +91 -0
- package/codex-skill/ep-stage/page-element-scan/evals/evals.json +37 -0
- package/codex-skill/ep-stage/page-element-scan/evals/fixtures/sample-list-page.html +85 -0
- package/codex-skill/ep-stage/page-element-scan/references/pagination-controls.md +153 -0
- package/codex-skill/ep-stage/page-element-scan/scripts/extract-page-elements-eval.mjs +233 -0
- package/codex-skill/ep-stage/page-element-scan/scripts/extract-page-elements.mjs +298 -0
- package/codex-skill/ep-stage/page-element-scan/scripts/scan-page.mjs +749 -0
- package/codex-skill/ep-stage/recording-to-glue/SKILL.md +28 -28
- package/codex-skill/ep-stage/scripts/validate-skill.mjs +98 -95
- package/dist/src/cli/generate-testcase.d.ts +17 -51
- package/dist/src/cli/generate-testcase.d.ts.map +1 -1
- package/dist/src/cli/generate-testcase.js +74 -181
- package/dist/src/cli/index.d.ts +3 -2
- package/dist/src/cli/index.d.ts.map +1 -1
- package/dist/src/cli/index.js +28 -15
- package/dist/src/cli/run.d.ts +136 -29
- package/dist/src/cli/run.d.ts.map +1 -1
- package/dist/src/cli/run.js +494 -65
- package/dist/src/cli/spec.d.ts +17 -13
- package/dist/src/cli/spec.d.ts.map +1 -1
- package/dist/src/cli/spec.js +59 -33
- package/dist/src/contracts/crud-business-module.d.ts +6 -0
- package/dist/src/contracts/crud-business-module.d.ts.map +1 -1
- package/dist/src/contracts/glue-contract-v1.d.ts +98 -0
- package/dist/src/contracts/glue-contract-v1.d.ts.map +1 -0
- package/dist/src/contracts/glue-contract-v1.js +9 -0
- package/dist/src/contracts/observable-chain.d.ts +4 -0
- package/dist/src/contracts/observable-chain.d.ts.map +1 -1
- package/dist/src/extractors/code-list-json5.d.ts +192 -0
- package/dist/src/extractors/code-list-json5.d.ts.map +1 -0
- package/dist/src/extractors/code-list-json5.js +91 -0
- package/dist/src/extractors/html-page.d.ts +9 -0
- package/dist/src/extractors/html-page.d.ts.map +1 -1
- package/dist/src/extractors/html-page.js +26 -2
- package/dist/src/extractors/prd-extractor.d.ts +70 -0
- package/dist/src/extractors/prd-extractor.d.ts.map +1 -0
- package/dist/src/extractors/prd-extractor.js +248 -0
- package/dist/src/generators/agent-inferred-workflow-script.js +66 -66
- package/dist/src/generators/stage-skeleton-script.d.ts +87 -20
- package/dist/src/generators/stage-skeleton-script.d.ts.map +1 -1
- package/dist/src/generators/stage-skeleton-script.js +647 -238
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -1
- package/dist/src/normalizers/crud-contract.d.ts +3 -0
- package/dist/src/normalizers/crud-contract.d.ts.map +1 -1
- package/dist/src/normalizers/crud-contract.js +3 -0
- package/dist/src/runtime/ai-runtime-trace.d.ts +88 -0
- package/dist/src/runtime/ai-runtime-trace.d.ts.map +1 -0
- package/dist/src/runtime/ai-runtime-trace.js +119 -0
- package/dist/src/testcase/button-interaction-inference.d.ts +37 -0
- package/dist/src/testcase/button-interaction-inference.d.ts.map +1 -0
- package/dist/src/testcase/button-interaction-inference.js +385 -0
- package/dist/src/testcase/contract-testcase-assembler.d.ts +44 -0
- package/dist/src/testcase/contract-testcase-assembler.d.ts.map +1 -0
- package/dist/src/testcase/contract-testcase-assembler.js +806 -0
- package/dist/src/testcase/prd-button-mapper.d.ts +72 -0
- package/dist/src/testcase/prd-button-mapper.d.ts.map +1 -0
- package/dist/src/testcase/prd-button-mapper.js +148 -0
- package/dist/src/testcase/runtime-context.d.ts +9 -0
- package/dist/src/testcase/runtime-context.d.ts.map +1 -0
- package/dist/src/testcase/runtime-context.js +1 -0
- package/dist/src/testcase/test-access.d.ts +38 -0
- package/dist/src/testcase/test-access.d.ts.map +1 -0
- package/dist/src/testcase/test-access.js +106 -0
- package/dist/src/testcase/testcase-execution-fields.d.ts +10 -0
- package/dist/src/testcase/testcase-execution-fields.d.ts.map +1 -0
- package/dist/src/testcase/testcase-execution-fields.js +1 -0
- package/dist/src/testcase/testcase-generator.d.ts.map +1 -1
- package/dist/src/testcase/testcase-generator.js +6 -0
- package/dist/src/testcase/testcase-markdown.d.ts +11 -0
- package/dist/src/testcase/testcase-markdown.d.ts.map +1 -0
- package/dist/src/testcase/testcase-markdown.js +181 -0
- package/dist/src/testcase/testcase-skeleton.d.ts +4 -0
- package/dist/src/testcase/testcase-skeleton.d.ts.map +1 -1
- package/dist/src/testcase/testcase-spec-assembly.d.ts +4 -0
- package/dist/src/testcase/testcase-spec-assembly.d.ts.map +1 -1
- package/dist/src/testcase/testcase-v1-draft.d.ts +58 -0
- package/dist/src/testcase/testcase-v1-draft.d.ts.map +1 -0
- package/dist/src/testcase/testcase-v1-draft.js +13 -0
- package/dist/src/validation/contract.d.ts +14 -0
- package/dist/src/validation/contract.d.ts.map +1 -0
- package/dist/src/validation/contract.js +132 -0
- package/dist/src/validation/credentials.d.ts +7 -2
- package/dist/src/validation/credentials.d.ts.map +1 -1
- package/dist/src/validation/credentials.js +15 -4
- package/dist/src/validation/index.d.ts +1 -0
- package/dist/src/validation/index.d.ts.map +1 -1
- package/dist/src/validation/index.js +1 -0
- package/dist/src/validation/testcase.d.ts +9 -7
- package/dist/src/validation/testcase.d.ts.map +1 -1
- package/dist/src/validation/testcase.js +181 -34
- package/docs/README.md +124 -134
- package/docs/mvp-usage-guide.md +298 -298
- 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 +2 -2
- package/codex-skill/ep-stage/.omc/state/sessions/d636ad35-5fc9-4d5e-85b0-2bcad7d86ee5/pre-tool-advisory-throttle.json +0 -10
- package/dist/test/cli/extract-contract.test.d.ts +0 -2
- package/dist/test/cli/extract-contract.test.d.ts.map +0 -1
- package/dist/test/cli/extract-contract.test.js +0 -32
- package/dist/test/cli/generate-testcase-inference.test.d.ts +0 -2
- package/dist/test/cli/generate-testcase-inference.test.d.ts.map +0 -1
- package/dist/test/cli/generate-testcase-inference.test.js +0 -63
- package/dist/test/cli/generate-testcase-paths.test.d.ts +0 -2
- package/dist/test/cli/generate-testcase-paths.test.d.ts.map +0 -1
- package/dist/test/cli/generate-testcase-paths.test.js +0 -165
- package/dist/test/cli/generate-testcase-requirements.test.d.ts +0 -2
- package/dist/test/cli/generate-testcase-requirements.test.d.ts.map +0 -1
- package/dist/test/cli/generate-testcase-requirements.test.js +0 -229
- package/dist/test/cli/generate-testcase.test.d.ts +0 -2
- package/dist/test/cli/generate-testcase.test.d.ts.map +0 -1
- package/dist/test/cli/generate-testcase.test.js +0 -130
- package/dist/test/cli/index.test.d.ts +0 -2
- package/dist/test/cli/index.test.d.ts.map +0 -1
- package/dist/test/cli/index.test.js +0 -113
- package/dist/test/cli/probe.test.d.ts +0 -2
- package/dist/test/cli/probe.test.d.ts.map +0 -1
- package/dist/test/cli/probe.test.js +0 -31
- package/dist/test/cli/run.test.d.ts +0 -2
- package/dist/test/cli/run.test.d.ts.map +0 -1
- package/dist/test/cli/run.test.js +0 -149
- package/dist/test/cli/spec-boundary.test.d.ts +0 -2
- package/dist/test/cli/spec-boundary.test.d.ts.map +0 -1
- package/dist/test/cli/spec-boundary.test.js +0 -198
- package/dist/test/cli/spec.test.d.ts +0 -2
- package/dist/test/cli/spec.test.d.ts.map +0 -1
- package/dist/test/cli/spec.test.js +0 -196
- package/dist/test/code-list.test.d.ts +0 -2
- package/dist/test/code-list.test.d.ts.map +0 -1
- package/dist/test/code-list.test.js +0 -39
- package/dist/test/crud-contract.test.d.ts +0 -2
- package/dist/test/crud-contract.test.d.ts.map +0 -1
- package/dist/test/crud-contract.test.js +0 -799
- package/dist/test/gap-inference.test.d.ts +0 -2
- package/dist/test/gap-inference.test.d.ts.map +0 -1
- package/dist/test/gap-inference.test.js +0 -597
- package/dist/test/generalization.test.d.ts +0 -2
- package/dist/test/generalization.test.d.ts.map +0 -1
- package/dist/test/generalization.test.js +0 -73
- package/dist/test/inference/module-hints-inference.test.d.ts +0 -2
- package/dist/test/inference/module-hints-inference.test.d.ts.map +0 -1
- package/dist/test/inference/module-hints-inference.test.js +0 -25
- package/dist/test/inference/source-analysis.test.d.ts +0 -2
- package/dist/test/inference/source-analysis.test.d.ts.map +0 -1
- package/dist/test/inference/source-analysis.test.js +0 -23
- package/dist/test/material-inventory.test.d.ts +0 -2
- package/dist/test/material-inventory.test.d.ts.map +0 -1
- package/dist/test/material-inventory.test.js +0 -154
- package/dist/test/observable-chain.test.d.ts +0 -2
- package/dist/test/observable-chain.test.d.ts.map +0 -1
- package/dist/test/observable-chain.test.js +0 -123
- package/dist/test/observable-pipeline.test.d.ts +0 -2
- package/dist/test/observable-pipeline.test.d.ts.map +0 -1
- package/dist/test/observable-pipeline.test.js +0 -470
- package/dist/test/page-structure.test.d.ts +0 -2
- package/dist/test/page-structure.test.d.ts.map +0 -1
- package/dist/test/page-structure.test.js +0 -45
- package/dist/test/scenario-inference.test.d.ts +0 -2
- package/dist/test/scenario-inference.test.d.ts.map +0 -1
- package/dist/test/scenario-inference.test.js +0 -73
- package/dist/test/stage-context.test.d.ts +0 -2
- package/dist/test/stage-context.test.d.ts.map +0 -1
- package/dist/test/stage-context.test.js +0 -395
- package/dist/test/testcase-generator.test.d.ts +0 -2
- package/dist/test/testcase-generator.test.d.ts.map +0 -1
- package/dist/test/testcase-generator.test.js +0 -276
- package/dist/test/testcase-skeleton.test.d.ts +0 -2
- package/dist/test/testcase-skeleton.test.d.ts.map +0 -1
- package/dist/test/testcase-skeleton.test.js +0 -185
- package/dist/test/testcase-spec-assembly.test.d.ts +0 -2
- package/dist/test/testcase-spec-assembly.test.d.ts.map +0 -1
- package/dist/test/testcase-spec-assembly.test.js +0 -105
- package/dist/test/util/credentials.test.d.ts +0 -2
- package/dist/test/util/credentials.test.d.ts.map +0 -1
- package/dist/test/util/credentials.test.js +0 -64
- package/dist/test/util/i18n-testcase.test.d.ts +0 -2
- package/dist/test/util/i18n-testcase.test.d.ts.map +0 -1
- package/dist/test/util/i18n-testcase.test.js +0 -119
- package/dist/test/util/softlink.test.d.ts +0 -2
- package/dist/test/util/softlink.test.d.ts.map +0 -1
- package/dist/test/util/softlink.test.js +0 -82
- package/dist/test/validation/credentials.test.d.ts +0 -2
- package/dist/test/validation/credentials.test.d.ts.map +0 -1
- package/dist/test/validation/credentials.test.js +0 -72
- package/dist/test/validation/projects-index.test.d.ts +0 -2
- package/dist/test/validation/projects-index.test.d.ts.map +0 -1
- package/dist/test/validation/projects-index.test.js +0 -48
- package/dist/test/validation/testcase.test.d.ts +0 -2
- package/dist/test/validation/testcase.test.d.ts.map +0 -1
- package/dist/test/validation/testcase.test.js +0 -129
- package/dist/vitest.config.d.ts +0 -3
- package/dist/vitest.config.d.ts.map +0 -1
- package/dist/vitest.config.js +0 -7
|
@@ -1,149 +1,149 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { existsSync, mkdirSync, readFileSync } from 'node:fs';
|
|
3
|
-
import path from 'node:path';
|
|
4
|
-
import { fileURLToPath } from 'node:url';
|
|
5
|
-
import { spawnSync } from 'node:child_process';
|
|
6
|
-
|
|
7
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
-
const skillRoot = path.resolve(__dirname, '..');
|
|
9
|
-
const packageRoot = path.resolve(skillRoot, '../..');
|
|
10
|
-
const repoRoot = path.resolve(packageRoot, '../..');
|
|
11
|
-
|
|
12
|
-
function help() {
|
|
13
|
-
console.log(`Usage:
|
|
14
|
-
node scripts/generate-crud-spec.mjs \\
|
|
15
|
-
--module-id <module-id> \\
|
|
16
|
-
--docs <docs-dir> \\
|
|
17
|
-
--code-list <code-list-md> \\
|
|
18
|
-
--webapp <html-dir> \\
|
|
19
|
-
--java-actions <java-action-dir> \\
|
|
20
|
-
--hints <module-hints-json> \\
|
|
21
|
-
--contract-out <package-output-json> \\
|
|
22
|
-
--spec-out <glue-project-spec-ts> \\
|
|
23
|
-
--menu <menu-path>
|
|
24
|
-
|
|
25
|
-
Notes:
|
|
26
|
-
- Relative upstream paths are resolved from the repository root.
|
|
27
|
-
- The contract path is passed to @epoint-testtech/ep-stage-skill relative to its package root.
|
|
28
|
-
- The script fails when unresolvedSlots is non-empty.
|
|
29
|
-
`);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function parseArgs(argv) {
|
|
33
|
-
const args = {};
|
|
34
|
-
for (let index = 0; index < argv.length; index += 2) {
|
|
35
|
-
const key = argv[index];
|
|
36
|
-
const value = argv[index + 1];
|
|
37
|
-
if (key === '--help' || key === '-h') {
|
|
38
|
-
help();
|
|
39
|
-
process.exit(0);
|
|
40
|
-
}
|
|
41
|
-
if (!key?.startsWith('--') || !value) {
|
|
42
|
-
throw new Error(`Invalid argument near ${key ?? '<empty>'}`);
|
|
43
|
-
}
|
|
44
|
-
args[key.slice(2)] = value;
|
|
45
|
-
}
|
|
46
|
-
return args;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
function resolveFromRepo(value) {
|
|
50
|
-
return path.isAbsolute(value) ? value : path.resolve(repoRoot, value);
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
function requirePath(label, value) {
|
|
54
|
-
if (!value) throw new Error(`Missing --${label}`);
|
|
55
|
-
const resolved = resolveFromRepo(value);
|
|
56
|
-
if (!existsSync(resolved)) throw new Error(`Path for --${label} does not exist: ${resolved}`);
|
|
57
|
-
return resolved;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function run(commandArgs) {
|
|
61
|
-
const result = spawnSync('pnpm', commandArgs, {
|
|
62
|
-
cwd: repoRoot,
|
|
63
|
-
stdio: 'inherit',
|
|
64
|
-
shell: false
|
|
65
|
-
});
|
|
66
|
-
if (result.status !== 0) {
|
|
67
|
-
process.exit(result.status ?? 1);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
const args = parseArgs(process.argv.slice(2));
|
|
72
|
-
const moduleId = args['module-id'];
|
|
73
|
-
if (!moduleId) throw new Error('Missing --module-id');
|
|
74
|
-
const docs = requirePath('docs', args.docs);
|
|
75
|
-
const codeList = requirePath('code-list', args['code-list']);
|
|
76
|
-
const webapp = requirePath('webapp', args.webapp);
|
|
77
|
-
const javaActions = requirePath('java-actions', args['java-actions']);
|
|
78
|
-
const hints = requirePath('hints', args.hints);
|
|
79
|
-
const contractOut = args['contract-out'] ?? `packages/ep-stage-skill/output/${moduleId}.crud.contract.json`;
|
|
80
|
-
const specOut = args['spec-out'];
|
|
81
|
-
const menu = args.menu;
|
|
82
|
-
if (!specOut) throw new Error('Missing --spec-out');
|
|
83
|
-
if (!menu) throw new Error('Missing --menu');
|
|
84
|
-
|
|
85
|
-
const contractAbs = resolveFromRepo(contractOut);
|
|
86
|
-
const contractForPackage = path.relative(packageRoot, contractAbs);
|
|
87
|
-
mkdirSync(path.dirname(contractAbs), { recursive: true });
|
|
88
|
-
|
|
89
|
-
run([
|
|
90
|
-
'--filter',
|
|
91
|
-
'@epoint-testtech/ep-stage-skill',
|
|
92
|
-
'generate:crud-contract',
|
|
93
|
-
'--',
|
|
94
|
-
'--module-id',
|
|
95
|
-
moduleId,
|
|
96
|
-
'--docs',
|
|
97
|
-
docs,
|
|
98
|
-
'--code-list',
|
|
99
|
-
codeList,
|
|
100
|
-
'--webapp',
|
|
101
|
-
webapp,
|
|
102
|
-
'--java-actions',
|
|
103
|
-
javaActions,
|
|
104
|
-
'--hints',
|
|
105
|
-
hints,
|
|
106
|
-
'--out',
|
|
107
|
-
contractForPackage
|
|
108
|
-
]);
|
|
109
|
-
|
|
110
|
-
const contract = JSON.parse(readFileSync(contractAbs, 'utf8'));
|
|
111
|
-
if (contract.unresolvedSlots?.length) {
|
|
112
|
-
console.error(JSON.stringify({
|
|
113
|
-
error: 'Contract has unresolvedSlots; update ModuleHints before generating a final spec.',
|
|
114
|
-
unresolvedSlots: contract.unresolvedSlots.map(({ slotId, reason, suggestedFormat }) => ({
|
|
115
|
-
slotId,
|
|
116
|
-
reason,
|
|
117
|
-
suggestedFormat
|
|
118
|
-
}))
|
|
119
|
-
}, null, 2));
|
|
120
|
-
process.exit(2);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
const specAbs = resolveFromRepo(specOut);
|
|
124
|
-
mkdirSync(path.dirname(specAbs), { recursive: true });
|
|
125
|
-
|
|
126
|
-
run([
|
|
127
|
-
'--filter',
|
|
128
|
-
'@epoint-testtech/ep-stage-skill',
|
|
129
|
-
'generate:playwright-tests',
|
|
130
|
-
'--',
|
|
131
|
-
'--contract',
|
|
132
|
-
contractForPackage,
|
|
133
|
-
'--out',
|
|
134
|
-
specAbs,
|
|
135
|
-
'--menu',
|
|
136
|
-
menu
|
|
137
|
-
]);
|
|
138
|
-
|
|
139
|
-
console.log(JSON.stringify({
|
|
140
|
-
module: contract.module,
|
|
141
|
-
searchConditions: contract.searchConditions?.map(({ field, label, component, value }) => ({
|
|
142
|
-
field,
|
|
143
|
-
label,
|
|
144
|
-
component,
|
|
145
|
-
value
|
|
146
|
-
})),
|
|
147
|
-
contract: contractAbs,
|
|
148
|
-
spec: specAbs
|
|
149
|
-
}, null, 2));
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync } from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { spawnSync } from 'node:child_process';
|
|
6
|
+
|
|
7
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const skillRoot = path.resolve(__dirname, '..');
|
|
9
|
+
const packageRoot = path.resolve(skillRoot, '../..');
|
|
10
|
+
const repoRoot = path.resolve(packageRoot, '../..');
|
|
11
|
+
|
|
12
|
+
function help() {
|
|
13
|
+
console.log(`Usage:
|
|
14
|
+
node scripts/generate-crud-spec.mjs \\
|
|
15
|
+
--module-id <module-id> \\
|
|
16
|
+
--docs <docs-dir> \\
|
|
17
|
+
--code-list <code-list-md> \\
|
|
18
|
+
--webapp <html-dir> \\
|
|
19
|
+
--java-actions <java-action-dir> \\
|
|
20
|
+
--hints <module-hints-json> \\
|
|
21
|
+
--contract-out <package-output-json> \\
|
|
22
|
+
--spec-out <glue-project-spec-ts> \\
|
|
23
|
+
--menu <menu-path>
|
|
24
|
+
|
|
25
|
+
Notes:
|
|
26
|
+
- Relative upstream paths are resolved from the repository root.
|
|
27
|
+
- The contract path is passed to @epoint-testtech/ep-stage-skill relative to its package root.
|
|
28
|
+
- The script fails when unresolvedSlots is non-empty.
|
|
29
|
+
`);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function parseArgs(argv) {
|
|
33
|
+
const args = {};
|
|
34
|
+
for (let index = 0; index < argv.length; index += 2) {
|
|
35
|
+
const key = argv[index];
|
|
36
|
+
const value = argv[index + 1];
|
|
37
|
+
if (key === '--help' || key === '-h') {
|
|
38
|
+
help();
|
|
39
|
+
process.exit(0);
|
|
40
|
+
}
|
|
41
|
+
if (!key?.startsWith('--') || !value) {
|
|
42
|
+
throw new Error(`Invalid argument near ${key ?? '<empty>'}`);
|
|
43
|
+
}
|
|
44
|
+
args[key.slice(2)] = value;
|
|
45
|
+
}
|
|
46
|
+
return args;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function resolveFromRepo(value) {
|
|
50
|
+
return path.isAbsolute(value) ? value : path.resolve(repoRoot, value);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function requirePath(label, value) {
|
|
54
|
+
if (!value) throw new Error(`Missing --${label}`);
|
|
55
|
+
const resolved = resolveFromRepo(value);
|
|
56
|
+
if (!existsSync(resolved)) throw new Error(`Path for --${label} does not exist: ${resolved}`);
|
|
57
|
+
return resolved;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function run(commandArgs) {
|
|
61
|
+
const result = spawnSync('pnpm', commandArgs, {
|
|
62
|
+
cwd: repoRoot,
|
|
63
|
+
stdio: 'inherit',
|
|
64
|
+
shell: false
|
|
65
|
+
});
|
|
66
|
+
if (result.status !== 0) {
|
|
67
|
+
process.exit(result.status ?? 1);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const args = parseArgs(process.argv.slice(2));
|
|
72
|
+
const moduleId = args['module-id'];
|
|
73
|
+
if (!moduleId) throw new Error('Missing --module-id');
|
|
74
|
+
const docs = requirePath('docs', args.docs);
|
|
75
|
+
const codeList = requirePath('code-list', args['code-list']);
|
|
76
|
+
const webapp = requirePath('webapp', args.webapp);
|
|
77
|
+
const javaActions = requirePath('java-actions', args['java-actions']);
|
|
78
|
+
const hints = requirePath('hints', args.hints);
|
|
79
|
+
const contractOut = args['contract-out'] ?? `packages/ep-stage-skill/output/${moduleId}.crud.contract.json`;
|
|
80
|
+
const specOut = args['spec-out'];
|
|
81
|
+
const menu = args.menu;
|
|
82
|
+
if (!specOut) throw new Error('Missing --spec-out');
|
|
83
|
+
if (!menu) throw new Error('Missing --menu');
|
|
84
|
+
|
|
85
|
+
const contractAbs = resolveFromRepo(contractOut);
|
|
86
|
+
const contractForPackage = path.relative(packageRoot, contractAbs);
|
|
87
|
+
mkdirSync(path.dirname(contractAbs), { recursive: true });
|
|
88
|
+
|
|
89
|
+
run([
|
|
90
|
+
'--filter',
|
|
91
|
+
'@epoint-testtech/ep-stage-skill',
|
|
92
|
+
'generate:crud-contract',
|
|
93
|
+
'--',
|
|
94
|
+
'--module-id',
|
|
95
|
+
moduleId,
|
|
96
|
+
'--docs',
|
|
97
|
+
docs,
|
|
98
|
+
'--code-list',
|
|
99
|
+
codeList,
|
|
100
|
+
'--webapp',
|
|
101
|
+
webapp,
|
|
102
|
+
'--java-actions',
|
|
103
|
+
javaActions,
|
|
104
|
+
'--hints',
|
|
105
|
+
hints,
|
|
106
|
+
'--out',
|
|
107
|
+
contractForPackage
|
|
108
|
+
]);
|
|
109
|
+
|
|
110
|
+
const contract = JSON.parse(readFileSync(contractAbs, 'utf8'));
|
|
111
|
+
if (contract.unresolvedSlots?.length) {
|
|
112
|
+
console.error(JSON.stringify({
|
|
113
|
+
error: 'Contract has unresolvedSlots; update ModuleHints before generating a final spec.',
|
|
114
|
+
unresolvedSlots: contract.unresolvedSlots.map(({ slotId, reason, suggestedFormat }) => ({
|
|
115
|
+
slotId,
|
|
116
|
+
reason,
|
|
117
|
+
suggestedFormat
|
|
118
|
+
}))
|
|
119
|
+
}, null, 2));
|
|
120
|
+
process.exit(2);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const specAbs = resolveFromRepo(specOut);
|
|
124
|
+
mkdirSync(path.dirname(specAbs), { recursive: true });
|
|
125
|
+
|
|
126
|
+
run([
|
|
127
|
+
'--filter',
|
|
128
|
+
'@epoint-testtech/ep-stage-skill',
|
|
129
|
+
'generate:playwright-tests',
|
|
130
|
+
'--',
|
|
131
|
+
'--contract',
|
|
132
|
+
contractForPackage,
|
|
133
|
+
'--out',
|
|
134
|
+
specAbs,
|
|
135
|
+
'--menu',
|
|
136
|
+
menu
|
|
137
|
+
]);
|
|
138
|
+
|
|
139
|
+
console.log(JSON.stringify({
|
|
140
|
+
module: contract.module,
|
|
141
|
+
searchConditions: contract.searchConditions?.map(({ field, label, component, value }) => ({
|
|
142
|
+
field,
|
|
143
|
+
label,
|
|
144
|
+
component,
|
|
145
|
+
value
|
|
146
|
+
})),
|
|
147
|
+
contract: contractAbs,
|
|
148
|
+
spec: specAbs
|
|
149
|
+
}, null, 2));
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: page-element-scan
|
|
3
|
+
description: 基于 Playwright 实时渲染识别页面中的可操作元素(按钮、表单字段、列表行操作、标签页、链接等),输出带定位符和可见/可用状态的元素清单(JSON + Markdown)。可与页面源 html 交叉比对,标注「静态有渲染无」「渲染有静态无」的差异证据。当用户想盘点页面元素、摸清页面有哪些按钮和字段、生成页面元素清单、为自动化测试/用例编写摸清页面结构时使用,即使用户只给了一个 URL 也应主动使用本技能。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# page-element-scan — 页面可操作元素识别
|
|
7
|
+
|
|
8
|
+
## 用途
|
|
9
|
+
|
|
10
|
+
对指定页面做一次「可操作元素盘点」:用 Playwright 真实渲染页面,提取所有用户可操作的元素,并为每个元素给出 Playwright 可用的定位符。
|
|
11
|
+
|
|
12
|
+
与静态读 html 源码的区别:运行时渲染才能回答「这个元素用户到底看不看得到、点不点得动」。 MiniUI 等框架会把声明式标记渲染成复杂 DOM,权限控制会让部分元素渲染时隐藏,脚本会动态创建新元素——这些只有渲染结果能告诉我们。如果用户同时提供了页面源 html,本技能会做双证据比对:
|
|
13
|
+
|
|
14
|
+
- `both`:源码声明且渲染确认 —— 最可信
|
|
15
|
+
- `runtime-only`:渲染时才出现(脚本动态创建)—— 静态分析会漏掉
|
|
16
|
+
- `source-only`:源码声明但渲染时不可见(权限隐藏/逻辑移除)—— 对最终用户不可操作
|
|
17
|
+
|
|
18
|
+
## 输入收集
|
|
19
|
+
|
|
20
|
+
开始扫描前,向用户确认以下信息(能从上下文推断就不要再问):
|
|
21
|
+
|
|
22
|
+
1. **目标 URL**(必填)。本地 html 文件用 `file:///` 绝对路径。
|
|
23
|
+
2. **登录方式**(如需要):账号密码,或已有的 Playwright storage-state 文件。若当前目录是胶水/e2e 项目且有 `.env`(含 `LOGIN_SYSTEM_URL` / `LOGIN_USERNAME` / `LOGIN_PASSWORD`),直接读取复用,不要问用户要密码。
|
|
24
|
+
3. **源 html 路径**(可选):有就给 `--source`,触发双证据比对。
|
|
25
|
+
4. **输出位置**:默认 `./page-elements/` 下,`<页面名>.json` + `<页面名>.md`。
|
|
26
|
+
|
|
27
|
+
## 执行步骤
|
|
28
|
+
|
|
29
|
+
1. 确定一个安装了 playwright 的工作目录作为 `--project-dir`:当前项目根、或 `e2e-test/` 下任一胶水项目(它们的 node_modules 里有 playwright)。脚本用 `createRequire` 从该目录解析依赖。
|
|
30
|
+
|
|
31
|
+
2. 运行扫描脚本(路径相对于本技能目录):
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
node <技能目录>/scripts/scan-page.mjs \
|
|
35
|
+
--url "<目标URL>" \
|
|
36
|
+
--out "<输出>.json" --md "<输出>.md" \
|
|
37
|
+
--project-dir "<含playwright的目录>"
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
按需追加:
|
|
41
|
+
- 登录:`--login-url <登录页> --login-user <账号> --login-pass <密码>`(启发式填用户名密码框);启发式失败时用 `--login-user-selector` / `--login-pass-selector` / `--login-submit-selector` 显式指定。
|
|
42
|
+
- 登录态复用:`--storage-state <state.json>`。
|
|
43
|
+
- 双证据:`--source <页面源html>`。
|
|
44
|
+
- 渲染不充分时加大 `--wait 3000`;调试时 `--headed` 看浏览器实际行为。
|
|
45
|
+
|
|
46
|
+
3. 脚本默认扫描主 frame 和所有子 frame(epoint 页面常嵌 iframe),每个元素的 `frame` 字段标注来源;跨域 frame 会自动跳过。
|
|
47
|
+
|
|
48
|
+
## 结果呈现
|
|
49
|
+
|
|
50
|
+
扫描完成后读 JSON,向用户汇报:
|
|
51
|
+
|
|
52
|
+
1. **概览**:元素总数、可操作数、按类型分布(button / field / grid / tab / link)。
|
|
53
|
+
2. **值得注意的发现**(这是盘点的价值所在,不要只报数字):
|
|
54
|
+
- 渲染时隐藏或禁用的元素(可能受权限/状态控制)
|
|
55
|
+
- `runtime-only` 元素(动态创建,静态分析发现不了)
|
|
56
|
+
- `source-only` 元素(声明了但用户看不到)
|
|
57
|
+
- grid 的行操作和列信息
|
|
58
|
+
3. 文件路径,提示 JSON 里的 `locator` 可直接用于 Playwright 脚本。
|
|
59
|
+
|
|
60
|
+
## 输出格式
|
|
61
|
+
|
|
62
|
+
JSON 结构:
|
|
63
|
+
|
|
64
|
+
```json
|
|
65
|
+
{
|
|
66
|
+
"url": "...", "title": "...", "scannedAt": "...",
|
|
67
|
+
"summary": { "total": 42, "byType": {"button": 8, "field": 12}, "actionable": 35,
|
|
68
|
+
"hiddenAtRuntime": 2, "runtimeOnly": 3, "sourceOnly": 1 },
|
|
69
|
+
"dialogs": [{"trigger": "openChild", "title": "新增窗口", "pagePath": "xxx.html"}],
|
|
70
|
+
"elements": [
|
|
71
|
+
{ "type": "button", "label": "新增", "locator": "xpath=//a[contains(@class,'mini-button')]...",
|
|
72
|
+
"visible": true, "enabled": true, "evidence": "both", "frame": "main" }
|
|
73
|
+
]
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
`dialogs` 仅 `--source` 模式产出(从 script 中解析 openTopDialog 调用)。
|
|
78
|
+
|
|
79
|
+
## 边界与须知
|
|
80
|
+
|
|
81
|
+
- 「可操作」判定 = 可见(非 display:none、有尺寸)且可用(非 disabled)。默认输出过滤不可见元素,`--include-hidden` 可保留。
|
|
82
|
+
- 脚本不做交互(不点按钮、不开弹窗)。弹窗/二级页面内的元素需把对应 URL 再扫一遍,或先用 `--headed` 观察交互路径。
|
|
83
|
+
- 登录启发式覆盖常见登录页;复杂登录(验证码、SSO 跳转)失败时,让用户手动登录后用 `--storage-state` 重试。
|
|
84
|
+
- 元素去重键是 `type|label|locator`,同 label 不同类型控件不会合并。
|
|
85
|
+
- **已知覆盖范围**(已验证于真实 epoint MiniUI 页面):
|
|
86
|
+
- 按钮:MiniUI `a.mini-button`、epoint `a.cond-srh-btn`(搜索按钮)、原生 `button`、`[role=button]`
|
|
87
|
+
- 字段:`div[label]` 包装的 MiniUI 控件(textbox/combobox/buttonedit/datepicker/spinner)、原生 `input/select/textarea`
|
|
88
|
+
- Grid:`mini-datagrid` 的 `.mini-grid-headertxt` 列名 + `[data-tooltip]` 行操作图标
|
|
89
|
+
- 标签页:`.mini-tabs li`
|
|
90
|
+
- 噪音过滤:无 label 的分页跳页输入框(`pagination-number`)等无业务意义输入不输出
|
|
91
|
+
- **双证据实现说明**:源码侧使用 `DOMParser` 解析 HTML(不执行脚本),得到纯声明态 DOM;运行时侧用 Playwright 渲染;两者按 type+label 比对后标注 `both / runtime-only / source-only`。`setContent` 方案会执行内联脚本导致 evidence 误标,已废弃。
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"skill_name": "page-element-scan",
|
|
3
|
+
"evals": [
|
|
4
|
+
{
|
|
5
|
+
"id": 1,
|
|
6
|
+
"prompt": "我手里有一个页面的 html 文件:F:\\001-Proj\\ep-stage\\.claude\\skills\\page-element-scan\\evals\\fixtures\\sample-list-page.html。想搞清楚这个页面上有哪些可以操作的元素(按钮、输入框、列表操作之类的),最好能给我可以直接用在 Playwright 脚本里的定位方式。另外注意区分一下哪些元素是页面上实际能看到的、哪些是被隐藏或禁用的。结果输出成文件给我(放 F:\\001-Proj\\ep-stage\\.claude\\skills\\page-element-scan-workspace\\ 下,json 和 markdown 各一份)。",
|
|
7
|
+
"expected_output": "产出 JSON + Markdown 元素清单:识别出查询/新增/导出/审核按钮(审核标记为禁用),批量删除因 display:none 被过滤或标记隐藏;4 个查询字段带控件类型和 label 定位符;datagrid1 含列信息和行操作(查看/编辑);标签页和链接;弹窗触发器 新增窗口→gxhzwplaceadd.html。",
|
|
8
|
+
"files": ["F:\\001-Proj\\ep-stage\\.claude\\skills\\page-element-scan\\evals\\fixtures\\sample-list-page.html"],
|
|
9
|
+
"assertions": [
|
|
10
|
+
"产出 JSON 和 Markdown 两份元素清单文件",
|
|
11
|
+
"识别出按钮:查询、新增、导出,且标记为可见可用",
|
|
12
|
+
"「审核」按钮被识别且标记为禁用/不可操作",
|
|
13
|
+
"「批量删除」(display:none)不出现在可操作清单中,或被明确标记为隐藏",
|
|
14
|
+
"识别出 4 个查询字段:场所名称、场所分类、状态、服务时间",
|
|
15
|
+
"识别出数据列表及其行操作:查看、编辑",
|
|
16
|
+
"识别出标签页:待办场所、已办场所",
|
|
17
|
+
"元素带有非空的 Playwright 定位符",
|
|
18
|
+
"识别出弹窗触发器:新增窗口 → gxhzwplaceadd.html"
|
|
19
|
+
]
|
|
20
|
+
},
|
|
21
|
+
{
|
|
22
|
+
"id": 2,
|
|
23
|
+
"prompt": "帮我盘点一下我们测试环境「场所窗口信息管理」列表页上有哪些可操作元素。登录页是 http://192.168.203.46:8100/epoint-web/frame/pages/login/login,业务管理员账号 yz,密码 Ep@2024Better,登录后列表页地址是 http://192.168.203.46:8100/epoint-web/perpage/zwplace/gxhzwplacelist.html。我想知道查询区有哪些筛选项、工具栏有哪些按钮、列表里每行能做什么操作,输出的定位符要能直接拿去写 Playwright 用例。结果存成 json 和 markdown 文件。",
|
|
24
|
+
"expected_output": "产出 JSON + Markdown:工具栏按钮(新增场所信息管理、删除选定、导出、同步窗口管理系统)、查询区字段(场所名称、场所分类、状态)、列表行操作(修改、查看);元素带基于运行时渲染 DOM 的可用 locator;登录成功完成。",
|
|
25
|
+
"files": [],
|
|
26
|
+
"assertions": [
|
|
27
|
+
"产出 JSON 和 Markdown 两份元素清单文件",
|
|
28
|
+
"登录成功并扫描到真实列表页(元素总数 ≥ 8,非登录页残留)",
|
|
29
|
+
"识别出工具栏按钮:新增场所信息管理、删除选定、导出、同步窗口管理系统",
|
|
30
|
+
"识别出查询区字段:场所名称、场所分类、状态",
|
|
31
|
+
"识别出列表行操作:修改、查看",
|
|
32
|
+
"元素带有非空的 Playwright 定位符",
|
|
33
|
+
"定位符基于运行时渲染 DOM(含 mini- 等渲染后类名或等价结构),而非静态声明式标记"
|
|
34
|
+
]
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<title>场所窗口信息管理(评估用模拟页)</title>
|
|
6
|
+
<style>
|
|
7
|
+
.hidden-btn { display: none; }
|
|
8
|
+
.mini-disabled { color: #999; cursor: not-allowed; }
|
|
9
|
+
</style>
|
|
10
|
+
</head>
|
|
11
|
+
<body>
|
|
12
|
+
|
|
13
|
+
<!-- 标签页 -->
|
|
14
|
+
<ul class="mini-tabs">
|
|
15
|
+
<li class="mini-tabs-active">待办场所</li>
|
|
16
|
+
<li>已办场所</li>
|
|
17
|
+
</ul>
|
|
18
|
+
|
|
19
|
+
<!-- 查询条件区 -->
|
|
20
|
+
<div class="fui-condition">
|
|
21
|
+
<div label="场所名称" role="control">
|
|
22
|
+
<input class="mini-textbox mini-textbox-input" bind="query.name" maxLength="50" />
|
|
23
|
+
</div>
|
|
24
|
+
<div label="场所分类" role="control">
|
|
25
|
+
<input class="mini-combobox" bind="query.category" />
|
|
26
|
+
</div>
|
|
27
|
+
<div label="状态" role="control">
|
|
28
|
+
<select class="mini-combobox" bind="query.status">
|
|
29
|
+
<option>请选择</option><option>草稿</option><option>在用</option><option>停用</option>
|
|
30
|
+
</select>
|
|
31
|
+
</div>
|
|
32
|
+
<div label="服务时间" role="control">
|
|
33
|
+
<input class="mini-datepicker" bind="query.serviceDate" />
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
<!-- 工具栏按钮 -->
|
|
38
|
+
<div class="mini-toolbar">
|
|
39
|
+
<a class="mini-button" id="btnQuery" onclick="doQuery()">查询</a>
|
|
40
|
+
<a class="mini-button" id="btnAdd" onclick="openChild()">新增</a>
|
|
41
|
+
<a class="mini-button" id="btnExport" onclick="doExport()">导出</a>
|
|
42
|
+
<a class="mini-button hidden-btn" id="btnBatchDelete" onclick="doBatchDelete()">批量删除</a>
|
|
43
|
+
<a class="mini-button mini-disabled" id="btnAudit" aria-disabled="true">审核</a>
|
|
44
|
+
</div>
|
|
45
|
+
|
|
46
|
+
<!-- 数据列表 -->
|
|
47
|
+
<div id="datagrid1" class="mini-datagrid" allowCellEdit="true">
|
|
48
|
+
<div property="columns">
|
|
49
|
+
<div type="indexcolumn">序号</div>
|
|
50
|
+
<div field="name" class="mini-grid-headerText">场所名称</div>
|
|
51
|
+
<div field="category" class="mini-grid-headerText">场所分类</div>
|
|
52
|
+
<div field="status" class="mini-grid-headerText">状态</div>
|
|
53
|
+
<div type="actioncolumn" class="mini-grid-headerText">操作
|
|
54
|
+
<i type="action" data-tooltip="查看" onclick="viewRow()"></i>
|
|
55
|
+
<i type="action" data-tooltip="编辑" onclick="editRow()"></i>
|
|
56
|
+
</div>
|
|
57
|
+
</div>
|
|
58
|
+
</div>
|
|
59
|
+
<div class="mini-grid-pager">共 128 条</div>
|
|
60
|
+
|
|
61
|
+
<!-- 普通链接 -->
|
|
62
|
+
<a href="/help/manual.html">查看操作手册</a>
|
|
63
|
+
|
|
64
|
+
<!-- 弹窗触发器(供源代码侧提取) -->
|
|
65
|
+
<script>
|
|
66
|
+
function openChild() { epoint.openTopDialog('新增窗口', 'gxhzwplaceadd.html'); }
|
|
67
|
+
function doQuery() {}
|
|
68
|
+
function doExport() {}
|
|
69
|
+
function doBatchDelete() {}
|
|
70
|
+
function viewRow() {}
|
|
71
|
+
function editRow() {}
|
|
72
|
+
|
|
73
|
+
// 运行时动态创建的按钮:静态分析看不到它的渲染结果
|
|
74
|
+
document.addEventListener('DOMContentLoaded', function () {
|
|
75
|
+
var bar = document.querySelector('.mini-toolbar');
|
|
76
|
+
var btn = document.createElement('a');
|
|
77
|
+
btn.className = 'mini-button';
|
|
78
|
+
btn.id = 'btnDynamicHelp';
|
|
79
|
+
btn.textContent = '动态帮助';
|
|
80
|
+
bar.appendChild(btn);
|
|
81
|
+
});
|
|
82
|
+
</script>
|
|
83
|
+
|
|
84
|
+
</body>
|
|
85
|
+
</html>
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
# MiniUI 分页控件识别规则
|
|
2
|
+
|
|
3
|
+
本文件定义 `scan-page.mjs` / `extract-page-elements.mjs` 对 MiniUI 分页控件的采集规范。
|
|
4
|
+
|
|
5
|
+
## 分页控件结构
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
.mini-grid-pager ← 分页容器(采集入口)
|
|
9
|
+
└── .mini-pagination ← 分页主体
|
|
10
|
+
├── .pagination-pagerinfo ← 总条数文本(如"共536条")
|
|
11
|
+
├── .pagination-buttons ← 按钮组
|
|
12
|
+
│ ├── a.pagination-button[action=prev] ← 上一页
|
|
13
|
+
│ ├── a.pagination-button[action=next] ← 下一页
|
|
14
|
+
│ ├── a.pagination-button.pagination-current ← 当前页(数字按钮)
|
|
15
|
+
│ ├── a.pagination-button ← 其他页码数字按钮
|
|
16
|
+
│ └── a.pagination-button-ellipsis[action=none] ← 省略号
|
|
17
|
+
├── span.mini-buttonedit(页面尺寸下拉)
|
|
18
|
+
│ └── input[type=hidden] ← 实际值(选中项的 value)
|
|
19
|
+
└── .pagination-pagerchange ← 跳转区
|
|
20
|
+
└── input.pagination-number ← 页码输入框
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## 采集条件
|
|
24
|
+
|
|
25
|
+
同时满足以下条件时才采集分页控件:
|
|
26
|
+
1. 源码静态包含 `.mini-grid-pager` 或运行时 DOM 存在 `.mini-grid-pager`
|
|
27
|
+
2. 页面类型为列表页(有 mini-datagrid),非详情/编辑页
|
|
28
|
+
3. 分页控件数量 ≤ 2(列表页 1 个,子表最多 1 个;超过则可能是误判)
|
|
29
|
+
|
|
30
|
+
## 采集规则
|
|
31
|
+
|
|
32
|
+
### 1. 分页容器
|
|
33
|
+
- **type**: `pagination`
|
|
34
|
+
- **label**: 父级 grid 的 id(如 `datagrid`)或 `grid`
|
|
35
|
+
- **locator**: `#${gridId} .mini-grid-pager` 或 `.mini-grid-pager`
|
|
36
|
+
|
|
37
|
+
### 2. 上一页按钮
|
|
38
|
+
- **运行时选择器**: `a[action=prev]`(在 `.pagination-buttons` 内)
|
|
39
|
+
- **locator 规则**:
|
|
40
|
+
- 有 gridId:`#${gridId} .pagination-buttons a[action=prev]`
|
|
41
|
+
- 无 gridId:`.pagination-buttons a[action=prev]`
|
|
42
|
+
- **enabled 判定**: `!a.classList.contains('disabled')`
|
|
43
|
+
|
|
44
|
+
### 3. 下一页按钮
|
|
45
|
+
- **运行时选择器**: `a[action=next]`(在 `.pagination-buttons` 内)
|
|
46
|
+
- **locator 规则**: 同上一页,`action=next`
|
|
47
|
+
|
|
48
|
+
### 4. 首页按钮
|
|
49
|
+
- 首页在"上一页"按钮存在 `.disabled` 类时可用
|
|
50
|
+
- 无独立元素,通过"上一页"按钮的 disabled 状态判断
|
|
51
|
+
|
|
52
|
+
### 5. 末页按钮
|
|
53
|
+
- 末页在"下一页"按钮存在 `.disabled` 类时可用
|
|
54
|
+
- 无独立元素,通过"下一页"按钮的 disabled 状态判断
|
|
55
|
+
|
|
56
|
+
### 6. 当前页码
|
|
57
|
+
- **运行时选择器**: `.pagination-current`
|
|
58
|
+
- **locator**: `#${gridId} .pagination-current`
|
|
59
|
+
- **label**: 页码数字文本(如 "1"、"2")
|
|
60
|
+
- **可见性**: 始终可见
|
|
61
|
+
|
|
62
|
+
### 7. 其他页码数字按钮
|
|
63
|
+
- **运行时选择器**: `.pagination-buttons a.pagination-button:not([action])`
|
|
64
|
+
- **locator**: `#${gridId} .pagination-buttons a.pagination-button`
|
|
65
|
+
- **注意**: 省略号 `a.pagination-button-ellipsis` 除外
|
|
66
|
+
```js
|
|
67
|
+
selector += ':not(.pagination-button-ellipsis)';
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### 8. 省略号按钮
|
|
71
|
+
- **运行时选择器**: `a.pagination-button-ellipsis`
|
|
72
|
+
- **locator**: `#${gridId} .pagination-button-ellipsis`
|
|
73
|
+
- **特征**: `action=none`,用于跳过中间页码
|
|
74
|
+
|
|
75
|
+
### 9. 页面尺寸下拉(可选用,通常不操作)
|
|
76
|
+
- **运行时选择器**: `.mini-pagination > span.mini-buttonedit`
|
|
77
|
+
- **实际值**: `span.mini-buttonedit input[type=hidden]`
|
|
78
|
+
- **locator**: `#${gridId} .mini-pagination > span.mini-buttonedit`
|
|
79
|
+
|
|
80
|
+
### 10. 页码跳转输入框
|
|
81
|
+
- **运行时选择器**: `input.pagination-number`
|
|
82
|
+
- **locator**: `#${gridId} .pagination-pagerchange input.pagination-number`
|
|
83
|
+
- **特征**: `type=text`,`class=pagination-number`
|
|
84
|
+
|
|
85
|
+
### 11. 总条数文本
|
|
86
|
+
- **运行时选择器**: `.pagination-pagerinfo`
|
|
87
|
+
- **locator**: `#${gridId} .pagination-pagerinfo`
|
|
88
|
+
- **label**: 文本内容(如 "共536条")
|
|
89
|
+
|
|
90
|
+
## 数据结构输出示例
|
|
91
|
+
|
|
92
|
+
```json
|
|
93
|
+
{
|
|
94
|
+
"type": "pagination",
|
|
95
|
+
"label": "datagrid",
|
|
96
|
+
"locator": "#datagrid .mini-grid-pager",
|
|
97
|
+
"visible": true,
|
|
98
|
+
"enabled": true,
|
|
99
|
+
"controls": {
|
|
100
|
+
"totalInfo": {
|
|
101
|
+
"label": "共536条",
|
|
102
|
+
"locator": "#datagrid .pagination-pagerinfo"
|
|
103
|
+
},
|
|
104
|
+
"prev": {
|
|
105
|
+
"label": "上一页",
|
|
106
|
+
"locator": "#datagrid .pagination-buttons a[action=prev]",
|
|
107
|
+
"enabled": false
|
|
108
|
+
},
|
|
109
|
+
"next": {
|
|
110
|
+
"label": "下一页",
|
|
111
|
+
"locator": "#datagrid .pagination-buttons a[action=next]",
|
|
112
|
+
"enabled": true
|
|
113
|
+
},
|
|
114
|
+
"currentPage": {
|
|
115
|
+
"label": "1",
|
|
116
|
+
"locator": "#datagrid .pagination-current"
|
|
117
|
+
},
|
|
118
|
+
"pageNumbers": [
|
|
119
|
+
{ "label": "1", "locator": "#datagrid .pagination-buttons a.pagination-button:nth-child(2)" },
|
|
120
|
+
{ "label": "2", "locator": "#datagrid .pagination-buttons a.pagination-button:nth-child(3)" }
|
|
121
|
+
],
|
|
122
|
+
"pageSize": {
|
|
123
|
+
"label": "条/页",
|
|
124
|
+
"locator": "#datagrid .mini-pagination > span.mini-buttonedit",
|
|
125
|
+
"valueLocator": "#datagrid .mini-pagination > span.mini-buttonedit input[type=hidden]"
|
|
126
|
+
},
|
|
127
|
+
"pageJump": {
|
|
128
|
+
"label": "跳至页",
|
|
129
|
+
"locator": "#datagrid .pagination-pagerchange input.pagination-number"
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
## 实现检查清单(scan-page.mjs)
|
|
136
|
+
|
|
137
|
+
- [ ] 入口:`document.querySelectorAll('.mini-grid-pager')`
|
|
138
|
+
- [ ] 每个 pager 按 `gridId`(父级 `.mini-datagrid` 的 id)区分
|
|
139
|
+
- [ ] 子表 grid 的分页:父元素是 `#windowGrid` 等子表 id
|
|
140
|
+
- [ ] 页码按钮过滤:`a.pagination-button:not(.pagination-button-ellipsis)`
|
|
141
|
+
- [ ] enabled 状态:`!el.classList.contains('disabled')`
|
|
142
|
+
- [ ] 总条数从 `.pagination-pagerinfo` 文本提取
|
|
143
|
+
- [ ] 页面尺寸值从 `span.mini-buttonedit input[type=hidden]` 获取
|
|
144
|
+
- [ ] 采集结果写入 `elements[]`,`type='pagination'`
|
|
145
|
+
|
|
146
|
+
## 已知干扰项
|
|
147
|
+
|
|
148
|
+
| 干扰 | 排除规则 |
|
|
149
|
+
|------|---------|
|
|
150
|
+
| 子表内重复的分页结构 | 以所属 grid id 区分,label 加 grid 前缀 |
|
|
151
|
+
| `.pagination-number` 被 `div[label]` 错误包装 | 在 `fieldLabel()` 中跳过,单独在分页采集逻辑中处理 |
|
|
152
|
+
| 省略号按钮 | `a.pagination-button-ellipsis` 单独识别,不归入页码数字 |
|
|
153
|
+
| 分页不可用时(无数据/只有一页)| `.disabled` 类标记,enabled=false |
|