@qa-ai-stlc/explorer 0.4.0 → 0.5.0
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/dist/analyze-static-source.d.ts +24 -0
- package/dist/analyze-static-source.d.ts.map +1 -0
- package/dist/analyze-static-source.js +133 -0
- package/dist/analyze-static-source.js.map +1 -0
- package/dist/build-missing-test-id-report.d.ts +16 -0
- package/dist/build-missing-test-id-report.d.ts.map +1 -0
- package/dist/build-missing-test-id-report.js +79 -0
- package/dist/build-missing-test-id-report.js.map +1 -0
- package/dist/build-selector-registry.d.ts +3 -1
- package/dist/build-selector-registry.d.ts.map +1 -1
- package/dist/build-selector-registry.js +0 -0
- package/dist/build-selector-registry.js.map +1 -1
- package/dist/generate-locator-module.d.ts +24 -0
- package/dist/generate-locator-module.d.ts.map +1 -0
- package/dist/generate-locator-module.js +80 -0
- package/dist/generate-locator-module.js.map +1 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -0
- package/dist/index.js.map +1 -1
- package/dist/naming.d.ts +15 -0
- package/dist/naming.d.ts.map +1 -0
- package/dist/naming.js +94 -0
- package/dist/naming.js.map +1 -0
- package/dist/pick-mode.d.ts +83 -0
- package/dist/pick-mode.d.ts.map +1 -0
- package/dist/pick-mode.js +265 -0
- package/dist/pick-mode.js.map +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { type Clock } from '@qa-ai-stlc/core';
|
|
2
|
+
import type { SelectorElement } from '@qa-ai-stlc/schemas';
|
|
3
|
+
export interface StaticSourceFile {
|
|
4
|
+
/** Project-relative, forward-slash path (`RelativePathSchema`), recorded on every finding. */
|
|
5
|
+
readonly filePath: string;
|
|
6
|
+
readonly content: string;
|
|
7
|
+
}
|
|
8
|
+
export interface AnalyzeStaticSourceOptions {
|
|
9
|
+
readonly files: readonly StaticSourceFile[];
|
|
10
|
+
readonly clock?: Clock;
|
|
11
|
+
}
|
|
12
|
+
export interface AnalyzeStaticSourceResult {
|
|
13
|
+
readonly elements: readonly SelectorElement[];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Extracts `data-testid`, `aria-label` and `role` from literal (never dynamically bound) HTML-shaped
|
|
17
|
+
* attributes across React, Angular and Vue sources (development plan 6.1.2), and turns each finding
|
|
18
|
+
* into a `SelectorElement` with `source: 'static'` and a `sourceLocation` the "missing test ID"
|
|
19
|
+
* report (P1-13) can point a developer at. Never touches routes (P1-19) or a live page: `kind` comes
|
|
20
|
+
* only from the tag name or, for a custom component, a `role` of `'button'`/`'link'`; every other
|
|
21
|
+
* element the source contains and does not name through one of those signals is not observed at all.
|
|
22
|
+
*/
|
|
23
|
+
export declare function analyzeStaticSource(options: AnalyzeStaticSourceOptions): AnalyzeStaticSourceResult;
|
|
24
|
+
//# sourceMappingURL=analyze-static-source.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyze-static-source.d.ts","sourceRoot":"","sources":["../src/analyze-static-source.ts"],"names":[],"mappings":"AAGA,OAAO,EAAyB,KAAK,KAAK,EAAE,MAAM,kBAAkB,CAAC;AACrE,OAAO,KAAK,EAA4C,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAGrG,MAAM,WAAW,gBAAgB;IAC/B,8FAA8F;IAC9F,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED,MAAM,WAAW,0BAA0B;IACzC,QAAQ,CAAC,KAAK,EAAE,SAAS,gBAAgB,EAAE,CAAC;IAC5C,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC;CACxB;AAED,MAAM,WAAW,yBAAyB;IACxC,QAAQ,CAAC,QAAQ,EAAE,SAAS,eAAe,EAAE,CAAC;CAC/C;AA4HD;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,0BAA0B,GAAG,yBAAyB,CAuBlG"}
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
// Copyright The QA-AI-STLC Authors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import { hashText, systemClock } from '@qa-ai-stlc/core';
|
|
4
|
+
import { createElementNamer } from './naming.js';
|
|
5
|
+
const TAG_KIND = {
|
|
6
|
+
button: 'button',
|
|
7
|
+
a: 'link',
|
|
8
|
+
input: 'input',
|
|
9
|
+
select: 'select',
|
|
10
|
+
textarea: 'textarea',
|
|
11
|
+
};
|
|
12
|
+
// A role value that unambiguously identifies its own element kind, for a custom component
|
|
13
|
+
// (`<div role="button">`, common in React/Vue/Angular design systems) that no tag-name mapping
|
|
14
|
+
// above can classify. Other roles (`textbox`, `combobox`, ...) are still captured as a locator
|
|
15
|
+
// candidate below; they just cannot alone decide the schema's fixed `kind` enum.
|
|
16
|
+
const KIND_BY_ROLE = {
|
|
17
|
+
button: 'button',
|
|
18
|
+
link: 'link',
|
|
19
|
+
};
|
|
20
|
+
// A literal attribute value only: `(?<=\s)` requires the attribute name be preceded by whitespace
|
|
21
|
+
// (never `:`, `.` or `[`), which is how every plain HTML/JSX attribute is written and how Vue's
|
|
22
|
+
// `:name=`/`v-bind:name=` and Angular's `[attr.name]=`/`[name]=` bindings never are — so a bound,
|
|
23
|
+
// non-literal value (a JS expression, not stable text) is never captured as one. The attrs chunk
|
|
24
|
+
// is padded with a leading space so an attribute at the very start of it still matches.
|
|
25
|
+
function literalAttribute(attrs, name) {
|
|
26
|
+
const pattern = new RegExp(`(?<=\\s)${name}\\s*=\\s*(?:"([^"]*)"|'([^']*)')`, 'u');
|
|
27
|
+
const match = pattern.exec(` ${attrs}`);
|
|
28
|
+
return match?.[1] ?? match?.[2];
|
|
29
|
+
}
|
|
30
|
+
function lineNumberAt(content, index) {
|
|
31
|
+
let line = 1;
|
|
32
|
+
for (let i = 0; i < index; i += 1) {
|
|
33
|
+
if (content[i] === '\n') {
|
|
34
|
+
line += 1;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return line;
|
|
38
|
+
}
|
|
39
|
+
// Splits `<button data-testid="x">`'s inner text ("button data-testid=\"x\"") into its tag name
|
|
40
|
+
// and the rest, on plain string search/slice rather than a second regex capture group: unlike a
|
|
41
|
+
// capture group, `.search()`/`.slice()` are never `string | undefined` under
|
|
42
|
+
// `noUncheckedIndexedAccess`, and every opening tag genuinely has a name, so there is no real
|
|
43
|
+
// "missing" case here to guard against.
|
|
44
|
+
function splitTag(tagText) {
|
|
45
|
+
const boundary = tagText.search(/[\s/]/u);
|
|
46
|
+
const nameEnd = boundary === -1 ? tagText.length : boundary;
|
|
47
|
+
return { tagName: tagText.slice(0, nameEnd).toLowerCase(), attrs: tagText.slice(nameEnd) };
|
|
48
|
+
}
|
|
49
|
+
// A lexical scan of the raw text for `<tag ...>`, JSX/Vue/Angular alike (React's JSX, Vue's SFC
|
|
50
|
+
// template block and Angular's component template all put literal HTML-shaped attributes on an
|
|
51
|
+
// opening tag the same way), not a per-framework parser. It stops at the first `>` even inside a
|
|
52
|
+
// quoted attribute value, and can match a `<` that starts a string or comment rather than a real
|
|
53
|
+
// tag — an accepted, documented false-positive risk in exchange for needing no `@babel/parser`,
|
|
54
|
+
// `vue/compiler-sfc` or `@angular/compiler` dependency.
|
|
55
|
+
function findStaticElements(file) {
|
|
56
|
+
const findings = [];
|
|
57
|
+
let cursor = 0;
|
|
58
|
+
for (;;) {
|
|
59
|
+
const tagStart = file.content.indexOf('<', cursor);
|
|
60
|
+
if (tagStart === -1) {
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
const tagEnd = file.content.indexOf('>', tagStart);
|
|
64
|
+
if (tagEnd === -1) {
|
|
65
|
+
break;
|
|
66
|
+
}
|
|
67
|
+
cursor = tagEnd + 1;
|
|
68
|
+
const tagText = file.content.slice(tagStart + 1, tagEnd);
|
|
69
|
+
if (!/^[A-Za-z]/u.test(tagText)) {
|
|
70
|
+
continue; // a closing tag, comment, doctype or fragment, never an opening tag with attributes
|
|
71
|
+
}
|
|
72
|
+
const { tagName, attrs } = splitTag(tagText);
|
|
73
|
+
const role = literalAttribute(attrs, 'role');
|
|
74
|
+
const kind = TAG_KIND[tagName] ?? (role === undefined ? undefined : KIND_BY_ROLE[role]);
|
|
75
|
+
if (kind === undefined) {
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
findings.push({
|
|
79
|
+
kind,
|
|
80
|
+
tagName,
|
|
81
|
+
testId: literalAttribute(attrs, 'data-testid'),
|
|
82
|
+
ariaLabel: literalAttribute(attrs, 'aria-label'),
|
|
83
|
+
role,
|
|
84
|
+
filePath: file.filePath,
|
|
85
|
+
line: lineNumberAt(file.content, tagStart),
|
|
86
|
+
});
|
|
87
|
+
}
|
|
88
|
+
return findings;
|
|
89
|
+
}
|
|
90
|
+
function findingNameSource(finding) {
|
|
91
|
+
return finding.testId ?? finding.ariaLabel ?? finding.role ?? `${finding.tagName}:${String(finding.line)}`;
|
|
92
|
+
}
|
|
93
|
+
function candidatesFor(finding) {
|
|
94
|
+
const candidates = [];
|
|
95
|
+
if (finding.role !== undefined && finding.ariaLabel !== undefined) {
|
|
96
|
+
candidates.push({
|
|
97
|
+
strategy: 'role',
|
|
98
|
+
value: JSON.stringify({ role: finding.role, name: finding.ariaLabel }),
|
|
99
|
+
fragile: false,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
if (finding.testId !== undefined) {
|
|
103
|
+
candidates.push({ strategy: 'testId', value: finding.testId, fragile: false });
|
|
104
|
+
}
|
|
105
|
+
return candidates;
|
|
106
|
+
}
|
|
107
|
+
/**
|
|
108
|
+
* Extracts `data-testid`, `aria-label` and `role` from literal (never dynamically bound) HTML-shaped
|
|
109
|
+
* attributes across React, Angular and Vue sources (development plan 6.1.2), and turns each finding
|
|
110
|
+
* into a `SelectorElement` with `source: 'static'` and a `sourceLocation` the "missing test ID"
|
|
111
|
+
* report (P1-13) can point a developer at. Never touches routes (P1-19) or a live page: `kind` comes
|
|
112
|
+
* only from the tag name or, for a custom component, a `role` of `'button'`/`'link'`; every other
|
|
113
|
+
* element the source contains and does not name through one of those signals is not observed at all.
|
|
114
|
+
*/
|
|
115
|
+
export function analyzeStaticSource(options) {
|
|
116
|
+
const clock = options.clock ?? systemClock;
|
|
117
|
+
const lastVerifiedAt = clock.now().toISOString();
|
|
118
|
+
const nameFor = createElementNamer();
|
|
119
|
+
const elements = options.files.flatMap((file) => findStaticElements(file).map((finding) => ({
|
|
120
|
+
elementId: hashText(`${finding.filePath} ${String(finding.line)} ${finding.kind} ${findingNameSource(finding)}`),
|
|
121
|
+
name: nameFor(findingNameSource(finding), finding.kind),
|
|
122
|
+
kind: finding.kind,
|
|
123
|
+
locatorCandidates: candidatesFor(finding),
|
|
124
|
+
stabilityScore: 0,
|
|
125
|
+
lastVerifiedAt,
|
|
126
|
+
pii: false,
|
|
127
|
+
dynamicText: false,
|
|
128
|
+
source: 'static',
|
|
129
|
+
sourceLocation: { filePath: finding.filePath, line: finding.line },
|
|
130
|
+
})));
|
|
131
|
+
return { elements };
|
|
132
|
+
}
|
|
133
|
+
//# sourceMappingURL=analyze-static-source.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analyze-static-source.js","sourceRoot":"","sources":["../src/analyze-static-source.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { MissingTestIdReport, SelectorRegistry } from '@qa-ai-stlc/schemas';
|
|
2
|
+
/**
|
|
3
|
+
* Lists every non-deprecated registry element with no `testId` locator candidate (development
|
|
4
|
+
* plan section 6.3 step 9), with its source file and line when the registry knows one — only
|
|
5
|
+
* `source: 'static'` entries (P1-12) ever do; `crawl`/`manual` entries report no `sourceLocation`
|
|
6
|
+
* rather than guessing. Entries with a known location sort first, by file then line; the rest sort
|
|
7
|
+
* by `elementId`, both orders deterministic across runs on an unchanged registry.
|
|
8
|
+
*/
|
|
9
|
+
export declare function buildMissingTestIdReport(registry: SelectorRegistry): MissingTestIdReport;
|
|
10
|
+
/**
|
|
11
|
+
* Renders a `MissingTestIdReport` to Markdown (ADR-002: Markdown is rendered from JSON, never
|
|
12
|
+
* hand-written) grouped by source file, with entries whose file is unknown collected under their
|
|
13
|
+
* own heading rather than mixed in or silently dropped.
|
|
14
|
+
*/
|
|
15
|
+
export declare function renderMissingTestIdReportMarkdown(report: MissingTestIdReport): string;
|
|
16
|
+
//# sourceMappingURL=build-missing-test-id-report.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-missing-test-id-report.d.ts","sourceRoot":"","sources":["../src/build-missing-test-id-report.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAsB,mBAAmB,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAwBrG;;;;;;GAMG;AACH,wBAAgB,wBAAwB,CAAC,QAAQ,EAAE,gBAAgB,GAAG,mBAAmB,CAaxF;AAQD;;;;GAIG;AACH,wBAAgB,iCAAiC,CAAC,MAAM,EAAE,mBAAmB,GAAG,MAAM,CA+BrF"}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// Copyright The QA-AI-STLC Authors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import { SCHEMA_VERSION } from '@qa-ai-stlc/schemas';
|
|
4
|
+
function hasTestId(element) {
|
|
5
|
+
return element.locatorCandidates.some((candidate) => candidate.strategy === 'testId');
|
|
6
|
+
}
|
|
7
|
+
// `sourceLocation` is either present on both `a` and `b`'s underlying type or absent — it comes
|
|
8
|
+
// straight from `SelectorElement.sourceLocation` (selector-registry.ts), whose own two fields are
|
|
9
|
+
// both required together — so once both are known to be present, `a.sourceLocation.line` is never
|
|
10
|
+
// missing and needs no fallback.
|
|
11
|
+
function compareEntries(a, b) {
|
|
12
|
+
if (a.sourceLocation === undefined || b.sourceLocation === undefined) {
|
|
13
|
+
if ((a.sourceLocation === undefined) !== (b.sourceLocation === undefined)) {
|
|
14
|
+
return a.sourceLocation === undefined ? 1 : -1; // a known location sorts first
|
|
15
|
+
}
|
|
16
|
+
return a.elementId.localeCompare(b.elementId);
|
|
17
|
+
}
|
|
18
|
+
return (a.sourceLocation.filePath.localeCompare(b.sourceLocation.filePath) ||
|
|
19
|
+
a.sourceLocation.line - b.sourceLocation.line ||
|
|
20
|
+
a.elementId.localeCompare(b.elementId));
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* Lists every non-deprecated registry element with no `testId` locator candidate (development
|
|
24
|
+
* plan section 6.3 step 9), with its source file and line when the registry knows one — only
|
|
25
|
+
* `source: 'static'` entries (P1-12) ever do; `crawl`/`manual` entries report no `sourceLocation`
|
|
26
|
+
* rather than guessing. Entries with a known location sort first, by file then line; the rest sort
|
|
27
|
+
* by `elementId`, both orders deterministic across runs on an unchanged registry.
|
|
28
|
+
*/
|
|
29
|
+
export function buildMissingTestIdReport(registry) {
|
|
30
|
+
const entries = registry.elements
|
|
31
|
+
.filter((element) => element.deprecatedAt === undefined && !hasTestId(element))
|
|
32
|
+
.map((element) => ({
|
|
33
|
+
elementId: element.elementId,
|
|
34
|
+
...(element.name === undefined ? {} : { name: element.name }),
|
|
35
|
+
kind: element.kind,
|
|
36
|
+
source: element.source,
|
|
37
|
+
...(element.sourceLocation === undefined ? {} : { sourceLocation: element.sourceLocation }),
|
|
38
|
+
}))
|
|
39
|
+
.sort(compareEntries);
|
|
40
|
+
return { schemaVersion: SCHEMA_VERSION, generatedAt: registry.generatedAt, entries };
|
|
41
|
+
}
|
|
42
|
+
function entryLine(entry) {
|
|
43
|
+
const label = entry.name === undefined ? entry.kind : `${entry.kind} \`${entry.name}\``;
|
|
44
|
+
const location = entry.sourceLocation === undefined ? '' : ` — line ${String(entry.sourceLocation.line)}`;
|
|
45
|
+
return `- ${label} (${entry.source})${location}`;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Renders a `MissingTestIdReport` to Markdown (ADR-002: Markdown is rendered from JSON, never
|
|
49
|
+
* hand-written) grouped by source file, with entries whose file is unknown collected under their
|
|
50
|
+
* own heading rather than mixed in or silently dropped.
|
|
51
|
+
*/
|
|
52
|
+
export function renderMissingTestIdReportMarkdown(report) {
|
|
53
|
+
const count = report.entries.length;
|
|
54
|
+
const header = `# Missing test ID report\n\n_Generated ${report.generatedAt} — ${String(count)} element(s) with no test ID._`;
|
|
55
|
+
if (count === 0) {
|
|
56
|
+
return `${header}\n\nEvery interactive element has a test ID candidate.\n`;
|
|
57
|
+
}
|
|
58
|
+
const sections = [];
|
|
59
|
+
let currentFilePath;
|
|
60
|
+
let currentLines = [];
|
|
61
|
+
const flush = () => {
|
|
62
|
+
if (currentLines.length === 0) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
const heading = currentFilePath ?? 'Unknown source';
|
|
66
|
+
sections.push(`## ${heading}\n\n${currentLines.join('\n')}`);
|
|
67
|
+
};
|
|
68
|
+
for (const entry of report.entries) {
|
|
69
|
+
if (entry.sourceLocation?.filePath !== currentFilePath) {
|
|
70
|
+
flush();
|
|
71
|
+
currentFilePath = entry.sourceLocation?.filePath;
|
|
72
|
+
currentLines = [];
|
|
73
|
+
}
|
|
74
|
+
currentLines.push(entryLine(entry));
|
|
75
|
+
}
|
|
76
|
+
flush();
|
|
77
|
+
return `${header}\n\n${sections.join('\n\n')}\n`;
|
|
78
|
+
}
|
|
79
|
+
//# sourceMappingURL=build-missing-test-id-report.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"build-missing-test-id-report.js","sourceRoot":"","sources":["../src/build-missing-test-id-report.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type BrowserLauncher, type Clock, type ViewportSize } from '@qa-ai-stlc/core';
|
|
2
|
-
import { type PageModelSet, type SelectorElement, type SelectorRegistry } from '@qa-ai-stlc/schemas';
|
|
2
|
+
import { type InteractiveElement, type PageModelSet, type SelectorElement, type SelectorRegistry } from '@qa-ai-stlc/schemas';
|
|
3
3
|
import { type ExplorerIdentity } from './identity.js';
|
|
4
4
|
import { type LocatorPolicy } from './synthesize-locators.js';
|
|
5
5
|
export interface BuildSelectorRegistryOptions {
|
|
@@ -16,6 +16,8 @@ export interface BuildSelectorRegistryResult {
|
|
|
16
16
|
/** Every non-GET request safe mode intercepted and cancelled; always 0 unless something is broken. */
|
|
17
17
|
readonly blockedRequestCount: number;
|
|
18
18
|
}
|
|
19
|
+
export declare function elementNameForId(element: InteractiveElement): string;
|
|
20
|
+
export declare function computeElementId(url: string, element: InteractiveElement): string;
|
|
19
21
|
/**
|
|
20
22
|
* Builds a `SelectorRegistry` from a `PageModelSet` produced by `analyzePages()` (development
|
|
21
23
|
* plan section 6.3 steps 4-6): synthesizes locator candidates for every interactive element under
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build-selector-registry.d.ts","sourceRoot":"","sources":["../src/build-selector-registry.ts"],"names":[],"mappings":"AAGA,OAAO,EAAyB,KAAK,eAAe,EAAE,KAAK,KAAK,EAAE,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAC9G,OAAO,
|
|
1
|
+
{"version":3,"file":"build-selector-registry.d.ts","sourceRoot":"","sources":["../src/build-selector-registry.ts"],"names":[],"mappings":"AAGA,OAAO,EAAyB,KAAK,eAAe,EAAE,KAAK,KAAK,EAAE,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAC9G,OAAO,EAEL,KAAK,kBAAkB,EACvB,KAAK,YAAY,EACjB,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACtB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAuB,KAAK,gBAAgB,EAAE,MAAM,eAAe,CAAC;AAI3E,OAAO,EAA+B,KAAK,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAE3F,MAAM,WAAW,4BAA4B;IAC3C,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAC;IACpC,QAAQ,CAAC,eAAe,EAAE,eAAe,CAAC;IAC1C,oFAAoF;IACpF,QAAQ,CAAC,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IACrC,QAAQ,CAAC,MAAM,CAAC,EAAE,aAAa,CAAC;IAChC,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,YAAY,EAAE,CAAC;IAC7C,QAAQ,CAAC,KAAK,CAAC,EAAE,KAAK,CAAC;CACxB;AAED,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,QAAQ,EAAE,gBAAgB,CAAC;IACpC,sGAAsG;IACtG,QAAQ,CAAC,mBAAmB,EAAE,MAAM,CAAC;CACtC;AAOD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,kBAAkB,GAAG,MAAM,CAQpE;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,kBAAkB,GAAG,MAAM,CAEjF;AAED;;;;;;GAMG;AACH,wBAAsB,qBAAqB,CACzC,OAAO,EAAE,4BAA4B,GACpC,OAAO,CAAC,2BAA2B,CAAC,CAsDtC;AAED;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,gBAAgB,EAC1B,KAAK,EAAE,gBAAgB,EACvB,WAAW,EAAE,MAAM,GAClB,gBAAgB,CAalB;AAED,MAAM,WAAW,uBAAuB;IACtC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;CAC/B;AAED,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,KAAK,EAAE,SAAS,eAAe,EAAE,CAAC;IAC3C,QAAQ,CAAC,OAAO,EAAE,SAAS,eAAe,EAAE,CAAC;IAC7C,QAAQ,CAAC,QAAQ,EAAE,SAAS,uBAAuB,EAAE,CAAC;CACvD;AAED;;;;GAIG;AACH,wBAAgB,oBAAoB,CAClC,QAAQ,EAAE,gBAAgB,EAC1B,OAAO,EAAE,gBAAgB,GACxB,oBAAoB,CAoBtB"}
|
|
Binary file
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build-selector-registry.js","sourceRoot":"","sources":["../src/build-selector-registry.ts"],"names":[],"mappings":"AAAA,mCAAmC;AACnC,sCAAsC;AAEtC,OAAO,EAAE,QAAQ,EAAE,WAAW,EAAuD,MAAM,kBAAkB,CAAC;AAC9G,OAAO,EACL,cAAc,GAKf,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,mBAAmB,EAAyB,MAAM,eAAe,CAAC;AAC3E,OAAO,EAAE,0BAA0B,EAAE,MAAM,gBAAgB,CAAC;AAC5D,OAAO,EAAE,qBAAqB,EAAE,MAAM,wBAAwB,CAAC;AAC/D,OAAO,EAAE,2BAA2B,EAAsB,MAAM,0BAA0B,CAAC;AAkB3F,uFAAuF;AACvF,gGAAgG;AAChG,kGAAkG;AAClG,
|
|
1
|
+
{"version":3,"file":"build-selector-registry.js","sourceRoot":"","sources":["../src/build-selector-registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import type { SelectorRegistry } from '@qa-ai-stlc/schemas';
|
|
2
|
+
export interface GenerateLocatorModuleOptions {
|
|
3
|
+
/** Stamped into the module header and `GENERATOR_VERSION` so a stale file can be detected. */
|
|
4
|
+
readonly generatorVersion: string;
|
|
5
|
+
}
|
|
6
|
+
export interface MissingLocatorElement {
|
|
7
|
+
readonly elementId: string;
|
|
8
|
+
readonly kind: string;
|
|
9
|
+
}
|
|
10
|
+
export interface GenerateLocatorModuleResult {
|
|
11
|
+
/** The `tests/qa/locators.ts` file contents (ADR-006), ready to write as-is. */
|
|
12
|
+
readonly source: string;
|
|
13
|
+
/** Elements with no export: no locator candidate, or no export name assigned yet. */
|
|
14
|
+
readonly missingLocators: readonly MissingLocatorElement[];
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* Renders the selector registry into a typed, deterministic `tests/qa/locators.ts` module (ADR-006):
|
|
18
|
+
* one exported function per element, named after its registry `name`, returning a Playwright
|
|
19
|
+
* `Locator` built from the element's primary (highest-preference) candidate. An element with no
|
|
20
|
+
* candidate, or predating the `name` field, gets no export and is reported in `missingLocators`
|
|
21
|
+
* instead of failing generation.
|
|
22
|
+
*/
|
|
23
|
+
export declare function generateLocatorModule(registry: SelectorRegistry, options: GenerateLocatorModuleOptions): GenerateLocatorModuleResult;
|
|
24
|
+
//# sourceMappingURL=generate-locator-module.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate-locator-module.d.ts","sourceRoot":"","sources":["../src/generate-locator-module.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAqC,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAE/F,MAAM,WAAW,4BAA4B;IAC3C,8FAA8F;IAC9F,QAAQ,CAAC,gBAAgB,EAAE,MAAM,CAAC;CACnC;AAED,MAAM,WAAW,qBAAqB;IACpC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,2BAA2B;IAC1C,gFAAgF;IAChF,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,qFAAqF;IACrF,QAAQ,CAAC,eAAe,EAAE,SAAS,qBAAqB,EAAE,CAAC;CAC5D;AA+DD;;;;;;GAMG;AACH,wBAAgB,qBAAqB,CACnC,QAAQ,EAAE,gBAAgB,EAC1B,OAAO,EAAE,4BAA4B,GACpC,2BAA2B,CAkC7B"}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// Copyright The QA-AI-STLC Authors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import { QaError } from '@qa-ai-stlc/core';
|
|
4
|
+
function roleCall(candidate) {
|
|
5
|
+
const parsed = JSON.parse(candidate.value);
|
|
6
|
+
if (typeof parsed !== 'object' ||
|
|
7
|
+
parsed === null ||
|
|
8
|
+
!('role' in parsed) ||
|
|
9
|
+
!('name' in parsed) ||
|
|
10
|
+
typeof parsed.role !== 'string' ||
|
|
11
|
+
typeof parsed.name !== 'string') {
|
|
12
|
+
throw new QaError('explorer.locator_module.invalid_role_candidate', `role candidate value is not a {role, name} pair: ${candidate.value}`);
|
|
13
|
+
}
|
|
14
|
+
return `page.getByRole(${JSON.stringify(parsed.role)}, { name: ${JSON.stringify(parsed.name)} })`;
|
|
15
|
+
}
|
|
16
|
+
function locatorCall(candidate) {
|
|
17
|
+
switch (candidate.strategy) {
|
|
18
|
+
case 'role':
|
|
19
|
+
return roleCall(candidate);
|
|
20
|
+
case 'testId':
|
|
21
|
+
return `page.getByTestId(${JSON.stringify(candidate.value)})`;
|
|
22
|
+
case 'label':
|
|
23
|
+
return `page.getByLabel(${JSON.stringify(candidate.value)})`;
|
|
24
|
+
case 'placeholder':
|
|
25
|
+
return `page.getByPlaceholder(${JSON.stringify(candidate.value)})`;
|
|
26
|
+
case 'text':
|
|
27
|
+
return `page.getByText(${JSON.stringify(candidate.value)})`;
|
|
28
|
+
case 'css':
|
|
29
|
+
return `page.locator(${JSON.stringify(candidate.value)})`;
|
|
30
|
+
default:
|
|
31
|
+
throw new QaError('explorer.locator_module.unknown_strategy', `unknown locator strategy: ${candidate.strategy}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function isGeneratable(element) {
|
|
35
|
+
return element.name !== undefined && element.deprecatedAt === undefined;
|
|
36
|
+
}
|
|
37
|
+
// Pairs each generatable element with its primary candidate in one pass, narrowing out elements
|
|
38
|
+
// with an empty `locatorCandidates` by the presence of `primary` rather than by re-checking
|
|
39
|
+
// `.length`, so the later render step never needs to handle an "impossible" missing primary.
|
|
40
|
+
function toGeneratableEntry(element) {
|
|
41
|
+
const primary = element.locatorCandidates[0];
|
|
42
|
+
return primary === undefined ? undefined : { element, primary };
|
|
43
|
+
}
|
|
44
|
+
function byName(a, b) {
|
|
45
|
+
return a.element.name.localeCompare(b.element.name);
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Renders the selector registry into a typed, deterministic `tests/qa/locators.ts` module (ADR-006):
|
|
49
|
+
* one exported function per element, named after its registry `name`, returning a Playwright
|
|
50
|
+
* `Locator` built from the element's primary (highest-preference) candidate. An element with no
|
|
51
|
+
* candidate, or predating the `name` field, gets no export and is reported in `missingLocators`
|
|
52
|
+
* instead of failing generation.
|
|
53
|
+
*/
|
|
54
|
+
export function generateLocatorModule(registry, options) {
|
|
55
|
+
const active = registry.elements.filter((element) => element.deprecatedAt === undefined);
|
|
56
|
+
const missingLocators = active
|
|
57
|
+
.filter((element) => !isGeneratable(element) || element.locatorCandidates.length === 0)
|
|
58
|
+
.map((element) => ({ elementId: element.elementId, kind: element.kind }))
|
|
59
|
+
.sort((a, b) => a.elementId.localeCompare(b.elementId));
|
|
60
|
+
const generatable = active
|
|
61
|
+
.filter((element) => isGeneratable(element))
|
|
62
|
+
.map(toGeneratableEntry)
|
|
63
|
+
.filter((entry) => entry !== undefined)
|
|
64
|
+
.sort(byName);
|
|
65
|
+
const functions = generatable.map(({ element, primary }) => `export function ${element.name}(page: Page): Locator {\n return ${locatorCall(primary)};\n}`);
|
|
66
|
+
const header = [
|
|
67
|
+
'// Copyright The QA-AI-STLC Authors',
|
|
68
|
+
'// SPDX-License-Identifier: Apache-2.0',
|
|
69
|
+
'',
|
|
70
|
+
`// Generated by @qa-ai-stlc/explorer@${options.generatorVersion} from the selector registry`,
|
|
71
|
+
'// (ADR-006). Do not edit by hand; regenerate with `qa explore`.',
|
|
72
|
+
'',
|
|
73
|
+
"import type { Locator, Page } from 'playwright';",
|
|
74
|
+
'',
|
|
75
|
+
`export const GENERATOR_VERSION = ${JSON.stringify(options.generatorVersion)};`,
|
|
76
|
+
].join('\n');
|
|
77
|
+
const source = `${[header, ...functions].join('\n\n')}\n`;
|
|
78
|
+
return { source, missingLocators };
|
|
79
|
+
}
|
|
80
|
+
//# sourceMappingURL=generate-locator-module.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generate-locator-module.js","sourceRoot":"","sources":["../src/generate-locator-module.ts"],"names":[],"mappings":"AAAA,mCAAmC;AACnC,sCAAsC;AAEtC,OAAO,EAAE,OAAO,EAAE,MAAM,kBAAkB,CAAC;AAoB3C,SAAS,QAAQ,CAAC,SAA2B;IAC3C,MAAM,MAAM,GAAY,IAAI,CAAC,KAAK,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;IACpD,IACE,OAAO,MAAM,KAAK,QAAQ;QAC1B,MAAM,KAAK,IAAI;QACf,CAAC,CAAC,MAAM,IAAI,MAAM,CAAC;QACnB,CAAC,CAAC,MAAM,IAAI,MAAM,CAAC;QACnB,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ;QAC/B,OAAO,MAAM,CAAC,IAAI,KAAK,QAAQ,EAC/B,CAAC;QACD,MAAM,IAAI,OAAO,CACf,gDAAgD,EAChD,oDAAoD,SAAS,CAAC,KAAK,EAAE,CACtE,CAAC;IACJ,CAAC;IACD,OAAO,kBAAkB,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,aAAa,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC;AACpG,CAAC;AAED,SAAS,WAAW,CAAC,SAA2B;IAC9C,QAAQ,SAAS,CAAC,QAAQ,EAAE,CAAC;QAC3B,KAAK,MAAM;YACT,OAAO,QAAQ,CAAC,SAAS,CAAC,CAAC;QAC7B,KAAK,QAAQ;YACX,OAAO,oBAAoB,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC;QAChE,KAAK,OAAO;YACV,OAAO,mBAAmB,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC;QAC/D,KAAK,aAAa;YAChB,OAAO,yBAAyB,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC;QACrE,KAAK,MAAM;YACT,OAAO,kBAAkB,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC;QAC9D,KAAK,KAAK;YACR,OAAO,gBAAgB,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,KAAK,CAAC,GAAG,CAAC;QAC5D;YACE,MAAM,IAAI,OAAO,CACf,0CAA0C,EAC1C,6BAA6B,SAAS,CAAC,QAAQ,EAAE,CAClD,CAAC;IACN,CAAC;AACH,CAAC;AAED,SAAS,aAAa,CAAC,OAAwB;IAC7C,OAAO,OAAO,CAAC,IAAI,KAAK,SAAS,IAAI,OAAO,CAAC,YAAY,KAAK,SAAS,CAAC;AAC1E,CAAC;AAOD,gGAAgG;AAChG,4FAA4F;AAC5F,6FAA6F;AAC7F,SAAS,kBAAkB,CAAC,OAA2C;IACrE,MAAM,OAAO,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC,CAAC,CAAC;IAC7C,OAAO,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,CAAC;AAClE,CAAC;AAED,SAAS,MAAM,CAAC,CAAmB,EAAE,CAAmB;IACtD,OAAO,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;AACtD,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,qBAAqB,CACnC,QAA0B,EAC1B,OAAqC;IAErC,MAAM,MAAM,GAAG,QAAQ,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,YAAY,KAAK,SAAS,CAAC,CAAC;IAEzF,MAAM,eAAe,GAA4B,MAAM;SACpD,MAAM,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,aAAa,CAAC,OAAO,CAAC,IAAI,OAAO,CAAC,iBAAiB,CAAC,MAAM,KAAK,CAAC,CAAC;SACtF,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,EAAE,SAAS,EAAE,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC,CAAC;SACxE,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,aAAa,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC;IAE1D,MAAM,WAAW,GAAG,MAAM;SACvB,MAAM,CAAC,CAAC,OAAO,EAAiD,EAAE,CAAC,aAAa,CAAC,OAAO,CAAC,CAAC;SAC1F,GAAG,CAAC,kBAAkB,CAAC;SACvB,MAAM,CAAC,CAAC,KAAK,EAA6B,EAAE,CAAC,KAAK,KAAK,SAAS,CAAC;SACjE,IAAI,CAAC,MAAM,CAAC,CAAC;IAEhB,MAAM,SAAS,GAAG,WAAW,CAAC,GAAG,CAC/B,CAAC,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,CACvB,mBAAmB,OAAO,CAAC,IAAI,qCAAqC,WAAW,CAAC,OAAO,CAAC,MAAM,CACjG,CAAC;IAEF,MAAM,MAAM,GAAG;QACb,qCAAqC;QACrC,wCAAwC;QACxC,EAAE;QACF,wCAAwC,OAAO,CAAC,gBAAgB,6BAA6B;QAC7F,kEAAkE;QAClE,EAAE;QACF,kDAAkD;QAClD,EAAE;QACF,oCAAoC,IAAI,CAAC,SAAS,CAAC,OAAO,CAAC,gBAAgB,CAAC,GAAG;KAChF,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAEb,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,CAAC;IAE1D,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC;AACrC,CAAC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,12 +2,17 @@ export * from './accessibility-tree.js';
|
|
|
2
2
|
export * from './allowlist.js';
|
|
3
3
|
export * from './analyze-page.js';
|
|
4
4
|
export * from './analyze-pages.js';
|
|
5
|
+
export * from './analyze-static-source.js';
|
|
6
|
+
export * from './build-missing-test-id-report.js';
|
|
5
7
|
export * from './build-selector-registry.js';
|
|
6
8
|
export * from './crawl.js';
|
|
7
9
|
export * from './extract-links.js';
|
|
10
|
+
export * from './generate-locator-module.js';
|
|
8
11
|
export * from './identity.js';
|
|
12
|
+
export * from './naming.js';
|
|
9
13
|
export * from './normalize.js';
|
|
10
14
|
export * from './page-elements.js';
|
|
15
|
+
export * from './pick-mode.js';
|
|
11
16
|
export * from './request-log.js';
|
|
12
17
|
export * from './safe-mode.js';
|
|
13
18
|
export * from './stability-scoring.js';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,cAAc,yBAAyB,CAAC;AACxC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,YAAY,CAAC;AAC3B,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAGA,cAAc,yBAAyB,CAAC;AACxC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,mCAAmC,CAAC;AAClD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,YAAY,CAAC;AAC3B,cAAc,oBAAoB,CAAC;AACnC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC;AACjC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -4,12 +4,17 @@ export * from './accessibility-tree.js';
|
|
|
4
4
|
export * from './allowlist.js';
|
|
5
5
|
export * from './analyze-page.js';
|
|
6
6
|
export * from './analyze-pages.js';
|
|
7
|
+
export * from './analyze-static-source.js';
|
|
8
|
+
export * from './build-missing-test-id-report.js';
|
|
7
9
|
export * from './build-selector-registry.js';
|
|
8
10
|
export * from './crawl.js';
|
|
9
11
|
export * from './extract-links.js';
|
|
12
|
+
export * from './generate-locator-module.js';
|
|
10
13
|
export * from './identity.js';
|
|
14
|
+
export * from './naming.js';
|
|
11
15
|
export * from './normalize.js';
|
|
12
16
|
export * from './page-elements.js';
|
|
17
|
+
export * from './pick-mode.js';
|
|
13
18
|
export * from './request-log.js';
|
|
14
19
|
export * from './safe-mode.js';
|
|
15
20
|
export * from './stability-scoring.js';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,mCAAmC;AACnC,sCAAsC;AAEtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,YAAY,CAAC;AAC3B,cAAc,oBAAoB,CAAC;AACnC,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,oBAAoB,CAAC;AACnC,cAAc,kBAAkB,CAAC;AACjC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,mCAAmC;AACnC,sCAAsC;AAEtC,cAAc,yBAAyB,CAAC;AACxC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,mBAAmB,CAAC;AAClC,cAAc,oBAAoB,CAAC;AACnC,cAAc,4BAA4B,CAAC;AAC3C,cAAc,mCAAmC,CAAC;AAClD,cAAc,8BAA8B,CAAC;AAC7C,cAAc,YAAY,CAAC;AAC3B,cAAc,oBAAoB,CAAC;AACnC,cAAc,8BAA8B,CAAC;AAC7C,cAAc,eAAe,CAAC;AAC9B,cAAc,aAAa,CAAC;AAC5B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,oBAAoB,CAAC;AACnC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,kBAAkB,CAAC;AACjC,cAAc,gBAAgB,CAAC;AAC/B,cAAc,wBAAwB,CAAC;AACvC,cAAc,0BAA0B,CAAC"}
|
package/dist/naming.d.ts
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turns human-meaningful, untrusted text (an accessible name, an ARIA label) into a valid camelCase
|
|
3
|
+
* identifier, falling back to `fallback` (a domain word guaranteed non-empty, like an element
|
|
4
|
+
* `kind`) when `text` has no letters or digits at all (e.g. an icon-only "→" accessible name).
|
|
5
|
+
* Never returns a reserved word or an identifier starting with a digit.
|
|
6
|
+
*/
|
|
7
|
+
export declare function toCamelCaseIdentifier(text: string, fallback: string): string;
|
|
8
|
+
/**
|
|
9
|
+
* Produces the camelCase export name a registry element's locator module entry will use
|
|
10
|
+
* (ADR-006). Each call to the returned function assigns one element's name; two elements
|
|
11
|
+
* resolving to the same base name (e.g. two buttons both named "Submit") get a numeric suffix,
|
|
12
|
+
* tracked across every call against the same namer, so no two elements it names ever collide.
|
|
13
|
+
*/
|
|
14
|
+
export declare function createElementNamer(): (text: string, fallback: string) => string;
|
|
15
|
+
//# sourceMappingURL=naming.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"naming.d.ts","sourceRoot":"","sources":["../src/naming.ts"],"names":[],"mappings":"AAiEA;;;;;GAKG;AACH,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,CAU5E;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,IAAI,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,KAAK,MAAM,CAQ/E"}
|
package/dist/naming.js
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
// Copyright The QA-AI-STLC Authors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
// Splits on anything that is not a letter or digit, dropping empty segments, so accented and
|
|
4
|
+
// non-Latin text collapses to nothing rather than surviving as invalid identifier characters.
|
|
5
|
+
function wordsOf(text) {
|
|
6
|
+
return text.split(/[^A-Za-z0-9]+/u).filter((word) => word.length > 0);
|
|
7
|
+
}
|
|
8
|
+
// An accessible name equal to a reserved word ("Export", "Delete") would otherwise produce a
|
|
9
|
+
// locator module export that fails to compile.
|
|
10
|
+
const RESERVED_WORDS = new Set([
|
|
11
|
+
'break',
|
|
12
|
+
'case',
|
|
13
|
+
'catch',
|
|
14
|
+
'class',
|
|
15
|
+
'const',
|
|
16
|
+
'continue',
|
|
17
|
+
'debugger',
|
|
18
|
+
'default',
|
|
19
|
+
'delete',
|
|
20
|
+
'do',
|
|
21
|
+
'else',
|
|
22
|
+
'enum',
|
|
23
|
+
'export',
|
|
24
|
+
'extends',
|
|
25
|
+
'false',
|
|
26
|
+
'finally',
|
|
27
|
+
'for',
|
|
28
|
+
'function',
|
|
29
|
+
'if',
|
|
30
|
+
'implements',
|
|
31
|
+
'import',
|
|
32
|
+
'in',
|
|
33
|
+
'instanceof',
|
|
34
|
+
'interface',
|
|
35
|
+
'let',
|
|
36
|
+
'new',
|
|
37
|
+
'null',
|
|
38
|
+
'package',
|
|
39
|
+
'private',
|
|
40
|
+
'protected',
|
|
41
|
+
'public',
|
|
42
|
+
'return',
|
|
43
|
+
'static',
|
|
44
|
+
'super',
|
|
45
|
+
'switch',
|
|
46
|
+
'this',
|
|
47
|
+
'throw',
|
|
48
|
+
'true',
|
|
49
|
+
'try',
|
|
50
|
+
'typeof',
|
|
51
|
+
'var',
|
|
52
|
+
'void',
|
|
53
|
+
'while',
|
|
54
|
+
'with',
|
|
55
|
+
'yield',
|
|
56
|
+
]);
|
|
57
|
+
// `.charAt(0)` (unlike `word[0]`) always returns a plain `string` even under
|
|
58
|
+
// `noUncheckedIndexedAccess`, so this never needs an `undefined` fallback for an empty `word`.
|
|
59
|
+
function upperFirst(word) {
|
|
60
|
+
return `${word.charAt(0).toUpperCase()}${word.slice(1)}`;
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Turns human-meaningful, untrusted text (an accessible name, an ARIA label) into a valid camelCase
|
|
64
|
+
* identifier, falling back to `fallback` (a domain word guaranteed non-empty, like an element
|
|
65
|
+
* `kind`) when `text` has no letters or digits at all (e.g. an icon-only "→" accessible name).
|
|
66
|
+
* Never returns a reserved word or an identifier starting with a digit.
|
|
67
|
+
*/
|
|
68
|
+
export function toCamelCaseIdentifier(text, fallback) {
|
|
69
|
+
const words = wordsOf(text);
|
|
70
|
+
const source = words.length > 0 ? words : wordsOf(fallback);
|
|
71
|
+
const identifier = source
|
|
72
|
+
.map((word, index) => (index === 0 ? word.toLowerCase() : upperFirst(word.toLowerCase())))
|
|
73
|
+
.join('');
|
|
74
|
+
if (/^[0-9]/u.test(identifier)) {
|
|
75
|
+
return `element${upperFirst(identifier)}`;
|
|
76
|
+
}
|
|
77
|
+
return RESERVED_WORDS.has(identifier) ? `${identifier}Element` : identifier;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Produces the camelCase export name a registry element's locator module entry will use
|
|
81
|
+
* (ADR-006). Each call to the returned function assigns one element's name; two elements
|
|
82
|
+
* resolving to the same base name (e.g. two buttons both named "Submit") get a numeric suffix,
|
|
83
|
+
* tracked across every call against the same namer, so no two elements it names ever collide.
|
|
84
|
+
*/
|
|
85
|
+
export function createElementNamer() {
|
|
86
|
+
const seenNameCounts = new Map();
|
|
87
|
+
return (text, fallback) => {
|
|
88
|
+
const base = toCamelCaseIdentifier(text, fallback);
|
|
89
|
+
const seen = seenNameCounts.get(base) ?? 0;
|
|
90
|
+
seenNameCounts.set(base, seen + 1);
|
|
91
|
+
return seen === 0 ? base : `${base}${String(seen + 1)}`;
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
//# sourceMappingURL=naming.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"naming.js","sourceRoot":"","sources":["../src/naming.ts"],"names":[],"mappings":"AAAA,mCAAmC;AACnC,sCAAsC;AAEtC,6FAA6F;AAC7F,8FAA8F;AAC9F,SAAS,OAAO,CAAC,IAAY;IAC3B,OAAO,IAAI,CAAC,KAAK,CAAC,gBAAgB,CAAC,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC;AACxE,CAAC;AAED,6FAA6F;AAC7F,+CAA+C;AAC/C,MAAM,cAAc,GAAG,IAAI,GAAG,CAAC;IAC7B,OAAO;IACP,MAAM;IACN,OAAO;IACP,OAAO;IACP,OAAO;IACP,UAAU;IACV,UAAU;IACV,SAAS;IACT,QAAQ;IACR,IAAI;IACJ,MAAM;IACN,MAAM;IACN,QAAQ;IACR,SAAS;IACT,OAAO;IACP,SAAS;IACT,KAAK;IACL,UAAU;IACV,IAAI;IACJ,YAAY;IACZ,QAAQ;IACR,IAAI;IACJ,YAAY;IACZ,WAAW;IACX,KAAK;IACL,KAAK;IACL,MAAM;IACN,SAAS;IACT,SAAS;IACT,WAAW;IACX,QAAQ;IACR,QAAQ;IACR,QAAQ;IACR,OAAO;IACP,QAAQ;IACR,MAAM;IACN,OAAO;IACP,MAAM;IACN,KAAK;IACL,QAAQ;IACR,KAAK;IACL,MAAM;IACN,OAAO;IACP,MAAM;IACN,OAAO;CACR,CAAC,CAAC;AAEH,6EAA6E;AAC7E,+FAA+F;AAC/F,SAAS,UAAU,CAAC,IAAY;IAC9B,OAAO,GAAG,IAAI,CAAC,MAAM,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC;AAC3D,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,qBAAqB,CAAC,IAAY,EAAE,QAAgB;IAClE,MAAM,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC5B,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC5D,MAAM,UAAU,GAAG,MAAM;SACtB,GAAG,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,CAAC,CAAC;SACzF,IAAI,CAAC,EAAE,CAAC,CAAC;IACZ,IAAI,SAAS,CAAC,IAAI,CAAC,UAAU,CAAC,EAAE,CAAC;QAC/B,OAAO,UAAU,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;IAC5C,CAAC;IACD,OAAO,cAAc,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,GAAG,UAAU,SAAS,CAAC,CAAC,CAAC,UAAU,CAAC;AAC9E,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,kBAAkB;IAChC,MAAM,cAAc,GAAG,IAAI,GAAG,EAAkB,CAAC;IACjD,OAAO,CAAC,IAAI,EAAE,QAAQ,EAAE,EAAE;QACxB,MAAM,IAAI,GAAG,qBAAqB,CAAC,IAAI,EAAE,QAAQ,CAAC,CAAC;QACnD,MAAM,IAAI,GAAG,cAAc,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QAC3C,cAAc,CAAC,GAAG,CAAC,IAAI,EAAE,IAAI,GAAG,CAAC,CAAC,CAAC;QACnC,OAAO,IAAI,KAAK,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,GAAG,IAAI,GAAG,MAAM,CAAC,IAAI,GAAG,CAAC,CAAC,EAAE,CAAC;IAC1D,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
import { type AuthPage, type ViewportSize } from '@qa-ai-stlc/core';
|
|
2
|
+
import type { InteractiveElement, LocatorCandidate, SelectorElement } from '@qa-ai-stlc/schemas';
|
|
3
|
+
import { type LocatorPolicy } from './synthesize-locators.js';
|
|
4
|
+
/**
|
|
5
|
+
* One element a human captured by clicking it in the overlay, before locator synthesis: the same
|
|
6
|
+
* signal `extractPageElements()` reads for a crawled element (development plan section 6.1, "Manual
|
|
7
|
+
* pick mode"), scoped to the same five interactive tags a crawl recognizes so a picked element
|
|
8
|
+
* always resolves to a valid `InteractiveElementKind`.
|
|
9
|
+
*/
|
|
10
|
+
export interface PickModeCapture {
|
|
11
|
+
readonly pickId: string;
|
|
12
|
+
readonly kind: InteractiveElement['kind'];
|
|
13
|
+
readonly tagName: string;
|
|
14
|
+
readonly nthOfType: number;
|
|
15
|
+
readonly accessibleName?: string;
|
|
16
|
+
readonly testId?: string;
|
|
17
|
+
readonly role?: string;
|
|
18
|
+
readonly label?: string;
|
|
19
|
+
readonly placeholder?: string;
|
|
20
|
+
readonly htmlId?: string;
|
|
21
|
+
}
|
|
22
|
+
interface PickModeState {
|
|
23
|
+
readonly done: boolean;
|
|
24
|
+
readonly captures: readonly PickModeCapture[];
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Injects the pick-mode overlay into the current page (development plan section 6.1): a fixed
|
|
28
|
+
* toolbar with a "Finish picking" button, plus a capture-phase click listener that records the
|
|
29
|
+
* clicked element (or its closest interactive ancestor) instead of letting the click reach the
|
|
30
|
+
* application, so a click on a submit button never actually submits the form (safe mode, AGENTS.md
|
|
31
|
+
* 12.4). A second call on the same page is a no-op — the overlay is idempotent across re-navigation
|
|
32
|
+
* within the same pick-mode session.
|
|
33
|
+
*/
|
|
34
|
+
export declare function injectPickModeOverlay(page: AuthPage): Promise<void>;
|
|
35
|
+
/** Reads the overlay's current state: every element captured so far, and whether the human is done. */
|
|
36
|
+
export declare function readPickModeState(page: AuthPage): Promise<PickModeState>;
|
|
37
|
+
export interface WaitForPickModeCompletionOptions {
|
|
38
|
+
readonly pollIntervalMs?: number;
|
|
39
|
+
readonly timeoutMs?: number;
|
|
40
|
+
readonly signal?: AbortSignal;
|
|
41
|
+
/** Injected so tests do not wait on real timers (AGENTS.md 5.5, 13). */
|
|
42
|
+
readonly wait?: (ms: number) => Promise<void>;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Polls the overlay until the human clicks "Finish picking" (or the given `signal` cancels the
|
|
46
|
+
* session), returning every element captured. Never resolves early because nothing was clicked yet:
|
|
47
|
+
* an empty pick-mode session is a human decision, not a failure, so it only rejects on cancellation
|
|
48
|
+
* or on exceeding `timeoutMs`.
|
|
49
|
+
*/
|
|
50
|
+
export declare function waitForPickModeCompletion(page: AuthPage, options?: WaitForPickModeCompletionOptions): Promise<readonly PickModeCapture[]>;
|
|
51
|
+
/** One captured element with its locator candidates synthesized and its primary candidate scored. */
|
|
52
|
+
export interface PickModeElement {
|
|
53
|
+
readonly pickId: string;
|
|
54
|
+
readonly elementId: string;
|
|
55
|
+
readonly kind: string;
|
|
56
|
+
/** The human-meaningful text a confirmed name is derived from, before camelCasing (naming.ts). */
|
|
57
|
+
readonly defaultNameText: string;
|
|
58
|
+
readonly locatorCandidates: readonly LocatorCandidate[];
|
|
59
|
+
readonly stabilityScore: number;
|
|
60
|
+
}
|
|
61
|
+
export interface CapturePickModeElementsOptions {
|
|
62
|
+
readonly policy?: LocatorPolicy;
|
|
63
|
+
readonly viewports?: readonly ViewportSize[];
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Synthesizes locator candidates and scores the primary candidate for every element a human
|
|
67
|
+
* captured, against the live page they were picked from (development plan section 6.1 step 3). The
|
|
68
|
+
* `elementId` assigned here is the same one a crawl would assign the same element (build-selector-
|
|
69
|
+
* registry.ts's `computeElementId`), so a page later crawled does not duplicate a manually-picked
|
|
70
|
+
* entry.
|
|
71
|
+
*/
|
|
72
|
+
export declare function capturePickModeElements(page: AuthPage, url: string, captures: readonly PickModeCapture[], options?: CapturePickModeElementsOptions): Promise<PickModeElement[]>;
|
|
73
|
+
/**
|
|
74
|
+
* Turns captured, scored elements into `source: 'manual'` registry entries (development plan
|
|
75
|
+
* section 6.1: "the agent proposes logical names ... the human confirms"). Naming itself never
|
|
76
|
+
* happens here — the engine makes no model calls (AGENTS.md non-negotiable boundary 1) — so
|
|
77
|
+
* `nameOverrides` carries whatever name the human confirmed for a given `pickId`; an element with
|
|
78
|
+
* no override falls back to its `defaultNameText`. Names are deduplicated across the whole batch the
|
|
79
|
+
* same way a crawled registry's names are (naming.ts's `createElementNamer`).
|
|
80
|
+
*/
|
|
81
|
+
export declare function finalizeManualSelectorEntries(elements: readonly PickModeElement[], nameOverrides: ReadonlyMap<string, string>, generatedAt: string): SelectorElement[];
|
|
82
|
+
export {};
|
|
83
|
+
//# sourceMappingURL=pick-mode.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pick-mode.d.ts","sourceRoot":"","sources":["../src/pick-mode.ts"],"names":[],"mappings":"AAGA,OAAO,EAAW,KAAK,QAAQ,EAAE,KAAK,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAC7E,OAAO,KAAK,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,eAAe,EAAE,MAAM,qBAAqB,CAAC;AAIjG,OAAO,EAA+B,KAAK,aAAa,EAAE,MAAM,0BAA0B,CAAC;AAE3F;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,kBAAkB,CAAC,MAAM,CAAC,CAAC;IAC1C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAC9B,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC1B;AAED,UAAU,aAAa;IACrB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,QAAQ,EAAE,SAAS,eAAe,EAAE,CAAC;CAC/C;AAED;;;;;;;GAOG;AACH,wBAAsB,qBAAqB,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,IAAI,CAAC,CAoIzE;AAwBD,uGAAuG;AACvG,wBAAsB,iBAAiB,CAAC,IAAI,EAAE,QAAQ,GAAG,OAAO,CAAC,aAAa,CAAC,CAM9E;AAED,MAAM,WAAW,gCAAgC;IAC/C,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAC5B,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,CAAC;IAC9B,wEAAwE;IACxE,QAAQ,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC/C;AAUD;;;;;GAKG;AACH,wBAAsB,yBAAyB,CAC7C,IAAI,EAAE,QAAQ,EACd,OAAO,GAAE,gCAAqC,GAC7C,OAAO,CAAC,SAAS,eAAe,EAAE,CAAC,CAqBrC;AAED,qGAAqG;AACrG,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,kGAAkG;IAClG,QAAQ,CAAC,eAAe,EAAE,MAAM,CAAC;IACjC,QAAQ,CAAC,iBAAiB,EAAE,SAAS,gBAAgB,EAAE,CAAC;IACxD,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAC;CACjC;AAED,MAAM,WAAW,8BAA8B;IAC7C,QAAQ,CAAC,MAAM,CAAC,EAAE,aAAa,CAAC;IAChC,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,YAAY,EAAE,CAAC;CAC9C;AA6BD;;;;;;GAMG;AACH,wBAAsB,uBAAuB,CAC3C,IAAI,EAAE,QAAQ,EACd,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,SAAS,eAAe,EAAE,EACpC,OAAO,GAAE,8BAAmC,GAC3C,OAAO,CAAC,eAAe,EAAE,CAAC,CA4B5B;AAED;;;;;;;GAOG;AACH,wBAAgB,6BAA6B,CAC3C,QAAQ,EAAE,SAAS,eAAe,EAAE,EACpC,aAAa,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,EAC1C,WAAW,EAAE,MAAM,GAClB,eAAe,EAAE,CAanB"}
|
|
@@ -0,0 +1,265 @@
|
|
|
1
|
+
// Copyright The QA-AI-STLC Authors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import { QaError } from '@qa-ai-stlc/core';
|
|
4
|
+
import { computeElementId, elementNameForId } from './build-selector-registry.js';
|
|
5
|
+
import { createElementNamer } from './naming.js';
|
|
6
|
+
import { scoreLocatorStability } from './stability-scoring.js';
|
|
7
|
+
import { synthesizeLocatorCandidates } from './synthesize-locators.js';
|
|
8
|
+
/**
|
|
9
|
+
* Injects the pick-mode overlay into the current page (development plan section 6.1): a fixed
|
|
10
|
+
* toolbar with a "Finish picking" button, plus a capture-phase click listener that records the
|
|
11
|
+
* clicked element (or its closest interactive ancestor) instead of letting the click reach the
|
|
12
|
+
* application, so a click on a submit button never actually submits the form (safe mode, AGENTS.md
|
|
13
|
+
* 12.4). A second call on the same page is a no-op — the overlay is idempotent across re-navigation
|
|
14
|
+
* within the same pick-mode session.
|
|
15
|
+
*/
|
|
16
|
+
export async function injectPickModeOverlay(page) {
|
|
17
|
+
/* v8 ignore next 95 -- runs in the browser's own V8 instance, invisible to Node coverage */
|
|
18
|
+
await page.evaluate(() => {
|
|
19
|
+
const globalWindow = window;
|
|
20
|
+
if (globalWindow.__qaPickMode !== undefined) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const state = { done: false, captures: [] };
|
|
24
|
+
globalWindow.__qaPickMode = state;
|
|
25
|
+
let nextPickId = 1;
|
|
26
|
+
function accessibleName(element) {
|
|
27
|
+
const ariaLabel = element.getAttribute('aria-label');
|
|
28
|
+
if (ariaLabel !== null && ariaLabel.trim().length > 0) {
|
|
29
|
+
return ariaLabel;
|
|
30
|
+
}
|
|
31
|
+
const text = element.textContent.trim();
|
|
32
|
+
return text.length > 0 ? text : undefined;
|
|
33
|
+
}
|
|
34
|
+
function computeRole(element, kind) {
|
|
35
|
+
const explicit = element.getAttribute('role');
|
|
36
|
+
if (explicit !== null && explicit.trim().length > 0) {
|
|
37
|
+
return explicit;
|
|
38
|
+
}
|
|
39
|
+
if (kind === 'textarea') {
|
|
40
|
+
return 'textbox';
|
|
41
|
+
}
|
|
42
|
+
if (kind === 'select') {
|
|
43
|
+
return 'combobox';
|
|
44
|
+
}
|
|
45
|
+
if (kind === 'input') {
|
|
46
|
+
const type = (element.getAttribute('type') ?? 'text').toLowerCase();
|
|
47
|
+
if (type === 'checkbox' || type === 'radio') {
|
|
48
|
+
return type;
|
|
49
|
+
}
|
|
50
|
+
if (type === 'button' || type === 'submit' || type === 'reset') {
|
|
51
|
+
return 'button';
|
|
52
|
+
}
|
|
53
|
+
return 'textbox';
|
|
54
|
+
}
|
|
55
|
+
return kind === 'button' || kind === 'link' ? kind : undefined;
|
|
56
|
+
}
|
|
57
|
+
function labelText(element) {
|
|
58
|
+
const id = element.getAttribute('id');
|
|
59
|
+
if (id !== null && id.length > 0) {
|
|
60
|
+
const associated = document.querySelector(`label[for="${CSS.escape(id)}"]`);
|
|
61
|
+
const text = associated?.textContent.trim();
|
|
62
|
+
if (text !== undefined && text.length > 0) {
|
|
63
|
+
return text;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
const wrapping = element.closest('label')?.textContent.trim();
|
|
67
|
+
return wrapping !== undefined && wrapping.length > 0 ? wrapping : undefined;
|
|
68
|
+
}
|
|
69
|
+
function nthOfType(element) {
|
|
70
|
+
const parent = element.parentElement;
|
|
71
|
+
if (parent === null) {
|
|
72
|
+
return 1;
|
|
73
|
+
}
|
|
74
|
+
let index = 0;
|
|
75
|
+
for (const sibling of parent.children) {
|
|
76
|
+
if (sibling.tagName === element.tagName) {
|
|
77
|
+
index += 1;
|
|
78
|
+
if (sibling === element) {
|
|
79
|
+
return index;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return 1;
|
|
84
|
+
}
|
|
85
|
+
const kindByTagName = {
|
|
86
|
+
a: 'link',
|
|
87
|
+
button: 'button',
|
|
88
|
+
input: 'input',
|
|
89
|
+
select: 'select',
|
|
90
|
+
textarea: 'textarea',
|
|
91
|
+
};
|
|
92
|
+
const overlay = document.createElement('div');
|
|
93
|
+
overlay.id = 'qa-pick-mode-overlay';
|
|
94
|
+
overlay.style.cssText =
|
|
95
|
+
'position:fixed;top:0;right:0;z-index:2147483647;background:#111;color:#fff;' +
|
|
96
|
+
'padding:8px 12px;font:13px sans-serif;display:flex;gap:8px;align-items:center;';
|
|
97
|
+
overlay.textContent = 'QA pick mode: click an element to capture it.';
|
|
98
|
+
const finishButton = document.createElement('button');
|
|
99
|
+
finishButton.id = 'qa-pick-mode-finish';
|
|
100
|
+
finishButton.type = 'button';
|
|
101
|
+
finishButton.textContent = 'Finish picking';
|
|
102
|
+
finishButton.addEventListener('click', () => {
|
|
103
|
+
state.done = true;
|
|
104
|
+
});
|
|
105
|
+
overlay.appendChild(finishButton);
|
|
106
|
+
document.body.appendChild(overlay);
|
|
107
|
+
document.addEventListener('click', (event) => {
|
|
108
|
+
const target = event.target;
|
|
109
|
+
if (!(target instanceof Element) || target.closest('#qa-pick-mode-overlay') !== null) {
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
const interactive = target.closest('button, a[href], input, select, textarea');
|
|
113
|
+
if (interactive === null) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
event.preventDefault();
|
|
117
|
+
event.stopPropagation();
|
|
118
|
+
const tagName = interactive.tagName.toLowerCase();
|
|
119
|
+
const kind = kindByTagName[tagName] ?? tagName;
|
|
120
|
+
state.captures.push({
|
|
121
|
+
pickId: `pick-${String(nextPickId)}`,
|
|
122
|
+
kind,
|
|
123
|
+
tagName,
|
|
124
|
+
nthOfType: nthOfType(interactive),
|
|
125
|
+
accessibleName: accessibleName(interactive),
|
|
126
|
+
testId: interactive.getAttribute('data-testid') ?? undefined,
|
|
127
|
+
role: computeRole(interactive, kind),
|
|
128
|
+
label: labelText(interactive),
|
|
129
|
+
placeholder: interactive.getAttribute('placeholder') ?? undefined,
|
|
130
|
+
htmlId: interactive.getAttribute('id') ?? undefined,
|
|
131
|
+
});
|
|
132
|
+
nextPickId += 1;
|
|
133
|
+
}, { capture: true });
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
function isPickModeCapture(value) {
|
|
137
|
+
if (typeof value !== 'object' || value === null) {
|
|
138
|
+
return false;
|
|
139
|
+
}
|
|
140
|
+
const record = value;
|
|
141
|
+
return (typeof record.pickId === 'string' && typeof record.kind === 'string' && typeof record.tagName === 'string');
|
|
142
|
+
}
|
|
143
|
+
function isPickModeState(value) {
|
|
144
|
+
if (typeof value !== 'object' || value === null) {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
const record = value;
|
|
148
|
+
return (typeof record.done === 'boolean' &&
|
|
149
|
+
Array.isArray(record.captures) &&
|
|
150
|
+
record.captures.every(isPickModeCapture));
|
|
151
|
+
}
|
|
152
|
+
/** Reads the overlay's current state: every element captured so far, and whether the human is done. */
|
|
153
|
+
export async function readPickModeState(page) {
|
|
154
|
+
/* v8 ignore next 3 -- runs in the browser's own V8 instance, invisible to Node coverage */
|
|
155
|
+
const result = await page.evaluate(() => {
|
|
156
|
+
return window.__qaPickMode;
|
|
157
|
+
});
|
|
158
|
+
return isPickModeState(result) ? result : { done: false, captures: [] };
|
|
159
|
+
}
|
|
160
|
+
const DEFAULT_POLL_INTERVAL_MS = 500;
|
|
161
|
+
// A human research session, not a machine operation: generous, but still bounded (AGENTS.md 5.5).
|
|
162
|
+
const DEFAULT_TIMEOUT_MS = 30 * 60 * 1000;
|
|
163
|
+
function defaultWait(ms) {
|
|
164
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Polls the overlay until the human clicks "Finish picking" (or the given `signal` cancels the
|
|
168
|
+
* session), returning every element captured. Never resolves early because nothing was clicked yet:
|
|
169
|
+
* an empty pick-mode session is a human decision, not a failure, so it only rejects on cancellation
|
|
170
|
+
* or on exceeding `timeoutMs`.
|
|
171
|
+
*/
|
|
172
|
+
export async function waitForPickModeCompletion(page, options = {}) {
|
|
173
|
+
const pollIntervalMs = options.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
174
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
175
|
+
const wait = options.wait ?? defaultWait;
|
|
176
|
+
const deadline = Date.now() + timeoutMs;
|
|
177
|
+
for (;;) {
|
|
178
|
+
options.signal?.throwIfAborted();
|
|
179
|
+
const state = await readPickModeState(page);
|
|
180
|
+
if (state.done) {
|
|
181
|
+
return state.captures;
|
|
182
|
+
}
|
|
183
|
+
if (Date.now() >= deadline) {
|
|
184
|
+
throw new QaError('explorer.pick_mode.timeout', `Pick mode did not finish within ${String(timeoutMs)}ms`, { remediation: 'Click "Finish picking" in the overlay once every element has been captured.' });
|
|
185
|
+
}
|
|
186
|
+
await wait(pollIntervalMs);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
function toInteractiveElement(capture) {
|
|
190
|
+
const element = {
|
|
191
|
+
kind: capture.kind,
|
|
192
|
+
tagName: capture.tagName,
|
|
193
|
+
nthOfType: capture.nthOfType,
|
|
194
|
+
};
|
|
195
|
+
if (capture.accessibleName !== undefined) {
|
|
196
|
+
element.accessibleName = capture.accessibleName;
|
|
197
|
+
}
|
|
198
|
+
if (capture.testId !== undefined) {
|
|
199
|
+
element.testId = capture.testId;
|
|
200
|
+
}
|
|
201
|
+
if (capture.role !== undefined) {
|
|
202
|
+
element.role = capture.role;
|
|
203
|
+
}
|
|
204
|
+
if (capture.label !== undefined) {
|
|
205
|
+
element.label = capture.label;
|
|
206
|
+
}
|
|
207
|
+
if (capture.placeholder !== undefined) {
|
|
208
|
+
element.placeholder = capture.placeholder;
|
|
209
|
+
}
|
|
210
|
+
if (capture.htmlId !== undefined) {
|
|
211
|
+
element.htmlId = capture.htmlId;
|
|
212
|
+
}
|
|
213
|
+
return element;
|
|
214
|
+
}
|
|
215
|
+
/**
|
|
216
|
+
* Synthesizes locator candidates and scores the primary candidate for every element a human
|
|
217
|
+
* captured, against the live page they were picked from (development plan section 6.1 step 3). The
|
|
218
|
+
* `elementId` assigned here is the same one a crawl would assign the same element (build-selector-
|
|
219
|
+
* registry.ts's `computeElementId`), so a page later crawled does not duplicate a manually-picked
|
|
220
|
+
* entry.
|
|
221
|
+
*/
|
|
222
|
+
export async function capturePickModeElements(page, url, captures, options = {}) {
|
|
223
|
+
const policy = options.policy ?? 'playwright-default';
|
|
224
|
+
const elements = [];
|
|
225
|
+
for (const capture of captures) {
|
|
226
|
+
const interactiveElement = toInteractiveElement(capture);
|
|
227
|
+
const candidates = synthesizeLocatorCandidates(interactiveElement, policy);
|
|
228
|
+
const primary = candidates[0];
|
|
229
|
+
const stabilityScore = primary === undefined
|
|
230
|
+
? 0
|
|
231
|
+
: await scoreLocatorStability(page, primary, options.viewports === undefined ? {} : { viewports: options.viewports });
|
|
232
|
+
elements.push({
|
|
233
|
+
pickId: capture.pickId,
|
|
234
|
+
elementId: computeElementId(url, interactiveElement),
|
|
235
|
+
kind: interactiveElement.kind,
|
|
236
|
+
defaultNameText: elementNameForId(interactiveElement),
|
|
237
|
+
locatorCandidates: candidates,
|
|
238
|
+
stabilityScore,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
return elements;
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Turns captured, scored elements into `source: 'manual'` registry entries (development plan
|
|
245
|
+
* section 6.1: "the agent proposes logical names ... the human confirms"). Naming itself never
|
|
246
|
+
* happens here — the engine makes no model calls (AGENTS.md non-negotiable boundary 1) — so
|
|
247
|
+
* `nameOverrides` carries whatever name the human confirmed for a given `pickId`; an element with
|
|
248
|
+
* no override falls back to its `defaultNameText`. Names are deduplicated across the whole batch the
|
|
249
|
+
* same way a crawled registry's names are (naming.ts's `createElementNamer`).
|
|
250
|
+
*/
|
|
251
|
+
export function finalizeManualSelectorEntries(elements, nameOverrides, generatedAt) {
|
|
252
|
+
const nameFor = createElementNamer();
|
|
253
|
+
return elements.map((element) => ({
|
|
254
|
+
elementId: element.elementId,
|
|
255
|
+
name: nameFor(nameOverrides.get(element.pickId) ?? element.defaultNameText, element.kind),
|
|
256
|
+
kind: element.kind,
|
|
257
|
+
locatorCandidates: [...element.locatorCandidates],
|
|
258
|
+
stabilityScore: element.stabilityScore,
|
|
259
|
+
lastVerifiedAt: generatedAt,
|
|
260
|
+
pii: false,
|
|
261
|
+
dynamicText: false,
|
|
262
|
+
source: 'manual',
|
|
263
|
+
}));
|
|
264
|
+
}
|
|
265
|
+
//# sourceMappingURL=pick-mode.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"pick-mode.js","sourceRoot":"","sources":["../src/pick-mode.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qa-ai-stlc/explorer",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.0",
|
|
4
4
|
"description": "The crawler: route discovery in safe mode, redacted request logging, and the route map every later explorer phase builds on.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|