userflow-mcp 0.2.1 → 0.3.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/feedback/generator.d.ts.map +1 -1
- package/dist/feedback/generator.js +105 -0
- package/dist/feedback/generator.js.map +1 -1
- package/dist/feedback/html-report.d.ts +8 -0
- package/dist/feedback/html-report.d.ts.map +1 -0
- package/dist/feedback/html-report.js +200 -0
- package/dist/feedback/html-report.js.map +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +241 -15
- package/dist/server.js.map +1 -1
- package/dist/session/session-manager.d.ts +18 -1
- package/dist/session/session-manager.d.ts.map +1 -1
- package/dist/session/session-manager.js +61 -5
- package/dist/session/session-manager.js.map +1 -1
- package/dist/session/types.d.ts +4 -0
- package/dist/session/types.d.ts.map +1 -1
- package/dist/types.d.ts +159 -36
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/accessibility.d.ts +43 -0
- package/dist/utils/accessibility.d.ts.map +1 -0
- package/dist/utils/accessibility.js +72 -0
- package/dist/utils/accessibility.js.map +1 -0
- package/dist/utils/actions.d.ts +2 -0
- package/dist/utils/actions.d.ts.map +1 -1
- package/dist/utils/actions.js +47 -13
- package/dist/utils/actions.js.map +1 -1
- package/dist/utils/console-monitor.d.ts +44 -0
- package/dist/utils/console-monitor.d.ts.map +1 -0
- package/dist/utils/console-monitor.js +90 -0
- package/dist/utils/console-monitor.js.map +1 -0
- package/dist/utils/device-profiles.d.ts +43 -0
- package/dist/utils/device-profiles.d.ts.map +1 -0
- package/dist/utils/device-profiles.js +132 -0
- package/dist/utils/device-profiles.js.map +1 -0
- package/dist/utils/network-monitor.d.ts +121 -0
- package/dist/utils/network-monitor.d.ts.map +1 -0
- package/dist/utils/network-monitor.js +238 -0
- package/dist/utils/network-monitor.js.map +1 -0
- package/dist/utils/page-snapshot.d.ts +12 -5
- package/dist/utils/page-snapshot.d.ts.map +1 -1
- package/dist/utils/page-snapshot.js +43 -2
- package/dist/utils/page-snapshot.js.map +1 -1
- package/dist/utils/performance.d.ts +43 -0
- package/dist/utils/performance.d.ts.map +1 -0
- package/dist/utils/performance.js +108 -0
- package/dist/utils/performance.js.map +1 -0
- package/dist/utils/screenshot-diff.d.ts +36 -0
- package/dist/utils/screenshot-diff.d.ts.map +1 -0
- package/dist/utils/screenshot-diff.js +62 -0
- package/dist/utils/screenshot-diff.js.map +1 -0
- package/dist/utils/selector-engine.d.ts +53 -0
- package/dist/utils/selector-engine.d.ts.map +1 -0
- package/dist/utils/selector-engine.js +180 -0
- package/dist/utils/selector-engine.js.map +1 -0
- package/dist/utils/storage-inspector.d.ts +47 -0
- package/dist/utils/storage-inspector.d.ts.map +1 -0
- package/dist/utils/storage-inspector.js +71 -0
- package/dist/utils/storage-inspector.js.map +1 -0
- package/package.json +5 -1
|
@@ -1,11 +1,18 @@
|
|
|
1
1
|
import type { Page } from "puppeteer-core";
|
|
2
2
|
import type { PageSnapshot } from "../types.js";
|
|
3
|
+
import type { NetworkMonitor } from "./network-monitor.js";
|
|
4
|
+
import type { ConsoleMonitor } from "./console-monitor.js";
|
|
5
|
+
export interface SnapshotOptions {
|
|
6
|
+
readonly fullPage?: boolean;
|
|
7
|
+
readonly networkMonitor?: NetworkMonitor;
|
|
8
|
+
readonly consoleMonitor?: ConsoleMonitor;
|
|
9
|
+
/** Skip heavy audits (accessibility, storage) for faster snapshots during steps. */
|
|
10
|
+
readonly lightweight?: boolean;
|
|
11
|
+
}
|
|
3
12
|
/**
|
|
4
13
|
* Extract a full snapshot of the current page state.
|
|
5
|
-
* Gathers all interactive elements, text content, headings, error messages,
|
|
6
|
-
*
|
|
14
|
+
* Gathers all interactive elements, text content, headings, error messages,
|
|
15
|
+
* plus optional v0.3 data: performance metrics, accessibility, network, console, storage.
|
|
7
16
|
*/
|
|
8
|
-
export declare function extractPageSnapshot(page: Page, options?:
|
|
9
|
-
readonly fullPage?: boolean;
|
|
10
|
-
}): Promise<PageSnapshot>;
|
|
17
|
+
export declare function extractPageSnapshot(page: Page, options?: SnapshotOptions): Promise<PageSnapshot>;
|
|
11
18
|
//# sourceMappingURL=page-snapshot.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"page-snapshot.d.ts","sourceRoot":"","sources":["../../src/utils/page-snapshot.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,KAAK,EAAE,YAAY,EAAe,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"page-snapshot.d.ts","sourceRoot":"","sources":["../../src/utils/page-snapshot.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,KAAK,EAAE,YAAY,EAAe,MAAM,aAAa,CAAC;AAC7D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAC3D,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAK3D,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAC;IAC5B,QAAQ,CAAC,cAAc,CAAC,EAAE,cAAc,CAAC;IACzC,QAAQ,CAAC,cAAc,CAAC,EAAE,cAAc,CAAC;IACzC,oFAAoF;IACpF,QAAQ,CAAC,WAAW,CAAC,EAAE,OAAO,CAAC;CAChC;AAED;;;;GAIG;AACH,wBAAsB,mBAAmB,CACvC,IAAI,EAAE,IAAI,EACV,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAAC,YAAY,CAAC,CAqJvB"}
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { collectPerformanceMetrics } from "./performance.js";
|
|
2
|
+
import { runAccessibilityAudit } from "./accessibility.js";
|
|
3
|
+
import { inspectStorage } from "./storage-inspector.js";
|
|
1
4
|
/**
|
|
2
5
|
* Extract a full snapshot of the current page state.
|
|
3
|
-
* Gathers all interactive elements, text content, headings, error messages,
|
|
4
|
-
*
|
|
6
|
+
* Gathers all interactive elements, text content, headings, error messages,
|
|
7
|
+
* plus optional v0.3 data: performance metrics, accessibility, network, console, storage.
|
|
5
8
|
*/
|
|
6
9
|
export async function extractPageSnapshot(page, options) {
|
|
7
10
|
const startTime = Date.now();
|
|
@@ -84,6 +87,39 @@ export async function extractPageSnapshot(page, options) {
|
|
|
84
87
|
});
|
|
85
88
|
const screenshot = Buffer.from(screenshotBuffer).toString("base64");
|
|
86
89
|
const loadTimeMs = Date.now() - startTime;
|
|
90
|
+
// ── v0.3 enrichment: collect performance, network, console, storage, a11y ──
|
|
91
|
+
// Performance metrics (always collected — lightweight)
|
|
92
|
+
let performance = undefined;
|
|
93
|
+
try {
|
|
94
|
+
performance = await collectPerformanceMetrics(page);
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
// Performance collection may fail on some pages — graceful degradation
|
|
98
|
+
}
|
|
99
|
+
// Network summary (from monitor if available)
|
|
100
|
+
const network = options?.networkMonitor?.getSummary() ?? undefined;
|
|
101
|
+
// Console summary (from monitor if available)
|
|
102
|
+
const console = options?.consoleMonitor?.getSummary() ?? undefined;
|
|
103
|
+
// Accessibility audit (skip in lightweight mode to keep step speed)
|
|
104
|
+
let accessibility = undefined;
|
|
105
|
+
if (!options?.lightweight) {
|
|
106
|
+
try {
|
|
107
|
+
accessibility = await runAccessibilityAudit(page);
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
// axe-core injection may fail on some pages
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
// Storage inspection (skip in lightweight mode)
|
|
114
|
+
let storage = undefined;
|
|
115
|
+
if (!options?.lightweight) {
|
|
116
|
+
try {
|
|
117
|
+
storage = await inspectStorage(page);
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
// Storage inspection may fail on cross-origin pages
|
|
121
|
+
}
|
|
122
|
+
}
|
|
87
123
|
return {
|
|
88
124
|
url,
|
|
89
125
|
title,
|
|
@@ -97,6 +133,11 @@ export async function extractPageSnapshot(page, options) {
|
|
|
97
133
|
buttons: pageData.buttons,
|
|
98
134
|
errorMessages: pageData.errorMessages,
|
|
99
135
|
loadTimeMs,
|
|
136
|
+
performance,
|
|
137
|
+
network,
|
|
138
|
+
console,
|
|
139
|
+
accessibility,
|
|
140
|
+
storage,
|
|
100
141
|
};
|
|
101
142
|
}
|
|
102
143
|
//# sourceMappingURL=page-snapshot.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"page-snapshot.js","sourceRoot":"","sources":["../../src/utils/page-snapshot.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"page-snapshot.js","sourceRoot":"","sources":["../../src/utils/page-snapshot.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { Page } from "puppeteer-core";
|
|
2
|
+
export interface PerformanceMetrics {
|
|
3
|
+
/** Largest Contentful Paint (ms) */
|
|
4
|
+
lcp: number | null;
|
|
5
|
+
/** Cumulative Layout Shift (unitless score) */
|
|
6
|
+
cls: number | null;
|
|
7
|
+
/** Interaction to Next Paint (ms) */
|
|
8
|
+
inp: number | null;
|
|
9
|
+
/** First Contentful Paint (ms) */
|
|
10
|
+
fcp: number | null;
|
|
11
|
+
/** Time to First Byte (ms) */
|
|
12
|
+
ttfb: number | null;
|
|
13
|
+
/** DOMContentLoaded relative to navigationStart (ms) */
|
|
14
|
+
domContentLoaded: number | null;
|
|
15
|
+
/** domComplete relative to navigationStart (ms) */
|
|
16
|
+
domComplete: number | null;
|
|
17
|
+
/** Total number of resources loaded */
|
|
18
|
+
resourceCount: number;
|
|
19
|
+
/** Total transfer size of all resources (bytes) */
|
|
20
|
+
totalResourceSize: number;
|
|
21
|
+
/** LCP quality rating per Google's thresholds */
|
|
22
|
+
lcpRating: "good" | "needs-improvement" | "poor" | null;
|
|
23
|
+
/** CLS quality rating per Google's thresholds */
|
|
24
|
+
clsRating: "good" | "needs-improvement" | "poor" | null;
|
|
25
|
+
/** INP quality rating per Google's thresholds */
|
|
26
|
+
inpRating: "good" | "needs-improvement" | "poor" | null;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Inject PerformanceObservers into every new document before navigation.
|
|
30
|
+
*
|
|
31
|
+
* Must be called before `page.goto()`. Stores collected values on
|
|
32
|
+
* `window.__userflow_perf` so they can be read by `collectPerformanceMetrics`.
|
|
33
|
+
*/
|
|
34
|
+
export declare function injectPerformanceObservers(page: Page): Promise<void>;
|
|
35
|
+
/**
|
|
36
|
+
* Read all performance metrics from the page after it has loaded.
|
|
37
|
+
*
|
|
38
|
+
* Combines values captured by `injectPerformanceObservers` with
|
|
39
|
+
* Navigation Timing and Resource Timing data from the browser's
|
|
40
|
+
* Performance API. Missing or unsupported metrics are returned as `null`.
|
|
41
|
+
*/
|
|
42
|
+
export declare function collectPerformanceMetrics(page: Page): Promise<PerformanceMetrics>;
|
|
43
|
+
//# sourceMappingURL=performance.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"performance.d.ts","sourceRoot":"","sources":["../../src/utils/performance.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,gBAAgB,CAAC;AAI3C,MAAM,WAAW,kBAAkB;IACjC,oCAAoC;IACpC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,+CAA+C;IAC/C,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,qCAAqC;IACrC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,kCAAkC;IAClC,GAAG,EAAE,MAAM,GAAG,IAAI,CAAC;IACnB,8BAA8B;IAC9B,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,wDAAwD;IACxD,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,mDAAmD;IACnD,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,uCAAuC;IACvC,aAAa,EAAE,MAAM,CAAC;IACtB,mDAAmD;IACnD,iBAAiB,EAAE,MAAM,CAAC;IAC1B,iDAAiD;IACjD,SAAS,EAAE,MAAM,GAAG,mBAAmB,GAAG,MAAM,GAAG,IAAI,CAAC;IACxD,iDAAiD;IACjD,SAAS,EAAE,MAAM,GAAG,mBAAmB,GAAG,MAAM,GAAG,IAAI,CAAC;IACxD,iDAAiD;IACjD,SAAS,EAAE,MAAM,GAAG,mBAAmB,GAAG,MAAM,GAAG,IAAI,CAAC;CACzD;AA0BD;;;;;GAKG;AACH,wBAAsB,0BAA0B,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CA2B1E;AAID;;;;;;GAMG;AACH,wBAAsB,yBAAyB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAmDvF"}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
function rateLcp(ms) {
|
|
2
|
+
if (ms <= 2500)
|
|
3
|
+
return "good";
|
|
4
|
+
if (ms <= 4000)
|
|
5
|
+
return "needs-improvement";
|
|
6
|
+
return "poor";
|
|
7
|
+
}
|
|
8
|
+
function rateCls(score) {
|
|
9
|
+
if (score <= 0.1)
|
|
10
|
+
return "good";
|
|
11
|
+
if (score <= 0.25)
|
|
12
|
+
return "needs-improvement";
|
|
13
|
+
return "poor";
|
|
14
|
+
}
|
|
15
|
+
function rateInp(ms) {
|
|
16
|
+
if (ms <= 200)
|
|
17
|
+
return "good";
|
|
18
|
+
if (ms <= 500)
|
|
19
|
+
return "needs-improvement";
|
|
20
|
+
return "poor";
|
|
21
|
+
}
|
|
22
|
+
// ── Observer injection ─────────────────────────────────────────
|
|
23
|
+
/**
|
|
24
|
+
* Inject PerformanceObservers into every new document before navigation.
|
|
25
|
+
*
|
|
26
|
+
* Must be called before `page.goto()`. Stores collected values on
|
|
27
|
+
* `window.__userflow_perf` so they can be read by `collectPerformanceMetrics`.
|
|
28
|
+
*/
|
|
29
|
+
export async function injectPerformanceObservers(page) {
|
|
30
|
+
await page.evaluateOnNewDocument(() => {
|
|
31
|
+
const store = { lcp: null, cls: null, inp: null };
|
|
32
|
+
// @ts-expect-error — injected global not in Window typings
|
|
33
|
+
window.__userflow_perf = store;
|
|
34
|
+
const observe = (type, opts, cb) => {
|
|
35
|
+
try {
|
|
36
|
+
new PerformanceObserver((l) => l.getEntries().forEach(cb)).observe({ type, buffered: true, ...opts });
|
|
37
|
+
}
|
|
38
|
+
catch { /* entry type unsupported in this browser */ }
|
|
39
|
+
};
|
|
40
|
+
// LCP — last entry wins (browser re-fires until first user interaction)
|
|
41
|
+
observe("largest-contentful-paint", {}, (e) => { store.lcp = e.startTime; });
|
|
42
|
+
// CLS — accumulate shifts not preceded by user input
|
|
43
|
+
observe("layout-shift", {}, (e) => {
|
|
44
|
+
const s = e;
|
|
45
|
+
if (!s.hadRecentInput)
|
|
46
|
+
store.cls = (store.cls ?? 0) + s.value;
|
|
47
|
+
});
|
|
48
|
+
// INP — worst (max) event duration; durationThreshold cast needed for older TS lib defs
|
|
49
|
+
observe("event", { durationThreshold: 16 }, (e) => {
|
|
50
|
+
const d = e.duration;
|
|
51
|
+
if (store.inp === null || d > store.inp)
|
|
52
|
+
store.inp = d;
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
// ── Metric collection ──────────────────────────────────────────
|
|
57
|
+
/**
|
|
58
|
+
* Read all performance metrics from the page after it has loaded.
|
|
59
|
+
*
|
|
60
|
+
* Combines values captured by `injectPerformanceObservers` with
|
|
61
|
+
* Navigation Timing and Resource Timing data from the browser's
|
|
62
|
+
* Performance API. Missing or unsupported metrics are returned as `null`.
|
|
63
|
+
*/
|
|
64
|
+
export async function collectPerformanceMetrics(page) {
|
|
65
|
+
const raw = await page.evaluate(() => {
|
|
66
|
+
// Observed vitals written by the injected PerformanceObservers
|
|
67
|
+
const vitals = window
|
|
68
|
+
.__userflow_perf ?? { lcp: null, cls: null, inp: null };
|
|
69
|
+
// Navigation Timing (Level 2)
|
|
70
|
+
const [navEntry] = performance.getEntriesByType("navigation");
|
|
71
|
+
const ttfb = navEntry ? navEntry.responseStart - navEntry.requestStart : null;
|
|
72
|
+
const domContentLoaded = navEntry ? navEntry.domContentLoadedEventEnd - navEntry.startTime : null;
|
|
73
|
+
const domComplete = navEntry ? navEntry.domComplete - navEntry.startTime : null;
|
|
74
|
+
// FCP via paint entries
|
|
75
|
+
const fcpEntry = performance.getEntriesByName("first-contentful-paint")[0];
|
|
76
|
+
const fcp = fcpEntry ? fcpEntry.startTime : null;
|
|
77
|
+
// Resource summary
|
|
78
|
+
const resources = performance.getEntriesByType("resource");
|
|
79
|
+
const resourceCount = resources.length;
|
|
80
|
+
const totalResourceSize = resources.reduce((sum, r) => sum + (r.transferSize ?? 0), 0);
|
|
81
|
+
return {
|
|
82
|
+
lcp: vitals.lcp,
|
|
83
|
+
cls: vitals.cls,
|
|
84
|
+
inp: vitals.inp,
|
|
85
|
+
fcp,
|
|
86
|
+
ttfb,
|
|
87
|
+
domContentLoaded,
|
|
88
|
+
domComplete,
|
|
89
|
+
resourceCount,
|
|
90
|
+
totalResourceSize,
|
|
91
|
+
};
|
|
92
|
+
});
|
|
93
|
+
return {
|
|
94
|
+
lcp: raw.lcp,
|
|
95
|
+
cls: raw.cls,
|
|
96
|
+
inp: raw.inp,
|
|
97
|
+
fcp: raw.fcp,
|
|
98
|
+
ttfb: raw.ttfb,
|
|
99
|
+
domContentLoaded: raw.domContentLoaded,
|
|
100
|
+
domComplete: raw.domComplete,
|
|
101
|
+
resourceCount: raw.resourceCount,
|
|
102
|
+
totalResourceSize: raw.totalResourceSize,
|
|
103
|
+
lcpRating: raw.lcp !== null ? rateLcp(raw.lcp) : null,
|
|
104
|
+
clsRating: raw.cls !== null ? rateCls(raw.cls) : null,
|
|
105
|
+
inpRating: raw.inp !== null ? rateInp(raw.inp) : null,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
//# sourceMappingURL=performance.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"performance.js","sourceRoot":"","sources":["../../src/utils/performance.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Visual screenshot comparison using pixelmatch and pngjs.
|
|
3
|
+
*
|
|
4
|
+
* Accepts two base64-encoded PNG screenshots, optionally pads the smaller
|
|
5
|
+
* one to match dimensions, computes a pixel-level diff, and returns match
|
|
6
|
+
* statistics together with a base64-encoded diff-overlay image.
|
|
7
|
+
*/
|
|
8
|
+
/** Result of a pixel-level screenshot comparison. */
|
|
9
|
+
export interface ScreenshotDiff {
|
|
10
|
+
/** Percentage of matching pixels (0 = no match, 100 = identical). */
|
|
11
|
+
matchPercentage: number;
|
|
12
|
+
/** Number of pixels that differ beyond the threshold. */
|
|
13
|
+
diffPixels: number;
|
|
14
|
+
/** Total number of pixels in the (possibly padded) canvas. */
|
|
15
|
+
totalPixels: number;
|
|
16
|
+
dimensions: {
|
|
17
|
+
width: number;
|
|
18
|
+
height: number;
|
|
19
|
+
};
|
|
20
|
+
/** Base64-encoded PNG highlighting differing pixels in red. */
|
|
21
|
+
diffImage: string;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Compares two base64-encoded PNG screenshots and produces a visual diff.
|
|
25
|
+
*
|
|
26
|
+
* When the images have different dimensions the smaller one is padded with
|
|
27
|
+
* transparent pixels so both canvases share the same bounding box before
|
|
28
|
+
* comparison.
|
|
29
|
+
*
|
|
30
|
+
* @param screenshot1 - Base64-encoded PNG (first / "before" image).
|
|
31
|
+
* @param screenshot2 - Base64-encoded PNG (second / "after" image).
|
|
32
|
+
* @param threshold - Per-channel colour tolerance in [0, 1]. Defaults to 0.1.
|
|
33
|
+
* @returns Diff statistics and a base64 diff-overlay image.
|
|
34
|
+
*/
|
|
35
|
+
export declare function compareScreenshots(screenshot1: string, screenshot2: string, threshold?: number): Promise<ScreenshotDiff>;
|
|
36
|
+
//# sourceMappingURL=screenshot-diff.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"screenshot-diff.d.ts","sourceRoot":"","sources":["../../src/utils/screenshot-diff.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAKH,qDAAqD;AACrD,MAAM,WAAW,cAAc;IAC7B,qEAAqE;IACrE,eAAe,EAAE,MAAM,CAAC;IACxB,yDAAyD;IACzD,UAAU,EAAE,MAAM,CAAC;IACnB,8DAA8D;IAC9D,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC9C,+DAA+D;IAC/D,SAAS,EAAE,MAAM,CAAC;CACnB;AAwBD;;;;;;;;;;;GAWG;AACH,wBAAsB,kBAAkB,CACtC,WAAW,EAAE,MAAM,EACnB,WAAW,EAAE,MAAM,EACnB,SAAS,SAAM,GACd,OAAO,CAAC,cAAc,CAAC,CAoCzB"}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Visual screenshot comparison using pixelmatch and pngjs.
|
|
3
|
+
*
|
|
4
|
+
* Accepts two base64-encoded PNG screenshots, optionally pads the smaller
|
|
5
|
+
* one to match dimensions, computes a pixel-level diff, and returns match
|
|
6
|
+
* statistics together with a base64-encoded diff-overlay image.
|
|
7
|
+
*/
|
|
8
|
+
import pixelmatch from "pixelmatch";
|
|
9
|
+
import { PNG } from "pngjs";
|
|
10
|
+
/** Decodes a base64 PNG string into a `pngjs` `PNG` object. */
|
|
11
|
+
function decodePng(base64) {
|
|
12
|
+
const buffer = Buffer.from(base64, "base64");
|
|
13
|
+
return PNG.sync.read(buffer);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Pads a PNG canvas to the target dimensions by copying pixels into a new,
|
|
17
|
+
* zero-initialised (transparent black) buffer. Returns the original PNG
|
|
18
|
+
* unchanged when it already matches the target size.
|
|
19
|
+
*/
|
|
20
|
+
function padPng(png, width, height) {
|
|
21
|
+
if (png.width === width && png.height === height)
|
|
22
|
+
return png;
|
|
23
|
+
const padded = new PNG({ width, height });
|
|
24
|
+
// Zero-fill guarantees transparent black padding pixels.
|
|
25
|
+
padded.data.fill(0);
|
|
26
|
+
PNG.bitblt(png, padded, 0, 0, Math.min(png.width, width), Math.min(png.height, height), 0, 0);
|
|
27
|
+
return padded;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Compares two base64-encoded PNG screenshots and produces a visual diff.
|
|
31
|
+
*
|
|
32
|
+
* When the images have different dimensions the smaller one is padded with
|
|
33
|
+
* transparent pixels so both canvases share the same bounding box before
|
|
34
|
+
* comparison.
|
|
35
|
+
*
|
|
36
|
+
* @param screenshot1 - Base64-encoded PNG (first / "before" image).
|
|
37
|
+
* @param screenshot2 - Base64-encoded PNG (second / "after" image).
|
|
38
|
+
* @param threshold - Per-channel colour tolerance in [0, 1]. Defaults to 0.1.
|
|
39
|
+
* @returns Diff statistics and a base64 diff-overlay image.
|
|
40
|
+
*/
|
|
41
|
+
export async function compareScreenshots(screenshot1, screenshot2, threshold = 0.1) {
|
|
42
|
+
const png1 = decodePng(screenshot1);
|
|
43
|
+
const png2 = decodePng(screenshot2);
|
|
44
|
+
// Compute the bounding box that encloses both images.
|
|
45
|
+
const width = Math.max(png1.width, png2.width);
|
|
46
|
+
const height = Math.max(png1.height, png2.height);
|
|
47
|
+
const padded1 = padPng(png1, width, height);
|
|
48
|
+
const padded2 = padPng(png2, width, height);
|
|
49
|
+
const diff = new PNG({ width, height });
|
|
50
|
+
const totalPixels = width * height;
|
|
51
|
+
const diffPixels = pixelmatch(padded1.data, padded2.data, diff.data, width, height, { threshold, includeAA: false });
|
|
52
|
+
const matchPercentage = parseFloat((((totalPixels - diffPixels) / totalPixels) * 100).toFixed(2));
|
|
53
|
+
const diffImage = PNG.sync.write(diff).toString("base64");
|
|
54
|
+
return {
|
|
55
|
+
matchPercentage,
|
|
56
|
+
diffPixels,
|
|
57
|
+
totalPixels,
|
|
58
|
+
dimensions: { width, height },
|
|
59
|
+
diffImage,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
//# sourceMappingURL=screenshot-diff.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"screenshot-diff.js","sourceRoot":"","sources":["../../src/utils/screenshot-diff.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,UAAU,MAAM,YAAY,CAAC;AACpC,OAAO,EAAE,GAAG,EAAE,MAAM,OAAO,CAAC;AAe5B,+DAA+D;AAC/D,SAAS,SAAS,CAAC,MAAc;IAC/B,MAAM,MAAM,GAAG,MAAM,CAAC,IAAI,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IAC7C,OAAO,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;AAC/B,CAAC;AAED;;;;GAIG;AACH,SAAS,MAAM,CAAC,GAAQ,EAAE,KAAa,EAAE,MAAc;IACrD,IAAI,GAAG,CAAC,KAAK,KAAK,KAAK,IAAI,GAAG,CAAC,MAAM,KAAK,MAAM;QAAE,OAAO,GAAG,CAAC;IAE7D,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;IAC1C,yDAAyD;IACzD,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAEpB,GAAG,CAAC,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;IAC9F,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,CAAC,KAAK,UAAU,kBAAkB,CACtC,WAAmB,EACnB,WAAmB,EACnB,SAAS,GAAG,GAAG;IAEf,MAAM,IAAI,GAAG,SAAS,CAAC,WAAW,CAAC,CAAC;IACpC,MAAM,IAAI,GAAG,SAAS,CAAC,WAAW,CAAC,CAAC;IAEpC,sDAAsD;IACtD,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC;IAC/C,MAAM,MAAM,GAAG,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;IAElD,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;IAC5C,MAAM,OAAO,GAAG,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;IAE5C,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC;IACxC,MAAM,WAAW,GAAG,KAAK,GAAG,MAAM,CAAC;IAEnC,MAAM,UAAU,GAAG,UAAU,CAC3B,OAAO,CAAC,IAAI,EACZ,OAAO,CAAC,IAAI,EACZ,IAAI,CAAC,IAAI,EACT,KAAK,EACL,MAAM,EACN,EAAE,SAAS,EAAE,SAAS,EAAE,KAAK,EAAE,CAChC,CAAC;IAEF,MAAM,eAAe,GAAG,UAAU,CAChC,CAAC,CAAC,CAAC,WAAW,GAAG,UAAU,CAAC,GAAG,WAAW,CAAC,GAAG,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAC9D,CAAC;IAEF,MAAM,SAAS,GAAG,GAAG,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;IAE1D,OAAO;QACL,eAAe;QACf,UAAU;QACV,WAAW;QACX,UAAU,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE;QAC7B,SAAS;KACV,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Smart Selector Engine for Puppeteer
|
|
3
|
+
*
|
|
4
|
+
* Generates reliable, fallback-capable selectors that survive styled-components
|
|
5
|
+
* and dynamic class names. Each element gets multiple selector strategies tried
|
|
6
|
+
* in priority order, from most stable to least stable.
|
|
7
|
+
*/
|
|
8
|
+
import type { Page, ElementHandle } from "puppeteer-core";
|
|
9
|
+
export interface SmartSelector {
|
|
10
|
+
/** The best selector to try first. */
|
|
11
|
+
primary: string;
|
|
12
|
+
/** Ordered list of alternatives to try if primary fails. */
|
|
13
|
+
fallbacks: string[];
|
|
14
|
+
/** Human-readable description of what the element is. */
|
|
15
|
+
description: string;
|
|
16
|
+
/** Strategy used to derive the primary selector. */
|
|
17
|
+
strategy: "testid" | "id" | "aria" | "role-text" | "input-attr" | "link-href" | "text" | "css";
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Generates multiple selector strategies for an element.
|
|
21
|
+
* Designed to run inside `page.evaluate()` — has no Node.js dependencies.
|
|
22
|
+
*
|
|
23
|
+
* Priority order (most → least stable):
|
|
24
|
+
* 1. data-testid
|
|
25
|
+
* 2. Unique id
|
|
26
|
+
* 3. aria-label / aria-labelledby
|
|
27
|
+
* 4. role + visible text
|
|
28
|
+
* 5. input type + name or placeholder
|
|
29
|
+
* 6. anchor href
|
|
30
|
+
* 7. Puppeteer text pseudo-selector
|
|
31
|
+
* 8. Minimal CSS path (fallback)
|
|
32
|
+
*/
|
|
33
|
+
export declare function generateSmartSelectors(element: Element): SmartSelector;
|
|
34
|
+
/**
|
|
35
|
+
* Tries to find an element on the page using multiple selector strategies.
|
|
36
|
+
* Returns the first `ElementHandle` that successfully resolves, or `null`.
|
|
37
|
+
*
|
|
38
|
+
* @param page - Puppeteer Page instance.
|
|
39
|
+
* @param target - Primary selector string or SmartSelector.primary.
|
|
40
|
+
* @param fallbacks - Additional selectors to try if primary fails.
|
|
41
|
+
* @param timeout - Per-selector wait timeout in ms (default: 3000).
|
|
42
|
+
*/
|
|
43
|
+
export declare function resolveSelector(page: Page, target: string, fallbacks?: string[], timeout?: number): Promise<ElementHandle | null>;
|
|
44
|
+
/**
|
|
45
|
+
* Takes a basic CSS selector (e.g. from a snapshot), injects
|
|
46
|
+
* `generateSmartSelectors` into the page, and returns a richer SmartSelector
|
|
47
|
+
* with stable alternatives.
|
|
48
|
+
*
|
|
49
|
+
* @param page - Puppeteer Page instance.
|
|
50
|
+
* @param cssSelector - Existing (possibly fragile) CSS selector.
|
|
51
|
+
*/
|
|
52
|
+
export declare function enhanceElementSelectors(page: Page, cssSelector: string): Promise<SmartSelector>;
|
|
53
|
+
//# sourceMappingURL=selector-engine.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selector-engine.d.ts","sourceRoot":"","sources":["../../src/utils/selector-engine.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,IAAI,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAM1D,MAAM,WAAW,aAAa;IAC5B,sCAAsC;IACtC,OAAO,EAAE,MAAM,CAAC;IAChB,4DAA4D;IAC5D,SAAS,EAAE,MAAM,EAAE,CAAC;IACpB,yDAAyD;IACzD,WAAW,EAAE,MAAM,CAAC;IACpB,oDAAoD;IACpD,QAAQ,EAAE,QAAQ,GAAG,IAAI,GAAG,MAAM,GAAG,WAAW,GAAG,YAAY,GAAG,WAAW,GAAG,MAAM,GAAG,KAAK,CAAC;CAChG;AAMD;;;;;;;;;;;;;GAaG;AACH,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,GAAG,aAAa,CA2EtE;AAkDD;;;;;;;;GAQG;AACH,wBAAsB,eAAe,CACnC,IAAI,EAAE,IAAI,EACV,MAAM,EAAE,MAAM,EACd,SAAS,GAAE,MAAM,EAAO,EACxB,OAAO,SAAQ,GACd,OAAO,CAAC,aAAa,GAAG,IAAI,CAAC,CAa/B;AAED;;;;;;;GAOG;AACH,wBAAsB,uBAAuB,CAC3C,IAAI,EAAE,IAAI,EACV,WAAW,EAAE,MAAM,GAClB,OAAO,CAAC,aAAa,CAAC,CA0BxB"}
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Smart Selector Engine for Puppeteer
|
|
3
|
+
*
|
|
4
|
+
* Generates reliable, fallback-capable selectors that survive styled-components
|
|
5
|
+
* and dynamic class names. Each element gets multiple selector strategies tried
|
|
6
|
+
* in priority order, from most stable to least stable.
|
|
7
|
+
*/
|
|
8
|
+
// ---------------------------------------------------------------------------
|
|
9
|
+
// Browser-context helper (runs inside page.evaluate)
|
|
10
|
+
// ---------------------------------------------------------------------------
|
|
11
|
+
/**
|
|
12
|
+
* Generates multiple selector strategies for an element.
|
|
13
|
+
* Designed to run inside `page.evaluate()` — has no Node.js dependencies.
|
|
14
|
+
*
|
|
15
|
+
* Priority order (most → least stable):
|
|
16
|
+
* 1. data-testid
|
|
17
|
+
* 2. Unique id
|
|
18
|
+
* 3. aria-label / aria-labelledby
|
|
19
|
+
* 4. role + visible text
|
|
20
|
+
* 5. input type + name or placeholder
|
|
21
|
+
* 6. anchor href
|
|
22
|
+
* 7. Puppeteer text pseudo-selector
|
|
23
|
+
* 8. Minimal CSS path (fallback)
|
|
24
|
+
*/
|
|
25
|
+
export function generateSmartSelectors(element) {
|
|
26
|
+
const tag = element.tagName.toLowerCase();
|
|
27
|
+
const candidates = [];
|
|
28
|
+
// 1. data-testid (most stable — explicit test hook)
|
|
29
|
+
const testid = element.getAttribute("data-testid") ?? element.getAttribute("data-test-id") ?? element.getAttribute("data-cy");
|
|
30
|
+
if (testid) {
|
|
31
|
+
const attr = element.hasAttribute("data-testid") ? "data-testid" : element.hasAttribute("data-test-id") ? "data-test-id" : "data-cy";
|
|
32
|
+
candidates.push({ selector: `[${attr}="${CSS.escape(testid)}"]`, strategy: "testid" });
|
|
33
|
+
}
|
|
34
|
+
// 2. Unique id attribute
|
|
35
|
+
const id = element.getAttribute("id");
|
|
36
|
+
if (id && document.querySelectorAll(`#${CSS.escape(id)}`).length === 1) {
|
|
37
|
+
candidates.push({ selector: `#${CSS.escape(id)}`, strategy: "id" });
|
|
38
|
+
}
|
|
39
|
+
// 3. aria-label / aria-labelledby
|
|
40
|
+
const ariaLabel = element.getAttribute("aria-label");
|
|
41
|
+
if (ariaLabel) {
|
|
42
|
+
candidates.push({ selector: `[aria-label="${CSS.escape(ariaLabel)}"]`, strategy: "aria" });
|
|
43
|
+
}
|
|
44
|
+
const labelledBy = element.getAttribute("aria-labelledby");
|
|
45
|
+
if (labelledBy) {
|
|
46
|
+
candidates.push({ selector: `[aria-labelledby="${CSS.escape(labelledBy)}"]`, strategy: "aria" });
|
|
47
|
+
}
|
|
48
|
+
// 4. Role + visible text content (for buttons, links, headings, etc.)
|
|
49
|
+
const role = element.getAttribute("role") ?? tag;
|
|
50
|
+
const visibleText = (element.textContent ?? "").trim().slice(0, 80);
|
|
51
|
+
if (visibleText && ["button", "a", "h1", "h2", "h3", "h4", "h5", "h6", "label", "li"].includes(tag)) {
|
|
52
|
+
candidates.push({ selector: `::-p-aria(${visibleText})`, strategy: "role-text" });
|
|
53
|
+
}
|
|
54
|
+
// 5. Input type + name or placeholder
|
|
55
|
+
if (tag === "input" || tag === "textarea" || tag === "select") {
|
|
56
|
+
const name = element.getAttribute("name");
|
|
57
|
+
const placeholder = element.getAttribute("placeholder");
|
|
58
|
+
const type = element.getAttribute("type") ?? "text";
|
|
59
|
+
if (name) {
|
|
60
|
+
candidates.push({ selector: `${tag}[name="${CSS.escape(name)}"]`, strategy: "input-attr" });
|
|
61
|
+
}
|
|
62
|
+
else if (placeholder) {
|
|
63
|
+
candidates.push({ selector: `${tag}[placeholder="${CSS.escape(placeholder)}"]`, strategy: "input-attr" });
|
|
64
|
+
}
|
|
65
|
+
else if (type !== "text") {
|
|
66
|
+
candidates.push({ selector: `${tag}[type="${CSS.escape(type)}"]`, strategy: "input-attr" });
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
// 6. Anchor href (for navigation links)
|
|
70
|
+
if (tag === "a") {
|
|
71
|
+
const href = element.getAttribute("href");
|
|
72
|
+
if (href && !href.startsWith("javascript:") && href !== "#") {
|
|
73
|
+
candidates.push({ selector: `a[href="${CSS.escape(href)}"]`, strategy: "link-href" });
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
// 7. Puppeteer text pseudo-selector
|
|
77
|
+
if (visibleText) {
|
|
78
|
+
candidates.push({ selector: `::-p-text(${visibleText})`, strategy: "text" });
|
|
79
|
+
}
|
|
80
|
+
// 8. Minimal CSS path (stable ancestor chain, stops at id or body)
|
|
81
|
+
candidates.push({ selector: buildMinimalCssPath(element), strategy: "css" });
|
|
82
|
+
// Build result: first candidate is primary, rest are fallbacks
|
|
83
|
+
const [first, ...rest] = candidates;
|
|
84
|
+
const description = buildDescription(tag, visibleText, ariaLabel, testid, id);
|
|
85
|
+
return {
|
|
86
|
+
primary: first.selector,
|
|
87
|
+
fallbacks: rest.map((c) => c.selector),
|
|
88
|
+
description,
|
|
89
|
+
strategy: first.strategy,
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
/** Builds a minimal CSS path by walking up the DOM, stopping at an id anchor or body. */
|
|
93
|
+
function buildMinimalCssPath(el) {
|
|
94
|
+
const parts = [];
|
|
95
|
+
let current = el;
|
|
96
|
+
while (current && current.tagName.toLowerCase() !== "body") {
|
|
97
|
+
const tag = current.tagName.toLowerCase();
|
|
98
|
+
const id = current.getAttribute("id");
|
|
99
|
+
if (id && document.querySelectorAll(`#${CSS.escape(id)}`).length === 1) {
|
|
100
|
+
parts.unshift(`#${CSS.escape(id)}`);
|
|
101
|
+
break;
|
|
102
|
+
}
|
|
103
|
+
// nth-of-type using only the tag (avoids fragile dynamic class names)
|
|
104
|
+
const siblings = Array.from(current.parentElement?.children ?? []).filter((s) => s.tagName === current.tagName);
|
|
105
|
+
if (siblings.length > 1) {
|
|
106
|
+
const idx = siblings.indexOf(current) + 1;
|
|
107
|
+
parts.unshift(`${tag}:nth-of-type(${idx})`);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
parts.unshift(tag);
|
|
111
|
+
}
|
|
112
|
+
current = current.parentElement;
|
|
113
|
+
}
|
|
114
|
+
return parts.join(" > ") || el.tagName.toLowerCase();
|
|
115
|
+
}
|
|
116
|
+
/** Builds a human-readable description of the element. */
|
|
117
|
+
function buildDescription(tag, text, ariaLabel, testid, id) {
|
|
118
|
+
const label = ariaLabel ?? testid ?? id ?? text;
|
|
119
|
+
if (label)
|
|
120
|
+
return `${tag}[${label.slice(0, 60)}]`;
|
|
121
|
+
return `<${tag}>`;
|
|
122
|
+
}
|
|
123
|
+
// ---------------------------------------------------------------------------
|
|
124
|
+
// Node.js-context helpers (use Page API)
|
|
125
|
+
// ---------------------------------------------------------------------------
|
|
126
|
+
/**
|
|
127
|
+
* Tries to find an element on the page using multiple selector strategies.
|
|
128
|
+
* Returns the first `ElementHandle` that successfully resolves, or `null`.
|
|
129
|
+
*
|
|
130
|
+
* @param page - Puppeteer Page instance.
|
|
131
|
+
* @param target - Primary selector string or SmartSelector.primary.
|
|
132
|
+
* @param fallbacks - Additional selectors to try if primary fails.
|
|
133
|
+
* @param timeout - Per-selector wait timeout in ms (default: 3000).
|
|
134
|
+
*/
|
|
135
|
+
export async function resolveSelector(page, target, fallbacks = [], timeout = 3_000) {
|
|
136
|
+
const strategies = [target, ...fallbacks];
|
|
137
|
+
for (const selector of strategies) {
|
|
138
|
+
try {
|
|
139
|
+
const handle = await page.waitForSelector(selector, { timeout });
|
|
140
|
+
if (handle)
|
|
141
|
+
return handle;
|
|
142
|
+
}
|
|
143
|
+
catch {
|
|
144
|
+
// Selector not found within timeout — try next
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Takes a basic CSS selector (e.g. from a snapshot), injects
|
|
151
|
+
* `generateSmartSelectors` into the page, and returns a richer SmartSelector
|
|
152
|
+
* with stable alternatives.
|
|
153
|
+
*
|
|
154
|
+
* @param page - Puppeteer Page instance.
|
|
155
|
+
* @param cssSelector - Existing (possibly fragile) CSS selector.
|
|
156
|
+
*/
|
|
157
|
+
export async function enhanceElementSelectors(page, cssSelector) {
|
|
158
|
+
const result = await page.evaluate(
|
|
159
|
+
// NOTE: generateSmartSelectors is serialised and sent to the browser.
|
|
160
|
+
// It must remain self-contained (no closures over Node.js variables).
|
|
161
|
+
(sel, genFnSrc) => {
|
|
162
|
+
const el = document.querySelector(sel);
|
|
163
|
+
if (!el)
|
|
164
|
+
return null;
|
|
165
|
+
// eslint-disable-next-line no-new-func
|
|
166
|
+
const fn = new Function(`return (${genFnSrc})`)();
|
|
167
|
+
return fn(el);
|
|
168
|
+
}, cssSelector, generateSmartSelectors.toString());
|
|
169
|
+
if (!result) {
|
|
170
|
+
// Element not found — return a degenerate SmartSelector
|
|
171
|
+
return {
|
|
172
|
+
primary: cssSelector,
|
|
173
|
+
fallbacks: [],
|
|
174
|
+
description: `<not found: ${cssSelector}>`,
|
|
175
|
+
strategy: "css",
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
return result;
|
|
179
|
+
}
|
|
180
|
+
//# sourceMappingURL=selector-engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"selector-engine.js","sourceRoot":"","sources":["../../src/utils/selector-engine.ts"],"names":[],"mappings":"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"}
|