@detiq/crawler 1.1.9 → 1.1.10
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/accessibility-auditor.js +54 -74
- package/dist/ai-login.js +227 -249
- package/dist/auth-token-rotator.js +57 -67
- package/dist/auth.js +299 -336
- package/dist/browser-extension.js +104 -142
- package/dist/captcha-solver.js +98 -125
- package/dist/color-contrast.js +77 -89
- package/dist/content-chunker.js +126 -126
- package/dist/content-dedup.js +54 -71
- package/dist/cookie-consent.js +68 -83
- package/dist/cookie-jar.js +53 -60
- package/dist/crawler.js +4494 -5353
- package/dist/custom-js-injector.js +45 -61
- package/dist/dark-mode-capture.js +67 -81
- package/dist/dead-link-detector.js +47 -55
- package/dist/element-hash.js +8 -12
- package/dist/email-otp.js +154 -195
- package/dist/engine-router.js +87 -98
- package/dist/export/bulk-export.js +70 -55
- package/dist/export/vector-export.js +77 -105
- package/dist/extractors/advanced.js +249 -266
- package/dist/extractors/artifact-envelope.js +59 -58
- package/dist/failure-analysis-agent.js +37 -33
- package/dist/feature-flag-detector.js +51 -66
- package/dist/form-detector.js +55 -75
- package/dist/frontier.js +76 -101
- package/dist/gap-pass.js +68 -89
- package/dist/graphql-detector.js +62 -72
- package/dist/http-engine.js +282 -330
- package/dist/incremental-diff.js +54 -87
- package/dist/index.js +67 -28
- package/dist/input-fuzzer.js +93 -97
- package/dist/interactive-probe.js +715 -827
- package/dist/job-queue.js +2151 -2169
- package/dist/js-coverage.js +52 -56
- package/dist/js-router-detector.js +86 -117
- package/dist/language-detector.js +51 -63
- package/dist/link-classifier.js +32 -50
- package/dist/live-view.js +91 -108
- package/dist/locator-extractor.js +151 -187
- package/dist/logger.js +9 -7
- package/dist/markdown-extract.js +48 -61
- package/dist/native-crawler.js +780 -946
- package/dist/network-observer.js +135 -156
- package/dist/network-throttle.js +29 -40
- package/dist/openapi-generator.js +77 -92
- package/dist/pdf-extractor.js +72 -90
- package/dist/performance-metrics.js +80 -102
- package/dist/phash.js +125 -144
- package/dist/pii-scrubber.js +65 -68
- package/dist/playwright-recorder.js +70 -58
- package/dist/policies/crawl-safety.js +31 -34
- package/dist/profiles/domain-profile.js +130 -125
- package/dist/proxy.js +146 -172
- package/dist/rate-limiter.js +58 -65
- package/dist/redirect-tracker.js +60 -64
- package/dist/redis-frontier.js +116 -136
- package/dist/redis-queue.js +157 -144
- package/dist/response-cache.js +127 -140
- package/dist/responsive-detector.js +42 -46
- package/dist/retry-policy.js +46 -50
- package/dist/robots-txt.js +69 -83
- package/dist/screen-reader.js +45 -70
- package/dist/screenshot-masker.js +59 -77
- package/dist/seo-checker.js +88 -107
- package/dist/session-capture.js +192 -212
- package/dist/session-export.js +107 -133
- package/dist/sitemap-writer.js +38 -41
- package/dist/sitemap.js +141 -202
- package/dist/skyvern.js +123 -148
- package/dist/sqs-queue.js +42 -42
- package/dist/sso-token.js +74 -90
- package/dist/stealth/human-behavior.js +156 -173
- package/dist/telemetry.js +68 -82
- package/dist/test-executor.js +309 -335
- package/dist/tls-fingerprint.js +77 -88
- package/dist/url-pattern-completer.js +71 -90
- package/dist/visual-diff-verdict.js +23 -38
- package/dist/webhook-notifier.js +71 -69
- package/dist/websocket-detector.js +56 -68
- package/dist/worker.js +29 -38
- package/package.json +1 -1
package/dist/live-view.js
CHANGED
|
@@ -1,118 +1,101 @@
|
|
|
1
|
-
import { chromium } from
|
|
2
|
-
import { randomBytes } from
|
|
3
|
-
import { logger } from
|
|
4
|
-
const sessions = new Map();
|
|
5
|
-
const SESSION_TTL_MS = 15 * 60 *
|
|
1
|
+
import { chromium } from "playwright";
|
|
2
|
+
import { randomBytes } from "node:crypto";
|
|
3
|
+
import { logger } from "./logger.js";
|
|
4
|
+
const sessions = /* @__PURE__ */ new Map();
|
|
5
|
+
const SESSION_TTL_MS = 15 * 60 * 1e3;
|
|
6
6
|
function generateId() {
|
|
7
|
-
|
|
7
|
+
return randomBytes(16).toString("hex");
|
|
8
8
|
}
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
if (storageState) {
|
|
23
|
-
try {
|
|
24
|
-
const state = JSON.parse(storageState);
|
|
25
|
-
if (state.cookies?.length)
|
|
26
|
-
await context.addCookies(state.cookies);
|
|
27
|
-
}
|
|
28
|
-
catch { /* ignore */ }
|
|
9
|
+
async function startLiveView(appUrl, storageState) {
|
|
10
|
+
const id = generateId();
|
|
11
|
+
const isDocker = !!process.env.PLAYWRIGHT_IN_DOCKER || process.platform === "linux";
|
|
12
|
+
const browser = await chromium.launch({
|
|
13
|
+
headless: true,
|
|
14
|
+
args: isDocker ? ["--no-sandbox", "--disable-setuid-sandbox", "--disable-dev-shm-usage", "--disable-gpu"] : []
|
|
15
|
+
});
|
|
16
|
+
const context = await browser.newContext({ viewport: { width: 1280, height: 800 } });
|
|
17
|
+
if (storageState) {
|
|
18
|
+
try {
|
|
19
|
+
const state = JSON.parse(storageState);
|
|
20
|
+
if (state.cookies?.length) await context.addCookies(state.cookies);
|
|
21
|
+
} catch {
|
|
29
22
|
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
23
|
+
}
|
|
24
|
+
const page = await context.newPage();
|
|
25
|
+
await page.goto(appUrl, { waitUntil: "domcontentloaded", timeout: 3e4 });
|
|
26
|
+
const session = {
|
|
27
|
+
id,
|
|
28
|
+
browser,
|
|
29
|
+
context,
|
|
30
|
+
page,
|
|
31
|
+
appUrl,
|
|
32
|
+
lastFrame: null,
|
|
33
|
+
streaming: true,
|
|
34
|
+
frameInterval: null,
|
|
35
|
+
createdAt: Date.now()
|
|
36
|
+
};
|
|
37
|
+
session.frameInterval = setInterval(async () => {
|
|
38
|
+
if (!session.streaming) return;
|
|
39
|
+
try {
|
|
40
|
+
const buf = await page.screenshot({ type: "jpeg", quality: 60, fullPage: false });
|
|
41
|
+
session.lastFrame = buf.toString("base64");
|
|
42
|
+
} catch {
|
|
43
|
+
session.streaming = false;
|
|
44
|
+
}
|
|
45
|
+
}, 500);
|
|
46
|
+
sessions.set(id, session);
|
|
47
|
+
setTimeout(() => closeLiveView(id).catch(() => {
|
|
48
|
+
}), SESSION_TTL_MS);
|
|
49
|
+
logger.info({ id, appUrl }, "[live-view] Session started");
|
|
50
|
+
return id;
|
|
54
51
|
}
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
*/
|
|
58
|
-
export function getFrame(id) {
|
|
59
|
-
return sessions.get(id)?.lastFrame ?? null;
|
|
52
|
+
function getFrame(id) {
|
|
53
|
+
return sessions.get(id)?.lastFrame ?? null;
|
|
60
54
|
}
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
const session = sessions.get(id);
|
|
66
|
-
if (!session)
|
|
67
|
-
throw new Error(`Live view session ${id} not found`);
|
|
68
|
-
await session.page.goto(url, { waitUntil: 'domcontentloaded' });
|
|
55
|
+
async function liveViewNavigate(id, url) {
|
|
56
|
+
const session = sessions.get(id);
|
|
57
|
+
if (!session) throw new Error(`Live view session ${id} not found`);
|
|
58
|
+
await session.page.goto(url, { waitUntil: "domcontentloaded" });
|
|
69
59
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
const state = await session.context.storageState();
|
|
78
|
-
const json = JSON.stringify(state);
|
|
79
|
-
await closeLiveView(id);
|
|
80
|
-
return json;
|
|
60
|
+
async function completeLiveView(id) {
|
|
61
|
+
const session = sessions.get(id);
|
|
62
|
+
if (!session) throw new Error(`Live view session ${id} not found or expired`);
|
|
63
|
+
const state = await session.context.storageState();
|
|
64
|
+
const json = JSON.stringify(state);
|
|
65
|
+
await closeLiveView(id);
|
|
66
|
+
return json;
|
|
81
67
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
else if (event.type === 'type' && event.text) {
|
|
93
|
-
await session.page.keyboard.type(event.text);
|
|
94
|
-
}
|
|
95
|
-
else if (event.type === 'key' && event.key) {
|
|
96
|
-
await session.page.keyboard.press(event.key);
|
|
97
|
-
}
|
|
68
|
+
async function sendInput(id, event) {
|
|
69
|
+
const session = sessions.get(id);
|
|
70
|
+
if (!session) throw new Error(`Live view session ${id} not found`);
|
|
71
|
+
if (event.type === "click" && event.x !== void 0 && event.y !== void 0) {
|
|
72
|
+
await session.page.mouse.click(event.x, event.y, { button: event.button ?? "left" });
|
|
73
|
+
} else if (event.type === "type" && event.text) {
|
|
74
|
+
await session.page.keyboard.type(event.text);
|
|
75
|
+
} else if (event.type === "key" && event.key) {
|
|
76
|
+
await session.page.keyboard.press(event.key);
|
|
77
|
+
}
|
|
98
78
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
clearInterval(session.frameInterval);
|
|
109
|
-
sessions.delete(id);
|
|
110
|
-
await session.browser.close().catch(() => { });
|
|
111
|
-
logger.info({ id }, '[live-view] Session closed');
|
|
79
|
+
async function closeLiveView(id) {
|
|
80
|
+
const session = sessions.get(id);
|
|
81
|
+
if (!session) return;
|
|
82
|
+
session.streaming = false;
|
|
83
|
+
if (session.frameInterval) clearInterval(session.frameInterval);
|
|
84
|
+
sessions.delete(id);
|
|
85
|
+
await session.browser.close().catch(() => {
|
|
86
|
+
});
|
|
87
|
+
logger.info({ id }, "[live-view] Session closed");
|
|
112
88
|
}
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
*/
|
|
116
|
-
export function listLiveViews() {
|
|
117
|
-
return [...sessions.keys()];
|
|
89
|
+
function listLiveViews() {
|
|
90
|
+
return [...sessions.keys()];
|
|
118
91
|
}
|
|
92
|
+
export {
|
|
93
|
+
closeLiveView,
|
|
94
|
+
completeLiveView,
|
|
95
|
+
getFrame,
|
|
96
|
+
listLiveViews,
|
|
97
|
+
liveViewNavigate,
|
|
98
|
+
sendInput,
|
|
99
|
+
startLiveView
|
|
100
|
+
};
|
|
101
|
+
//# sourceMappingURL=live-view.js.map
|
|
@@ -1,199 +1,163 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Converts raw DB Element records (role, meaning, notes JSON) into Playwright-idiomatic locator strings.
|
|
3
|
-
* Priority: data-testid → aria-label/placeholder (inputs) → role+name (buttons/links) → heading → text → id
|
|
4
|
-
*/
|
|
5
|
-
/** Parse the notes JSON string stored on Element records. */
|
|
6
1
|
function parseNotes(notes) {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
catch { }
|
|
15
|
-
return {};
|
|
2
|
+
if (!notes) return {};
|
|
3
|
+
try {
|
|
4
|
+
const parsed = typeof notes === "string" ? JSON.parse(notes) : notes;
|
|
5
|
+
if (parsed && typeof parsed === "object") return parsed;
|
|
6
|
+
} catch {
|
|
7
|
+
}
|
|
8
|
+
return {};
|
|
16
9
|
}
|
|
17
|
-
/** Map crawler role strings to Playwright role values. */
|
|
18
10
|
function mapRole(role) {
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
return 'checkbox';
|
|
32
|
-
if (r === 'radio')
|
|
33
|
-
return 'radio';
|
|
34
|
-
if (r === 'heading' || r === 'h1' || r === 'h2' || r === 'h3' || r === 'h4' || r === 'h5' || r === 'h6')
|
|
35
|
-
return 'heading';
|
|
36
|
-
if (r === 'img' || r === 'image')
|
|
37
|
-
return 'img';
|
|
38
|
-
if (r === 'nav' || r === 'navigation')
|
|
39
|
-
return 'navigation';
|
|
40
|
-
return r;
|
|
11
|
+
if (!role) return null;
|
|
12
|
+
const r = role.toLowerCase().trim();
|
|
13
|
+
if (r === "button" || r === "submit" || r === "reset") return "button";
|
|
14
|
+
if (r === "link" || r === "a" || r === "anchor") return "link";
|
|
15
|
+
if (r === "textbox" || r === "input" || r === "text" || r === "email" || r === "password" || r === "tel" || r === "number" || r === "search") return "textbox";
|
|
16
|
+
if (r === "combobox" || r === "select" || r === "dropdown") return "combobox";
|
|
17
|
+
if (r === "checkbox") return "checkbox";
|
|
18
|
+
if (r === "radio") return "radio";
|
|
19
|
+
if (r === "heading" || r === "h1" || r === "h2" || r === "h3" || r === "h4" || r === "h5" || r === "h6") return "heading";
|
|
20
|
+
if (r === "img" || r === "image") return "img";
|
|
21
|
+
if (r === "nav" || r === "navigation") return "navigation";
|
|
22
|
+
return r;
|
|
41
23
|
}
|
|
42
|
-
/** Convert "Submit Form Button" → "submitFormButton" */
|
|
43
24
|
function toKey(text) {
|
|
44
|
-
|
|
45
|
-
.replace(/[^a-zA-Z0-9\s]/g, ' ')
|
|
46
|
-
.replace(/\s+(.)/g, (_, c) => c.toUpperCase())
|
|
47
|
-
.replace(/^(.)/, (_, c) => c.toLowerCase())
|
|
48
|
-
.replace(/\s/g, '')
|
|
49
|
-
.slice(0, 40)
|
|
50
|
-
|| 'element';
|
|
25
|
+
return text.replace(/[^a-zA-Z0-9\s]/g, " ").replace(/\s+(.)/g, (_, c) => c.toUpperCase()).replace(/^(.)/, (_, c) => c.toLowerCase()).replace(/\s/g, "").slice(0, 40) || "element";
|
|
51
26
|
}
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
source: 'label',
|
|
85
|
-
description: ariaLabel,
|
|
86
|
-
};
|
|
87
|
-
}
|
|
88
|
-
if (placeholder) {
|
|
89
|
-
return {
|
|
90
|
-
key,
|
|
91
|
-
expression: `page.getByPlaceholder(${JSON.stringify(placeholder)})`,
|
|
92
|
-
elementType: 'input',
|
|
93
|
-
confidence: 'high',
|
|
94
|
-
source: 'placeholder',
|
|
95
|
-
description: placeholder,
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
// Priority 3: checkbox/radio → getByLabel
|
|
100
|
-
if ((role === 'checkbox' || role === 'radio') && ariaLabel) {
|
|
101
|
-
return {
|
|
102
|
-
key,
|
|
103
|
-
expression: `page.getByLabel(${JSON.stringify(ariaLabel)})`,
|
|
104
|
-
elementType: role,
|
|
105
|
-
confidence: 'high',
|
|
106
|
-
source: 'label',
|
|
107
|
-
description: ariaLabel,
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
// Priority 4: button/link with meaningful name → getByRole
|
|
111
|
-
if ((role === 'button' || role === 'link') && meaning) {
|
|
112
|
-
return {
|
|
113
|
-
key,
|
|
114
|
-
expression: `page.getByRole(${JSON.stringify(role)}, { name: ${JSON.stringify(meaning)} })`,
|
|
115
|
-
elementType: role,
|
|
116
|
-
confidence: 'high',
|
|
117
|
-
source: 'role-name',
|
|
118
|
-
description: meaning,
|
|
119
|
-
};
|
|
120
|
-
}
|
|
121
|
-
// Priority 5: heading → getByRole heading
|
|
122
|
-
if (role === 'heading' && (meaning || text)) {
|
|
123
|
-
const name2 = meaning || text;
|
|
124
|
-
return {
|
|
125
|
-
key,
|
|
126
|
-
expression: `page.getByRole('heading', { name: ${JSON.stringify(name2)} })`,
|
|
127
|
-
elementType: 'heading',
|
|
128
|
-
confidence: 'medium',
|
|
129
|
-
source: 'heading',
|
|
130
|
-
description: name2,
|
|
131
|
-
};
|
|
132
|
-
}
|
|
133
|
-
// Priority 6: existing CSS selector from crawler
|
|
134
|
-
if (element.selector && element.selector.length > 0) {
|
|
135
|
-
return {
|
|
136
|
-
key,
|
|
137
|
-
expression: `page.locator(${JSON.stringify(element.selector)})`,
|
|
138
|
-
elementType: role ?? 'element',
|
|
139
|
-
confidence: 'medium',
|
|
140
|
-
source: 'id',
|
|
141
|
-
description: meaning || text || element.selector,
|
|
142
|
-
};
|
|
143
|
-
}
|
|
144
|
-
// Priority 7: id from attrs
|
|
145
|
-
if (id) {
|
|
146
|
-
return {
|
|
147
|
-
key,
|
|
148
|
-
expression: `page.locator(${JSON.stringify('#' + id)})`,
|
|
149
|
-
elementType: role ?? 'element',
|
|
150
|
-
confidence: 'medium',
|
|
151
|
-
source: 'id',
|
|
152
|
-
description: meaning || id,
|
|
153
|
-
};
|
|
27
|
+
function elementToPlaywrightLocator(element) {
|
|
28
|
+
const attrs = parseNotes(element.notes);
|
|
29
|
+
const testId = attrs["data-testid"];
|
|
30
|
+
const ariaLabel = attrs["aria-label"] || element.meaning;
|
|
31
|
+
const id = attrs["id"];
|
|
32
|
+
const placeholder = attrs["placeholder"];
|
|
33
|
+
const name = attrs["name"];
|
|
34
|
+
const text = (element.text ?? "").trim();
|
|
35
|
+
const meaning = (element.meaning ?? "").trim();
|
|
36
|
+
const role = mapRole(element.role);
|
|
37
|
+
const label = meaning || text || testId || ariaLabel || placeholder || id || name || "element";
|
|
38
|
+
const key = toKey(label);
|
|
39
|
+
if (testId) {
|
|
40
|
+
return {
|
|
41
|
+
key,
|
|
42
|
+
expression: `page.getByTestId(${JSON.stringify(testId)})`,
|
|
43
|
+
elementType: role ?? "element",
|
|
44
|
+
confidence: "high",
|
|
45
|
+
source: "testid",
|
|
46
|
+
description: meaning || testId
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
if (role === "textbox" || role === "combobox") {
|
|
50
|
+
if (ariaLabel) {
|
|
51
|
+
return {
|
|
52
|
+
key,
|
|
53
|
+
expression: `page.getByLabel(${JSON.stringify(ariaLabel)})`,
|
|
54
|
+
elementType: "input",
|
|
55
|
+
confidence: "high",
|
|
56
|
+
source: "label",
|
|
57
|
+
description: ariaLabel
|
|
58
|
+
};
|
|
154
59
|
}
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
description: t,
|
|
165
|
-
};
|
|
60
|
+
if (placeholder) {
|
|
61
|
+
return {
|
|
62
|
+
key,
|
|
63
|
+
expression: `page.getByPlaceholder(${JSON.stringify(placeholder)})`,
|
|
64
|
+
elementType: "input",
|
|
65
|
+
confidence: "high",
|
|
66
|
+
source: "placeholder",
|
|
67
|
+
description: placeholder
|
|
68
|
+
};
|
|
166
69
|
}
|
|
167
|
-
|
|
70
|
+
}
|
|
71
|
+
if ((role === "checkbox" || role === "radio") && ariaLabel) {
|
|
72
|
+
return {
|
|
73
|
+
key,
|
|
74
|
+
expression: `page.getByLabel(${JSON.stringify(ariaLabel)})`,
|
|
75
|
+
elementType: role,
|
|
76
|
+
confidence: "high",
|
|
77
|
+
source: "label",
|
|
78
|
+
description: ariaLabel
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
if ((role === "button" || role === "link") && meaning) {
|
|
82
|
+
return {
|
|
83
|
+
key,
|
|
84
|
+
expression: `page.getByRole(${JSON.stringify(role)}, { name: ${JSON.stringify(meaning)} })`,
|
|
85
|
+
elementType: role,
|
|
86
|
+
confidence: "high",
|
|
87
|
+
source: "role-name",
|
|
88
|
+
description: meaning
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
if (role === "heading" && (meaning || text)) {
|
|
92
|
+
const name2 = meaning || text;
|
|
93
|
+
return {
|
|
94
|
+
key,
|
|
95
|
+
expression: `page.getByRole('heading', { name: ${JSON.stringify(name2)} })`,
|
|
96
|
+
elementType: "heading",
|
|
97
|
+
confidence: "medium",
|
|
98
|
+
source: "heading",
|
|
99
|
+
description: name2
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
if (element.selector && element.selector.length > 0) {
|
|
103
|
+
return {
|
|
104
|
+
key,
|
|
105
|
+
expression: `page.locator(${JSON.stringify(element.selector)})`,
|
|
106
|
+
elementType: role ?? "element",
|
|
107
|
+
confidence: "medium",
|
|
108
|
+
source: "id",
|
|
109
|
+
description: meaning || text || element.selector
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
if (id) {
|
|
113
|
+
return {
|
|
114
|
+
key,
|
|
115
|
+
expression: `page.locator(${JSON.stringify("#" + id)})`,
|
|
116
|
+
elementType: role ?? "element",
|
|
117
|
+
confidence: "medium",
|
|
118
|
+
source: "id",
|
|
119
|
+
description: meaning || id
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
if (meaning || text) {
|
|
123
|
+
const t = meaning || text;
|
|
124
|
+
return {
|
|
125
|
+
key,
|
|
126
|
+
expression: `page.getByText(${JSON.stringify(t)}, { exact: false })`,
|
|
127
|
+
elementType: role ?? "text",
|
|
128
|
+
confidence: "low",
|
|
129
|
+
source: "text",
|
|
130
|
+
description: t
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
return null;
|
|
168
134
|
}
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
if (!loc)
|
|
179
|
-
continue;
|
|
180
|
-
const existing = byKey.get(loc.key);
|
|
181
|
-
if (!existing || CONFIDENCE_RANK[loc.confidence] < CONFIDENCE_RANK[existing.confidence]) {
|
|
182
|
-
byKey.set(loc.key, loc);
|
|
183
|
-
}
|
|
135
|
+
function elementsToPlaywrightLocators(elements) {
|
|
136
|
+
const CONFIDENCE_RANK = { high: 0, medium: 1, low: 2 };
|
|
137
|
+
const byKey = /* @__PURE__ */ new Map();
|
|
138
|
+
for (const el of elements) {
|
|
139
|
+
const loc = elementToPlaywrightLocator(el);
|
|
140
|
+
if (!loc) continue;
|
|
141
|
+
const existing = byKey.get(loc.key);
|
|
142
|
+
if (!existing || CONFIDENCE_RANK[loc.confidence] < CONFIDENCE_RANK[existing.confidence]) {
|
|
143
|
+
byKey.set(loc.key, loc);
|
|
184
144
|
}
|
|
185
|
-
|
|
145
|
+
}
|
|
146
|
+
return Array.from(byKey.values());
|
|
186
147
|
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
'EXTRACTED PAGE LOCATORS (use these EXACTLY — do not invent other selectors; these were verified against the live page):',
|
|
197
|
-
...lines,
|
|
198
|
-
].join('\n');
|
|
148
|
+
function formatLocatorsForPrompt(locators) {
|
|
149
|
+
if (locators.length === 0) return "";
|
|
150
|
+
const lines = locators.map(
|
|
151
|
+
(l) => ` ${l.key}: ${l.expression} // ${l.description} [${l.confidence}]`
|
|
152
|
+
);
|
|
153
|
+
return [
|
|
154
|
+
"EXTRACTED PAGE LOCATORS (use these EXACTLY \u2014 do not invent other selectors; these were verified against the live page):",
|
|
155
|
+
...lines
|
|
156
|
+
].join("\n");
|
|
199
157
|
}
|
|
158
|
+
export {
|
|
159
|
+
elementToPlaywrightLocator,
|
|
160
|
+
elementsToPlaywrightLocators,
|
|
161
|
+
formatLocatorsForPrompt
|
|
162
|
+
};
|
|
163
|
+
//# sourceMappingURL=locator-extractor.js.map
|
package/dist/logger.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
|
-
import pino from
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
: {}),
|
|
7
|
-
base: { service: 'crawler' },
|
|
1
|
+
import pino from "pino";
|
|
2
|
+
const logger = pino({
|
|
3
|
+
level: process.env.LOG_LEVEL ?? "info",
|
|
4
|
+
...process.env.NODE_ENV === "development" ? { transport: { target: "pino-pretty", options: { colorize: true } } } : {},
|
|
5
|
+
base: { service: "crawler" }
|
|
8
6
|
});
|
|
7
|
+
export {
|
|
8
|
+
logger
|
|
9
|
+
};
|
|
10
|
+
//# sourceMappingURL=logger.js.map
|