@dyyz1993/agent-browser 0.13.2 → 0.23.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/bin/agent-browser-linux-x64 +0 -0
- package/dist/__tests__/e2e/utils/test-helpers.d.ts +1 -0
- package/dist/__tests__/e2e/utils/test-helpers.d.ts.map +1 -1
- package/dist/__tests__/e2e/utils/test-helpers.js +14 -1
- package/dist/__tests__/e2e/utils/test-helpers.js.map +1 -1
- package/dist/__tests__/utils/parseCli.d.ts.map +1 -1
- package/dist/__tests__/utils/parseCli.js +83 -1
- package/dist/__tests__/utils/parseCli.js.map +1 -1
- package/dist/actions.d.ts.map +1 -1
- package/dist/actions.js +269 -0
- package/dist/actions.js.map +1 -1
- package/dist/browser.d.ts +11 -1
- package/dist/browser.d.ts.map +1 -1
- package/dist/browser.js +63 -2
- package/dist/browser.js.map +1 -1
- package/dist/cli/commands.d.ts.map +1 -1
- package/dist/cli/commands.js +165 -1
- package/dist/cli/commands.js.map +1 -1
- package/dist/cli/connection.d.ts +13 -0
- package/dist/cli/connection.d.ts.map +1 -1
- package/dist/cli/connection.js +51 -1
- package/dist/cli/connection.js.map +1 -1
- package/dist/cli/help.d.ts.map +1 -1
- package/dist/cli/help.js +39 -0
- package/dist/cli/help.js.map +1 -1
- package/dist/cli.js +20 -1
- package/dist/cli.js.map +1 -1
- package/dist/daemon.d.ts +1 -0
- package/dist/daemon.d.ts.map +1 -1
- package/dist/daemon.js +22 -0
- package/dist/daemon.js.map +1 -1
- package/dist/diff.d.ts.map +1 -1
- package/dist/diff.js +1 -1
- package/dist/diff.js.map +1 -1
- package/dist/flow/exporters/index.d.ts +4 -0
- package/dist/flow/exporters/index.d.ts.map +1 -0
- package/dist/flow/exporters/index.js +3 -0
- package/dist/flow/exporters/index.js.map +1 -0
- package/dist/flow/exporters/playwright.d.ts +20 -0
- package/dist/flow/exporters/playwright.d.ts.map +1 -0
- package/dist/flow/exporters/playwright.js +175 -0
- package/dist/flow/exporters/playwright.js.map +1 -0
- package/dist/flow/exporters/python.d.ts +20 -0
- package/dist/flow/exporters/python.d.ts.map +1 -0
- package/dist/flow/exporters/python.js +163 -0
- package/dist/flow/exporters/python.js.map +1 -0
- package/dist/flow/exporters/types.d.ts +13 -0
- package/dist/flow/exporters/types.d.ts.map +1 -0
- package/dist/flow/exporters/types.js +2 -0
- package/dist/flow/exporters/types.js.map +1 -0
- package/dist/flow/flow-executor.d.ts +55 -0
- package/dist/flow/flow-executor.d.ts.map +1 -0
- package/dist/flow/flow-executor.js +1169 -0
- package/dist/flow/flow-executor.js.map +1 -0
- package/dist/flow/index.d.ts +15 -0
- package/dist/flow/index.d.ts.map +1 -0
- package/dist/flow/index.js +10 -0
- package/dist/flow/index.js.map +1 -0
- package/dist/flow/output.d.ts +11 -0
- package/dist/flow/output.d.ts.map +1 -0
- package/dist/flow/output.js +84 -0
- package/dist/flow/output.js.map +1 -0
- package/dist/flow/plugin-system.d.ts +48 -0
- package/dist/flow/plugin-system.d.ts.map +1 -0
- package/dist/flow/plugin-system.js +132 -0
- package/dist/flow/plugin-system.js.map +1 -0
- package/dist/flow/plugins/file-output-plugin.d.ts +8 -0
- package/dist/flow/plugins/file-output-plugin.d.ts.map +1 -0
- package/dist/flow/plugins/file-output-plugin.js +31 -0
- package/dist/flow/plugins/file-output-plugin.js.map +1 -0
- package/dist/flow/plugins/index.d.ts +4 -0
- package/dist/flow/plugins/index.d.ts.map +1 -0
- package/dist/flow/plugins/index.js +4 -0
- package/dist/flow/plugins/index.js.map +1 -0
- package/dist/flow/plugins/logging-plugin.d.ts +7 -0
- package/dist/flow/plugins/logging-plugin.d.ts.map +1 -0
- package/dist/flow/plugins/logging-plugin.js +40 -0
- package/dist/flow/plugins/logging-plugin.js.map +1 -0
- package/dist/flow/plugins/webhook-plugin.d.ts +7 -0
- package/dist/flow/plugins/webhook-plugin.d.ts.map +1 -0
- package/dist/flow/plugins/webhook-plugin.js +24 -0
- package/dist/flow/plugins/webhook-plugin.js.map +1 -0
- package/dist/flow/presets/index.d.ts +10 -0
- package/dist/flow/presets/index.d.ts.map +1 -0
- package/dist/flow/presets/index.js +29 -0
- package/dist/flow/presets/index.js.map +1 -0
- package/dist/flow/recorder-to-flow.d.ts +70 -0
- package/dist/flow/recorder-to-flow.d.ts.map +1 -0
- package/dist/flow/recorder-to-flow.js +392 -0
- package/dist/flow/recorder-to-flow.js.map +1 -0
- package/dist/flow/site-manager.d.ts +24 -0
- package/dist/flow/site-manager.d.ts.map +1 -0
- package/dist/flow/site-manager.js +125 -0
- package/dist/flow/site-manager.js.map +1 -0
- package/dist/flow/types.d.ts +181 -0
- package/dist/flow/types.d.ts.map +1 -0
- package/dist/flow/types.js +2 -0
- package/dist/flow/types.js.map +1 -0
- package/dist/flow/yaml-parser.d.ts +15 -0
- package/dist/flow/yaml-parser.d.ts.map +1 -0
- package/dist/flow/yaml-parser.js +214 -0
- package/dist/flow/yaml-parser.js.map +1 -0
- package/dist/protocol.d.ts.map +1 -1
- package/dist/protocol.js +15 -0
- package/dist/protocol.js.map +1 -1
- package/dist/recorder/inject.js +730 -332
- package/dist/snapshot-store.d.ts +77 -0
- package/dist/snapshot-store.d.ts.map +1 -0
- package/dist/snapshot-store.js +97 -0
- package/dist/snapshot-store.js.map +1 -0
- package/dist/snapshot.d.ts +6 -7
- package/dist/snapshot.d.ts.map +1 -1
- package/dist/snapshot.js +437 -1
- package/dist/snapshot.js.map +1 -1
- package/dist/types.d.ts +13 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/viewer-script.js +4 -4
- package/dist/viewer-script.js.map +1 -1
- package/package.json +7 -3
- package/skills/agent-browser/SKILL.md +102 -3
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SnapshotStore - In-memory store for snapshot data with stable selectors.
|
|
3
|
+
*
|
|
4
|
+
* Each snapshot gets a unique sequential ID (snap_1, snap_2, ...).
|
|
5
|
+
* Elements within each snapshot have stable CSS selectors and XPaths
|
|
6
|
+
* that can be retrieved later by snapshot_id + ref/index.
|
|
7
|
+
*/
|
|
8
|
+
export interface SnapshotElement {
|
|
9
|
+
/** Ref ID (e.g., "e1") */
|
|
10
|
+
ref: string;
|
|
11
|
+
/** Display index (1-based, e.g., 1, 2, 3) */
|
|
12
|
+
index: number;
|
|
13
|
+
/** ARIA role (e.g., "button", "link", "textbox") */
|
|
14
|
+
role: string;
|
|
15
|
+
/** Accessible name (e.g., "Submit") */
|
|
16
|
+
name?: string;
|
|
17
|
+
/** Stable CSS selector (e.g., "#su", "input[name='wd']") */
|
|
18
|
+
cssSelector: string;
|
|
19
|
+
/** Stable XPath (e.g., "//*[@id='su']") */
|
|
20
|
+
xpath: string;
|
|
21
|
+
}
|
|
22
|
+
export interface SnapshotEntry {
|
|
23
|
+
/** Unique snapshot ID (e.g., "snap_3") */
|
|
24
|
+
id: string;
|
|
25
|
+
/** Creation timestamp */
|
|
26
|
+
timestamp: number;
|
|
27
|
+
/** Page URL at snapshot time */
|
|
28
|
+
url: string;
|
|
29
|
+
/** Iframe path if snapshot was taken inside an iframe */
|
|
30
|
+
framePath?: string;
|
|
31
|
+
/** Elements keyed by ref ID (e.g., "e1" -> SnapshotElement) */
|
|
32
|
+
elements: Map<string, SnapshotElement>;
|
|
33
|
+
/** Whether stable selectors have been generated for this snapshot */
|
|
34
|
+
selectorsGenerated: boolean;
|
|
35
|
+
}
|
|
36
|
+
export declare class SnapshotStore {
|
|
37
|
+
private snapshots;
|
|
38
|
+
private counter;
|
|
39
|
+
/**
|
|
40
|
+
* Create a new snapshot entry.
|
|
41
|
+
* @returns The generated snapshot ID (e.g., "snap_3")
|
|
42
|
+
*/
|
|
43
|
+
create(url: string, elements: SnapshotElement[], framePath?: string): string;
|
|
44
|
+
/**
|
|
45
|
+
* Get a snapshot entry by ID.
|
|
46
|
+
*/
|
|
47
|
+
get(id: string): SnapshotEntry | undefined;
|
|
48
|
+
/**
|
|
49
|
+
* Get a specific element from a snapshot by ref or index.
|
|
50
|
+
*
|
|
51
|
+
* @param snapId - Snapshot ID (e.g., "snap_3")
|
|
52
|
+
* @param refOrIndex - Either a ref like "e1" or "@e1", or an index like "1"
|
|
53
|
+
* @returns The SnapshotElement or undefined
|
|
54
|
+
*/
|
|
55
|
+
getElement(snapId: string, refOrIndex: string): SnapshotElement | undefined;
|
|
56
|
+
/**
|
|
57
|
+
* Get all elements from a snapshot as an array, sorted by index.
|
|
58
|
+
*/
|
|
59
|
+
getElements(snapId: string): SnapshotElement[] | undefined;
|
|
60
|
+
/**
|
|
61
|
+
* Get the current counter value (for testing/debugging).
|
|
62
|
+
*/
|
|
63
|
+
getCounter(): number;
|
|
64
|
+
/**
|
|
65
|
+
* Check if a snapshot exists.
|
|
66
|
+
*/
|
|
67
|
+
has(id: string): boolean;
|
|
68
|
+
/**
|
|
69
|
+
* Mark selectors as generated for a snapshot.
|
|
70
|
+
*/
|
|
71
|
+
markSelectorsGenerated(id: string): void;
|
|
72
|
+
/**
|
|
73
|
+
* Check if selectors have been generated for a snapshot.
|
|
74
|
+
*/
|
|
75
|
+
isSelectorsGenerated(id: string): boolean;
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=snapshot-store.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshot-store.d.ts","sourceRoot":"","sources":["../src/snapshot-store.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,MAAM,WAAW,eAAe;IAC9B,0BAA0B;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,6CAA6C;IAC7C,KAAK,EAAE,MAAM,CAAC;IACd,oDAAoD;IACpD,IAAI,EAAE,MAAM,CAAC;IACb,uCAAuC;IACvC,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,4DAA4D;IAC5D,WAAW,EAAE,MAAM,CAAC;IACpB,2CAA2C;IAC3C,KAAK,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,aAAa;IAC5B,0CAA0C;IAC1C,EAAE,EAAE,MAAM,CAAC;IACX,yBAAyB;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,gCAAgC;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,yDAAyD;IACzD,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,+DAA+D;IAC/D,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,eAAe,CAAC,CAAC;IACvC,qEAAqE;IACrE,kBAAkB,EAAE,OAAO,CAAC;CAC7B;AAED,qBAAa,aAAa;IACxB,OAAO,CAAC,SAAS,CAAyC;IAC1D,OAAO,CAAC,OAAO,CAAa;IAE5B;;;OAGG;IACH,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,EAAE,eAAe,EAAE,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM;IAc5E;;OAEG;IACH,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,aAAa,GAAG,SAAS;IAI1C;;;;;;OAMG;IACH,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,eAAe,GAAG,SAAS;IAuB3E;;OAEG;IACH,WAAW,CAAC,MAAM,EAAE,MAAM,GAAG,eAAe,EAAE,GAAG,SAAS;IAM1D;;OAEG;IACH,UAAU,IAAI,MAAM;IAIpB;;OAEG;IACH,GAAG,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;IAIxB;;OAEG;IACH,sBAAsB,CAAC,EAAE,EAAE,MAAM,GAAG,IAAI;IAKxC;;OAEG;IACH,oBAAoB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO;CAG1C"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SnapshotStore - In-memory store for snapshot data with stable selectors.
|
|
3
|
+
*
|
|
4
|
+
* Each snapshot gets a unique sequential ID (snap_1, snap_2, ...).
|
|
5
|
+
* Elements within each snapshot have stable CSS selectors and XPaths
|
|
6
|
+
* that can be retrieved later by snapshot_id + ref/index.
|
|
7
|
+
*/
|
|
8
|
+
export class SnapshotStore {
|
|
9
|
+
snapshots = new Map();
|
|
10
|
+
counter = 0;
|
|
11
|
+
/**
|
|
12
|
+
* Create a new snapshot entry.
|
|
13
|
+
* @returns The generated snapshot ID (e.g., "snap_3")
|
|
14
|
+
*/
|
|
15
|
+
create(url, elements, framePath) {
|
|
16
|
+
const id = `snap_${++this.counter}`;
|
|
17
|
+
const elementMap = new Map(elements.map((e) => [e.ref, e]));
|
|
18
|
+
this.snapshots.set(id, {
|
|
19
|
+
id,
|
|
20
|
+
timestamp: Date.now(),
|
|
21
|
+
url,
|
|
22
|
+
framePath,
|
|
23
|
+
elements: elementMap,
|
|
24
|
+
selectorsGenerated: false,
|
|
25
|
+
});
|
|
26
|
+
return id;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Get a snapshot entry by ID.
|
|
30
|
+
*/
|
|
31
|
+
get(id) {
|
|
32
|
+
return this.snapshots.get(id);
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Get a specific element from a snapshot by ref or index.
|
|
36
|
+
*
|
|
37
|
+
* @param snapId - Snapshot ID (e.g., "snap_3")
|
|
38
|
+
* @param refOrIndex - Either a ref like "e1" or "@e1", or an index like "1"
|
|
39
|
+
* @returns The SnapshotElement or undefined
|
|
40
|
+
*/
|
|
41
|
+
getElement(snapId, refOrIndex) {
|
|
42
|
+
const entry = this.snapshots.get(snapId);
|
|
43
|
+
if (!entry)
|
|
44
|
+
return undefined;
|
|
45
|
+
// Strip @ prefix if present (e.g., "@e1" -> "e1")
|
|
46
|
+
const cleaned = refOrIndex.startsWith('@') ? refOrIndex.slice(1) : refOrIndex;
|
|
47
|
+
// Try as ref first (e.g., "e1")
|
|
48
|
+
if (entry.elements.has(cleaned)) {
|
|
49
|
+
return entry.elements.get(cleaned);
|
|
50
|
+
}
|
|
51
|
+
// Try as index (e.g., "1")
|
|
52
|
+
const index = parseInt(cleaned, 10);
|
|
53
|
+
if (!isNaN(index)) {
|
|
54
|
+
for (const el of entry.elements.values()) {
|
|
55
|
+
if (el.index === index)
|
|
56
|
+
return el;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Get all elements from a snapshot as an array, sorted by index.
|
|
63
|
+
*/
|
|
64
|
+
getElements(snapId) {
|
|
65
|
+
const entry = this.snapshots.get(snapId);
|
|
66
|
+
if (!entry)
|
|
67
|
+
return undefined;
|
|
68
|
+
return Array.from(entry.elements.values()).sort((a, b) => a.index - b.index);
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Get the current counter value (for testing/debugging).
|
|
72
|
+
*/
|
|
73
|
+
getCounter() {
|
|
74
|
+
return this.counter;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Check if a snapshot exists.
|
|
78
|
+
*/
|
|
79
|
+
has(id) {
|
|
80
|
+
return this.snapshots.has(id);
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Mark selectors as generated for a snapshot.
|
|
84
|
+
*/
|
|
85
|
+
markSelectorsGenerated(id) {
|
|
86
|
+
const entry = this.snapshots.get(id);
|
|
87
|
+
if (entry)
|
|
88
|
+
entry.selectorsGenerated = true;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Check if selectors have been generated for a snapshot.
|
|
92
|
+
*/
|
|
93
|
+
isSelectorsGenerated(id) {
|
|
94
|
+
return this.snapshots.get(id)?.selectorsGenerated ?? false;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=snapshot-store.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"snapshot-store.js","sourceRoot":"","sources":["../src/snapshot-store.ts"],"names":[],"mappings":"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"}
|
package/dist/snapshot.d.ts
CHANGED
|
@@ -37,25 +37,24 @@ export interface EnhancedSnapshot {
|
|
|
37
37
|
refs: RefMap;
|
|
38
38
|
}
|
|
39
39
|
export interface SnapshotOptions {
|
|
40
|
-
/** Only include interactive elements (buttons, links, inputs, etc.) */
|
|
41
40
|
interactive?: boolean;
|
|
42
|
-
/** Include cursor-interactive elements (cursor:pointer, onclick, tabindex) */
|
|
43
41
|
cursor?: boolean;
|
|
44
|
-
/** Maximum depth of tree to include (0 = root only) */
|
|
45
42
|
maxDepth?: number;
|
|
46
|
-
/** Remove structural elements without meaningful content */
|
|
47
43
|
compact?: boolean;
|
|
48
|
-
/** CSS selector to scope the snapshot */
|
|
49
44
|
selector?: string;
|
|
50
|
-
/** Include xpath and cssPath in refs (requires selector) */
|
|
51
45
|
path?: boolean;
|
|
52
|
-
/** Include element attributes in refs (requires selector) */
|
|
53
46
|
attrs?: boolean;
|
|
47
|
+
selectors?: boolean;
|
|
48
|
+
all?: boolean;
|
|
54
49
|
}
|
|
55
50
|
/**
|
|
56
51
|
* Reset ref counter (call at start of each snapshot)
|
|
57
52
|
*/
|
|
58
53
|
export declare function resetRefs(): void;
|
|
54
|
+
export declare function generateStableSelectors(page: Page | Frame, refs: RefMap): Promise<Record<string, {
|
|
55
|
+
cssSelector: string;
|
|
56
|
+
xpath: string;
|
|
57
|
+
}>>;
|
|
59
58
|
/**
|
|
60
59
|
* Get enhanced snapshot with refs and optional filtering
|
|
61
60
|
*/
|
package/dist/snapshot.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"snapshot.d.ts","sourceRoot":"","sources":["../src/snapshot.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,KAAK,EAAW,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAE1E,MAAM,WAAW,MAAM;IACrB,CAAC,GAAG,EAAE,MAAM,GAAG;QACb,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,0EAA0E;QAC1E,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,uCAAuC;QACvC,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,8CAA8C;QAC9C,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,gDAAgD;QAChD,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KACrC,CAAC;CACH;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,eAAe;IAC9B,
|
|
1
|
+
{"version":3,"file":"snapshot.d.ts","sourceRoot":"","sources":["../src/snapshot.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,KAAK,EAAW,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAE1E,MAAM,WAAW,MAAM;IACrB,CAAC,GAAG,EAAE,MAAM,GAAG;QACb,QAAQ,EAAE,MAAM,CAAC;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,0EAA0E;QAC1E,GAAG,CAAC,EAAE,MAAM,CAAC;QACb,uCAAuC;QACvC,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,8CAA8C;QAC9C,OAAO,CAAC,EAAE,MAAM,CAAC;QACjB,gDAAgD;QAChD,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;KACrC,CAAC;CACH;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,eAAe;IAC9B,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAKD;;GAEG;AACH,wBAAgB,SAAS,IAAI,IAAI,CAEhC;AAwTD,wBAAsB,uBAAuB,CAC3C,IAAI,EAAE,IAAI,GAAG,KAAK,EAClB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE;IAAE,WAAW,EAAE,MAAM,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC,CAAC,CA4VjE;AAED;;GAEG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,IAAI,GAAG,KAAK,GAAG,YAAY,EACjC,OAAO,GAAE,eAAoB,GAC5B,OAAO,CAAC,gBAAgB,CAAC,CAwF3B;AA44BD;;GAEG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAcnD;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,EACZ,IAAI,EAAE,MAAM,GACX;IACD,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;CACrB,CAUA;AA6BD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,GAAG,IAAI,CAYhE;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,GAAE,MAAU,GAAG,MAAM,CAqB5E;AAkDD,wBAAgB,eAAe,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,GAAE,MAAU,GAAG,MAAM,CAyC9E;AAyBD,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAS1E"}
|
package/dist/snapshot.js
CHANGED
|
@@ -311,6 +311,369 @@ async function suggestSelectors(page) {
|
|
|
311
311
|
}
|
|
312
312
|
return selectors;
|
|
313
313
|
}
|
|
314
|
+
export async function generateStableSelectors(page, refs) {
|
|
315
|
+
const result = {};
|
|
316
|
+
for (const [ref, data] of Object.entries(refs)) {
|
|
317
|
+
if (data.role === 'clickable' || data.role === 'focusable') {
|
|
318
|
+
if (data.selector && !data.selector.startsWith('getByRole')) {
|
|
319
|
+
result[ref] = { cssSelector: data.selector, xpath: '' };
|
|
320
|
+
}
|
|
321
|
+
continue;
|
|
322
|
+
}
|
|
323
|
+
try {
|
|
324
|
+
let locator;
|
|
325
|
+
if (data.name) {
|
|
326
|
+
locator = page.getByRole(data.role, { name: data.name, exact: true });
|
|
327
|
+
}
|
|
328
|
+
else {
|
|
329
|
+
locator = page.getByRole(data.role);
|
|
330
|
+
}
|
|
331
|
+
if (data.nth !== undefined) {
|
|
332
|
+
locator = locator.nth(data.nth);
|
|
333
|
+
}
|
|
334
|
+
const elementCount = await locator.count();
|
|
335
|
+
if (elementCount === 0)
|
|
336
|
+
continue;
|
|
337
|
+
const selectorData = await locator
|
|
338
|
+
.evaluate((el) => {
|
|
339
|
+
const UTILITY_CLASS_PATTERNS = [
|
|
340
|
+
/^_/,
|
|
341
|
+
/^css-/,
|
|
342
|
+
/^[a-z]{1,2}$/,
|
|
343
|
+
/^(active|disabled|hidden|visible|selected|hover|focus|current|open|closed)$/i,
|
|
344
|
+
/^(text-|font-|bg-|p-|m-|w-|h-|flex|grid|border|rounded|shadow|opacity|z-)/,
|
|
345
|
+
/^(sm:|md:|lg:|xl:|2xl:)/,
|
|
346
|
+
];
|
|
347
|
+
const SEMANTIC_ATTRS = [
|
|
348
|
+
'data-testid',
|
|
349
|
+
'data-test',
|
|
350
|
+
'data-cy',
|
|
351
|
+
'name',
|
|
352
|
+
'aria-label',
|
|
353
|
+
'aria-labelledby',
|
|
354
|
+
'role',
|
|
355
|
+
'type',
|
|
356
|
+
'placeholder',
|
|
357
|
+
'title',
|
|
358
|
+
'alt',
|
|
359
|
+
];
|
|
360
|
+
function isHighEntropyClassName(className) {
|
|
361
|
+
if (!className || className.length < 4 || className.length > 15)
|
|
362
|
+
return false;
|
|
363
|
+
if (/^[a-zA-Z]+_[a-zA-Z]+_{2}[a-zA-Z0-9]+$/.test(className))
|
|
364
|
+
return true;
|
|
365
|
+
if (/^sc-[a-zA-Z0-9]+$/.test(className))
|
|
366
|
+
return true;
|
|
367
|
+
const hasUpper = /[A-Z]/.test(className);
|
|
368
|
+
const hasLower = /[a-z]/.test(className);
|
|
369
|
+
const hasDigit = /[0-9]/.test(className);
|
|
370
|
+
const hasSeparator = /[-_]/.test(className);
|
|
371
|
+
if (hasSeparator)
|
|
372
|
+
return false;
|
|
373
|
+
if (hasUpper && hasLower && hasDigit)
|
|
374
|
+
return true;
|
|
375
|
+
if (/^[A-Z][a-z0-9]+[A-Z]/.test(className) && className.length <= 12)
|
|
376
|
+
return true;
|
|
377
|
+
if (/^[a-z]/.test(className) && /[a-z][A-Z][a-z][A-Z]/.test(className))
|
|
378
|
+
return true;
|
|
379
|
+
return false;
|
|
380
|
+
}
|
|
381
|
+
function isUniqueSelector(selector) {
|
|
382
|
+
try {
|
|
383
|
+
return document.querySelectorAll(selector).length === 1;
|
|
384
|
+
}
|
|
385
|
+
catch {
|
|
386
|
+
return false;
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
function filterUsefulClasses(element) {
|
|
390
|
+
if (!element.className || typeof element.className !== 'string')
|
|
391
|
+
return [];
|
|
392
|
+
return element.className
|
|
393
|
+
.trim()
|
|
394
|
+
.split(/\s+/)
|
|
395
|
+
.filter((c) => {
|
|
396
|
+
if (!c)
|
|
397
|
+
return false;
|
|
398
|
+
if (UTILITY_CLASS_PATTERNS.some((p) => p.test(c)))
|
|
399
|
+
return false;
|
|
400
|
+
if (isHighEntropyClassName(c))
|
|
401
|
+
return false;
|
|
402
|
+
return true;
|
|
403
|
+
});
|
|
404
|
+
}
|
|
405
|
+
function tryIdSelector(element) {
|
|
406
|
+
if (element.id) {
|
|
407
|
+
const sel = '#' + CSS.escape(element.id);
|
|
408
|
+
if (isUniqueSelector(sel))
|
|
409
|
+
return sel;
|
|
410
|
+
}
|
|
411
|
+
return null;
|
|
412
|
+
}
|
|
413
|
+
function getMultiAttributeSelector(element) {
|
|
414
|
+
const tag = element.tagName.toLowerCase();
|
|
415
|
+
const attrs = [];
|
|
416
|
+
for (const attr of SEMANTIC_ATTRS) {
|
|
417
|
+
const value = element.getAttribute(attr);
|
|
418
|
+
if (value)
|
|
419
|
+
attrs.push({ attr, value });
|
|
420
|
+
}
|
|
421
|
+
if (attrs.length === 0)
|
|
422
|
+
return null;
|
|
423
|
+
for (const { attr, value } of attrs) {
|
|
424
|
+
const sel = tag + '[' + attr + '="' + CSS.escape(value) + '"]';
|
|
425
|
+
if (isUniqueSelector(sel))
|
|
426
|
+
return sel;
|
|
427
|
+
}
|
|
428
|
+
if (attrs.length >= 2) {
|
|
429
|
+
for (let i = 0; i < attrs.length; i++) {
|
|
430
|
+
for (let j = i + 1; j < attrs.length; j++) {
|
|
431
|
+
const sel = tag +
|
|
432
|
+
'[' +
|
|
433
|
+
attrs[i].attr +
|
|
434
|
+
'="' +
|
|
435
|
+
CSS.escape(attrs[i].value) +
|
|
436
|
+
'"]' +
|
|
437
|
+
'[' +
|
|
438
|
+
attrs[j].attr +
|
|
439
|
+
'="' +
|
|
440
|
+
CSS.escape(attrs[j].value) +
|
|
441
|
+
'"]';
|
|
442
|
+
if (isUniqueSelector(sel))
|
|
443
|
+
return sel;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
return null;
|
|
448
|
+
}
|
|
449
|
+
function getAttributeClassComboSelector(element) {
|
|
450
|
+
const tag = element.tagName.toLowerCase();
|
|
451
|
+
const classes = filterUsefulClasses(element);
|
|
452
|
+
if (classes.length === 0)
|
|
453
|
+
return null;
|
|
454
|
+
classes.sort((a, b) => b.length - a.length);
|
|
455
|
+
const bestClass = classes[0];
|
|
456
|
+
for (const attr of SEMANTIC_ATTRS) {
|
|
457
|
+
const value = element.getAttribute(attr);
|
|
458
|
+
if (value) {
|
|
459
|
+
const sel = tag + '.' + CSS.escape(bestClass) + '[' + attr + '="' + CSS.escape(value) + '"]';
|
|
460
|
+
if (isUniqueSelector(sel))
|
|
461
|
+
return sel;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
return null;
|
|
465
|
+
}
|
|
466
|
+
function getBestClassSelector(element) {
|
|
467
|
+
const classes = filterUsefulClasses(element);
|
|
468
|
+
if (classes.length === 0)
|
|
469
|
+
return null;
|
|
470
|
+
classes.sort((a, b) => b.length - a.length);
|
|
471
|
+
const tag = element.tagName.toLowerCase();
|
|
472
|
+
for (const cls of classes) {
|
|
473
|
+
const sel = tag + '.' + CSS.escape(cls);
|
|
474
|
+
if (isUniqueSelector(sel))
|
|
475
|
+
return sel;
|
|
476
|
+
}
|
|
477
|
+
for (let i = 2; i <= Math.min(3, classes.length); i++) {
|
|
478
|
+
const sel = tag +
|
|
479
|
+
'.' +
|
|
480
|
+
classes
|
|
481
|
+
.slice(0, i)
|
|
482
|
+
.map((c) => CSS.escape(c))
|
|
483
|
+
.join('.');
|
|
484
|
+
if (isUniqueSelector(sel))
|
|
485
|
+
return sel;
|
|
486
|
+
}
|
|
487
|
+
return null;
|
|
488
|
+
}
|
|
489
|
+
function getFeatureSelector(element) {
|
|
490
|
+
if (!element || element === document.body)
|
|
491
|
+
return null;
|
|
492
|
+
if (element.id)
|
|
493
|
+
return '#' + CSS.escape(element.id);
|
|
494
|
+
for (const attr of ['data-testid', 'data-test', 'name', 'role', 'aria-label']) {
|
|
495
|
+
const value = element.getAttribute(attr);
|
|
496
|
+
if (value)
|
|
497
|
+
return element.tagName.toLowerCase() + '[' + attr + '="' + CSS.escape(value) + '"]';
|
|
498
|
+
}
|
|
499
|
+
const classes = filterUsefulClasses(element);
|
|
500
|
+
if (classes.length > 0) {
|
|
501
|
+
classes.sort((a, b) => b.length - a.length);
|
|
502
|
+
const sel = element.tagName.toLowerCase() + '.' + CSS.escape(classes[0]);
|
|
503
|
+
if (isUniqueSelector(sel))
|
|
504
|
+
return sel;
|
|
505
|
+
}
|
|
506
|
+
return null;
|
|
507
|
+
}
|
|
508
|
+
function getBaseSelector(element) {
|
|
509
|
+
let sel = element.tagName.toLowerCase();
|
|
510
|
+
const classes = filterUsefulClasses(element);
|
|
511
|
+
if (classes.length > 0) {
|
|
512
|
+
classes.sort((a, b) => b.length - a.length);
|
|
513
|
+
sel +=
|
|
514
|
+
'.' +
|
|
515
|
+
classes
|
|
516
|
+
.slice(0, 2)
|
|
517
|
+
.map((c) => CSS.escape(c))
|
|
518
|
+
.join('.');
|
|
519
|
+
}
|
|
520
|
+
return sel;
|
|
521
|
+
}
|
|
522
|
+
function makeUniqueWithNth(element, baseSelector) {
|
|
523
|
+
const parent = element.parentElement;
|
|
524
|
+
if (!parent)
|
|
525
|
+
return baseSelector;
|
|
526
|
+
const siblings = Array.from(parent.children);
|
|
527
|
+
const sameTagSiblings = siblings.filter((s) => s.tagName === element.tagName);
|
|
528
|
+
if (sameTagSiblings.length === 1)
|
|
529
|
+
return baseSelector;
|
|
530
|
+
const index = siblings.indexOf(element) + 1;
|
|
531
|
+
return baseSelector + ':nth-child(' + index + ')';
|
|
532
|
+
}
|
|
533
|
+
function getSiblingBasedSelector(element) {
|
|
534
|
+
let prevSibling = element.previousElementSibling;
|
|
535
|
+
let attempts = 0;
|
|
536
|
+
while (prevSibling && attempts < 3) {
|
|
537
|
+
const siblingSelector = getFeatureSelector(prevSibling);
|
|
538
|
+
if (siblingSelector && isUniqueSelector(siblingSelector)) {
|
|
539
|
+
const elementSelector = getBaseSelector(element);
|
|
540
|
+
const combined = siblingSelector + ' + ' + elementSelector;
|
|
541
|
+
if (isUniqueSelector(combined))
|
|
542
|
+
return combined;
|
|
543
|
+
}
|
|
544
|
+
prevSibling = prevSibling.previousElementSibling;
|
|
545
|
+
attempts++;
|
|
546
|
+
}
|
|
547
|
+
return null;
|
|
548
|
+
}
|
|
549
|
+
function buildComposedSelector(element) {
|
|
550
|
+
const selfSelector = getBestClassSelector(element);
|
|
551
|
+
if (selfSelector && isUniqueSelector(selfSelector))
|
|
552
|
+
return selfSelector;
|
|
553
|
+
const parts = [];
|
|
554
|
+
let current = element;
|
|
555
|
+
let depth = 0;
|
|
556
|
+
const maxDepth = 3;
|
|
557
|
+
while (current && current !== document.body && depth < maxDepth) {
|
|
558
|
+
const featureSelector = getFeatureSelector(current);
|
|
559
|
+
if (featureSelector) {
|
|
560
|
+
parts.unshift(featureSelector);
|
|
561
|
+
const elementSelector = depth === 0 ? getBaseSelector(element) : getBaseSelector(current);
|
|
562
|
+
const fullSelector = parts.join(' > ') + (depth > 0 ? '' : ' > ' + elementSelector);
|
|
563
|
+
if (isUniqueSelector(fullSelector))
|
|
564
|
+
return fullSelector;
|
|
565
|
+
}
|
|
566
|
+
else {
|
|
567
|
+
const baseSelector = getBaseSelector(current);
|
|
568
|
+
const selector = makeUniqueWithNth(current, baseSelector);
|
|
569
|
+
parts.unshift(selector);
|
|
570
|
+
const fullSelector = parts.join(' > ');
|
|
571
|
+
if (isUniqueSelector(fullSelector))
|
|
572
|
+
return fullSelector;
|
|
573
|
+
}
|
|
574
|
+
current = current.parentElement;
|
|
575
|
+
depth++;
|
|
576
|
+
}
|
|
577
|
+
return parts.length > 0 ? parts.join(' > ') : null;
|
|
578
|
+
}
|
|
579
|
+
function tryNthChild(element) {
|
|
580
|
+
const baseSelector = getBaseSelector(element);
|
|
581
|
+
const uniqueSelector = makeUniqueWithNth(element, baseSelector);
|
|
582
|
+
try {
|
|
583
|
+
if (document.querySelectorAll(uniqueSelector).length === 1)
|
|
584
|
+
return uniqueSelector;
|
|
585
|
+
}
|
|
586
|
+
catch { }
|
|
587
|
+
return null;
|
|
588
|
+
}
|
|
589
|
+
function buildUniquePath(element) {
|
|
590
|
+
const parts = [];
|
|
591
|
+
let current = element;
|
|
592
|
+
let depth = 0;
|
|
593
|
+
while (current && current !== document.body && depth < 5) {
|
|
594
|
+
const baseSelector = getBaseSelector(current);
|
|
595
|
+
const selector = makeUniqueWithNth(current, baseSelector);
|
|
596
|
+
parts.unshift(selector);
|
|
597
|
+
const fullSelector = parts.join(' > ');
|
|
598
|
+
if (isUniqueSelector(fullSelector))
|
|
599
|
+
return fullSelector;
|
|
600
|
+
current = current.parentElement;
|
|
601
|
+
depth++;
|
|
602
|
+
}
|
|
603
|
+
return parts.length > 0 ? parts.join(' > ') : null;
|
|
604
|
+
}
|
|
605
|
+
function generateXPath(element) {
|
|
606
|
+
if (element.id)
|
|
607
|
+
return '//*[@id="' + element.id + '"]';
|
|
608
|
+
const testId = element.getAttribute('data-testid');
|
|
609
|
+
if (testId)
|
|
610
|
+
return '//*[@data-testid="' + testId + '"]';
|
|
611
|
+
const nameAttr = element.getAttribute('name');
|
|
612
|
+
if (nameAttr)
|
|
613
|
+
return '//' + element.tagName.toLowerCase() + '[@name="' + nameAttr + '"]';
|
|
614
|
+
const parts = [];
|
|
615
|
+
let current = element;
|
|
616
|
+
let depth = 0;
|
|
617
|
+
while (current && depth < 5) {
|
|
618
|
+
if (current.id) {
|
|
619
|
+
parts.unshift('//*[@id="' + current.id + '"]');
|
|
620
|
+
break;
|
|
621
|
+
}
|
|
622
|
+
const testId = current.getAttribute('data-testid');
|
|
623
|
+
if (testId) {
|
|
624
|
+
parts.unshift('//*[@data-testid="' + testId + '"]');
|
|
625
|
+
break;
|
|
626
|
+
}
|
|
627
|
+
const tagName = current.tagName.toLowerCase();
|
|
628
|
+
const parent = current.parentElement;
|
|
629
|
+
if (parent) {
|
|
630
|
+
const siblings = Array.from(parent.children).filter((c) => c.tagName === current.tagName);
|
|
631
|
+
const index = siblings.indexOf(current) + 1;
|
|
632
|
+
parts.unshift(tagName + '[' + index + ']');
|
|
633
|
+
}
|
|
634
|
+
else {
|
|
635
|
+
parts.unshift(tagName);
|
|
636
|
+
}
|
|
637
|
+
current = current.parentElement;
|
|
638
|
+
depth++;
|
|
639
|
+
}
|
|
640
|
+
if (parts.length > 0 && !parts[0].startsWith('//'))
|
|
641
|
+
parts.unshift('//');
|
|
642
|
+
return parts.join('/');
|
|
643
|
+
}
|
|
644
|
+
let cssSelector = null;
|
|
645
|
+
cssSelector = tryIdSelector(el);
|
|
646
|
+
if (!cssSelector)
|
|
647
|
+
cssSelector = getMultiAttributeSelector(el);
|
|
648
|
+
if (!cssSelector)
|
|
649
|
+
cssSelector = getAttributeClassComboSelector(el);
|
|
650
|
+
if (!cssSelector)
|
|
651
|
+
cssSelector = getBestClassSelector(el);
|
|
652
|
+
if (!cssSelector)
|
|
653
|
+
cssSelector = getSiblingBasedSelector(el);
|
|
654
|
+
if (!cssSelector)
|
|
655
|
+
cssSelector = buildComposedSelector(el);
|
|
656
|
+
if (!cssSelector)
|
|
657
|
+
cssSelector = tryNthChild(el);
|
|
658
|
+
if (!cssSelector)
|
|
659
|
+
cssSelector = buildUniquePath(el);
|
|
660
|
+
if (!cssSelector)
|
|
661
|
+
cssSelector = el.tagName.toLowerCase();
|
|
662
|
+
const xpath = generateXPath(el);
|
|
663
|
+
return { cssSelector, xpath };
|
|
664
|
+
})
|
|
665
|
+
.catch(() => null);
|
|
666
|
+
if (selectorData) {
|
|
667
|
+
result[ref] = {
|
|
668
|
+
cssSelector: selectorData.cssSelector,
|
|
669
|
+
xpath: selectorData.xpath,
|
|
670
|
+
};
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
catch { }
|
|
674
|
+
}
|
|
675
|
+
return result;
|
|
676
|
+
}
|
|
314
677
|
/**
|
|
315
678
|
* Get enhanced snapshot with refs and optional filtering
|
|
316
679
|
*/
|
|
@@ -378,7 +741,80 @@ export async function getEnhancedSnapshot(page, options = {}) {
|
|
|
378
741
|
if (options.path || options.attrs) {
|
|
379
742
|
await enrichRefsWithPathsAndAttrs(page, refs, options);
|
|
380
743
|
}
|
|
381
|
-
|
|
744
|
+
let finalTree = enhancedTree;
|
|
745
|
+
if (options.selectors && Object.keys(refs).length > 0) {
|
|
746
|
+
const selectorMap = await buildCompactSelectors(page, refs, options);
|
|
747
|
+
if (selectorMap) {
|
|
748
|
+
finalTree += '\n## Selectors\n' + selectorMap;
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
return { tree: finalTree, refs };
|
|
752
|
+
}
|
|
753
|
+
async function buildCompactSelectors(page, refs, options) {
|
|
754
|
+
const entries = Object.entries(refs);
|
|
755
|
+
const parts = [];
|
|
756
|
+
const includeAll = options?.all ?? false;
|
|
757
|
+
for (const [ref, data] of entries) {
|
|
758
|
+
if (data.role === 'clickable' || data.role === 'focusable')
|
|
759
|
+
continue;
|
|
760
|
+
try {
|
|
761
|
+
let locator;
|
|
762
|
+
if (data.name) {
|
|
763
|
+
locator = page.getByRole(data.role, { name: data.name, exact: true });
|
|
764
|
+
}
|
|
765
|
+
else {
|
|
766
|
+
locator = page.getByRole(data.role);
|
|
767
|
+
}
|
|
768
|
+
if (data.nth !== undefined)
|
|
769
|
+
locator = locator.nth(data.nth);
|
|
770
|
+
if (!includeAll) {
|
|
771
|
+
const isReallyVisible = await locator
|
|
772
|
+
.evaluate((el) => {
|
|
773
|
+
const style = getComputedStyle(el);
|
|
774
|
+
const rect = el.getBoundingClientRect();
|
|
775
|
+
return !(style.display === 'none' ||
|
|
776
|
+
style.visibility === 'hidden' ||
|
|
777
|
+
parseFloat(style.opacity) === 0 ||
|
|
778
|
+
(rect.width === 0 && rect.height === 0) ||
|
|
779
|
+
rect.x + rect.width < 0 ||
|
|
780
|
+
rect.y + rect.height < 0);
|
|
781
|
+
})
|
|
782
|
+
.catch(() => false);
|
|
783
|
+
if (!isReallyVisible)
|
|
784
|
+
continue;
|
|
785
|
+
}
|
|
786
|
+
const attrs = await locator
|
|
787
|
+
.evaluate((el) => {
|
|
788
|
+
const htmlEl = el;
|
|
789
|
+
const r = {};
|
|
790
|
+
if (htmlEl.dataset.testid)
|
|
791
|
+
r['testid'] = `[data-testid="${htmlEl.dataset.testid}"]`;
|
|
792
|
+
if (htmlEl.id && !htmlEl.id.match(/^[:]/))
|
|
793
|
+
r['id'] = '#' + CSS.escape(htmlEl.id);
|
|
794
|
+
const nameAttr = htmlEl.getAttribute('name');
|
|
795
|
+
if (nameAttr)
|
|
796
|
+
r['name'] = `${htmlEl.tagName.toLowerCase()}[name="${nameAttr}"]`;
|
|
797
|
+
return r;
|
|
798
|
+
})
|
|
799
|
+
.catch(() => null);
|
|
800
|
+
if (!attrs)
|
|
801
|
+
continue;
|
|
802
|
+
let bestSelector = '';
|
|
803
|
+
if (attrs.testid)
|
|
804
|
+
bestSelector = attrs.testid;
|
|
805
|
+
else if (attrs.id)
|
|
806
|
+
bestSelector = attrs.id;
|
|
807
|
+
else if (attrs.name)
|
|
808
|
+
bestSelector = attrs.name;
|
|
809
|
+
if (bestSelector) {
|
|
810
|
+
parts.push(`${ref}: ${bestSelector}`);
|
|
811
|
+
}
|
|
812
|
+
}
|
|
813
|
+
catch {
|
|
814
|
+
// skip
|
|
815
|
+
}
|
|
816
|
+
}
|
|
817
|
+
return parts.join(' | ');
|
|
382
818
|
}
|
|
383
819
|
async function enrichRefsWithPathsAndAttrs(page, refs, options) {
|
|
384
820
|
if (Object.keys(refs).length === 0) {
|