@qa-ai-stlc/explorer 0.3.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 +53 -0
- package/dist/build-selector-registry.d.ts.map +1 -0
- package/dist/build-selector-registry.js +116 -0
- package/dist/build-selector-registry.js.map +1 -0
- 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 +7 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +7 -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/dist/stability-scoring.d.ts +19 -0
- package/dist/stability-scoring.d.ts.map +1 -0
- package/dist/stability-scoring.js +82 -0
- package/dist/stability-scoring.js.map +1 -0
- package/package.json +3 -3
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,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,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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"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { AuthPage, PageLocator, ViewportSize } from '@qa-ai-stlc/core';
|
|
2
|
+
import type { LocatorCandidate } from '@qa-ai-stlc/schemas';
|
|
3
|
+
export interface StabilityScoringOptions {
|
|
4
|
+
/** Viewport sizes checked after uniqueness and reload survival; defaults to desktop/tablet/mobile. */
|
|
5
|
+
readonly viewports?: readonly ViewportSize[];
|
|
6
|
+
}
|
|
7
|
+
export declare const DEFAULT_VIEWPORTS: readonly ViewportSize[];
|
|
8
|
+
/** Resolves one of P1-08's `LocatorCandidate` values to a live `PageLocator`, by strategy. */
|
|
9
|
+
export declare function resolveCandidateLocator(page: AuthPage, candidate: LocatorCandidate): PageLocator;
|
|
10
|
+
/**
|
|
11
|
+
* Verifies one locator candidate against the current live page: uniqueness now, then survival
|
|
12
|
+
* across a reload and across each configured viewport size (development plan section 6.3.5).
|
|
13
|
+
* Uniqueness is a hard gate — a candidate that does not resolve to exactly one element right now
|
|
14
|
+
* cannot be called stable, so reload/viewport are never checked against a candidate that has
|
|
15
|
+
* already failed. The page's original viewport size is restored before returning, so scoring one
|
|
16
|
+
* element's candidate does not affect the next.
|
|
17
|
+
*/
|
|
18
|
+
export declare function scoreLocatorStability(page: AuthPage, candidate: LocatorCandidate, options?: StabilityScoringOptions): Promise<number>;
|
|
19
|
+
//# sourceMappingURL=stability-scoring.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stability-scoring.d.ts","sourceRoot":"","sources":["../src/stability-scoring.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,QAAQ,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAC;AAE5E,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AAE5D,MAAM,WAAW,uBAAuB;IACtC,sGAAsG;IACtG,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,YAAY,EAAE,CAAC;CAC9C;AAED,eAAO,MAAM,iBAAiB,EAAE,SAAS,YAAY,EAIpD,CAAC;AAkCF,8FAA8F;AAC9F,wBAAgB,uBAAuB,CAAC,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,gBAAgB,GAAG,WAAW,CAmBhG;AAOD;;;;;;;GAOG;AACH,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,QAAQ,EACd,SAAS,EAAE,gBAAgB,EAC3B,OAAO,GAAE,uBAA4B,GACpC,OAAO,CAAC,MAAM,CAAC,CA0BjB"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// Copyright The QA-AI-STLC Authors
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
import { QaError } from '@qa-ai-stlc/core';
|
|
4
|
+
export const DEFAULT_VIEWPORTS = [
|
|
5
|
+
{ width: 1280, height: 720 },
|
|
6
|
+
{ width: 768, height: 1024 },
|
|
7
|
+
{ width: 375, height: 667 },
|
|
8
|
+
];
|
|
9
|
+
function parseRoleCandidateValue(value) {
|
|
10
|
+
let parsed;
|
|
11
|
+
try {
|
|
12
|
+
parsed = JSON.parse(value);
|
|
13
|
+
}
|
|
14
|
+
catch (cause) {
|
|
15
|
+
throw new QaError('locator-candidate-invalid', `Role locator candidate value is not valid JSON: "${value}".`, { cause });
|
|
16
|
+
}
|
|
17
|
+
if (typeof parsed !== 'object' ||
|
|
18
|
+
parsed === null ||
|
|
19
|
+
typeof parsed.role !== 'string') {
|
|
20
|
+
throw new QaError('locator-candidate-invalid', `Role locator candidate value has no "role" string: "${value}".`);
|
|
21
|
+
}
|
|
22
|
+
const record = parsed;
|
|
23
|
+
return typeof record.name === 'string'
|
|
24
|
+
? { role: record.role, name: record.name }
|
|
25
|
+
: { role: record.role };
|
|
26
|
+
}
|
|
27
|
+
/** Resolves one of P1-08's `LocatorCandidate` values to a live `PageLocator`, by strategy. */
|
|
28
|
+
export function resolveCandidateLocator(page, candidate) {
|
|
29
|
+
switch (candidate.strategy) {
|
|
30
|
+
case 'role': {
|
|
31
|
+
const { role, name } = parseRoleCandidateValue(candidate.value);
|
|
32
|
+
return page.getByRole(role, name === undefined ? undefined : { name });
|
|
33
|
+
}
|
|
34
|
+
case 'testId':
|
|
35
|
+
return page.getByTestId(candidate.value);
|
|
36
|
+
case 'label':
|
|
37
|
+
return page.getByLabel(candidate.value);
|
|
38
|
+
case 'placeholder':
|
|
39
|
+
return page.getByPlaceholder(candidate.value);
|
|
40
|
+
case 'text':
|
|
41
|
+
return page.getByText(candidate.value);
|
|
42
|
+
case 'css':
|
|
43
|
+
return page.locator(candidate.value);
|
|
44
|
+
default:
|
|
45
|
+
throw new QaError('locator-strategy-unknown', `Unknown locator strategy "${candidate.strategy}".`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async function isUniqueMatch(page, candidate) {
|
|
49
|
+
const count = await resolveCandidateLocator(page, candidate).count();
|
|
50
|
+
return count === 1;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Verifies one locator candidate against the current live page: uniqueness now, then survival
|
|
54
|
+
* across a reload and across each configured viewport size (development plan section 6.3.5).
|
|
55
|
+
* Uniqueness is a hard gate — a candidate that does not resolve to exactly one element right now
|
|
56
|
+
* cannot be called stable, so reload/viewport are never checked against a candidate that has
|
|
57
|
+
* already failed. The page's original viewport size is restored before returning, so scoring one
|
|
58
|
+
* element's candidate does not affect the next.
|
|
59
|
+
*/
|
|
60
|
+
export async function scoreLocatorStability(page, candidate, options = {}) {
|
|
61
|
+
if (!(await isUniqueMatch(page, candidate))) {
|
|
62
|
+
return 0;
|
|
63
|
+
}
|
|
64
|
+
const viewports = options.viewports ?? DEFAULT_VIEWPORTS;
|
|
65
|
+
const originalViewport = page.viewportSize();
|
|
66
|
+
let survivedChecks = 0;
|
|
67
|
+
await page.reload();
|
|
68
|
+
if (await isUniqueMatch(page, candidate)) {
|
|
69
|
+
survivedChecks += 1;
|
|
70
|
+
}
|
|
71
|
+
for (const viewport of viewports) {
|
|
72
|
+
await page.setViewportSize(viewport);
|
|
73
|
+
if (await isUniqueMatch(page, candidate)) {
|
|
74
|
+
survivedChecks += 1;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
if (originalViewport !== null) {
|
|
78
|
+
await page.setViewportSize(originalViewport);
|
|
79
|
+
}
|
|
80
|
+
return survivedChecks / (1 + viewports.length);
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=stability-scoring.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"stability-scoring.js","sourceRoot":"","sources":["../src/stability-scoring.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",
|
|
@@ -28,8 +28,8 @@
|
|
|
28
28
|
"clean": "node ../../scripts/clean-dir.mjs dist"
|
|
29
29
|
},
|
|
30
30
|
"dependencies": {
|
|
31
|
-
"@qa-ai-stlc/core": "0.
|
|
32
|
-
"@qa-ai-stlc/schemas": "0.
|
|
31
|
+
"@qa-ai-stlc/core": "0.3.0",
|
|
32
|
+
"@qa-ai-stlc/schemas": "0.4.0"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@qa-ai-stlc/test-utils": "0.0.0"
|