@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.
Files changed (82) hide show
  1. package/dist/accessibility-auditor.js +54 -74
  2. package/dist/ai-login.js +227 -249
  3. package/dist/auth-token-rotator.js +57 -67
  4. package/dist/auth.js +299 -336
  5. package/dist/browser-extension.js +104 -142
  6. package/dist/captcha-solver.js +98 -125
  7. package/dist/color-contrast.js +77 -89
  8. package/dist/content-chunker.js +126 -126
  9. package/dist/content-dedup.js +54 -71
  10. package/dist/cookie-consent.js +68 -83
  11. package/dist/cookie-jar.js +53 -60
  12. package/dist/crawler.js +4494 -5353
  13. package/dist/custom-js-injector.js +45 -61
  14. package/dist/dark-mode-capture.js +67 -81
  15. package/dist/dead-link-detector.js +47 -55
  16. package/dist/element-hash.js +8 -12
  17. package/dist/email-otp.js +154 -195
  18. package/dist/engine-router.js +87 -98
  19. package/dist/export/bulk-export.js +70 -55
  20. package/dist/export/vector-export.js +77 -105
  21. package/dist/extractors/advanced.js +249 -266
  22. package/dist/extractors/artifact-envelope.js +59 -58
  23. package/dist/failure-analysis-agent.js +37 -33
  24. package/dist/feature-flag-detector.js +51 -66
  25. package/dist/form-detector.js +55 -75
  26. package/dist/frontier.js +76 -101
  27. package/dist/gap-pass.js +68 -89
  28. package/dist/graphql-detector.js +62 -72
  29. package/dist/http-engine.js +282 -330
  30. package/dist/incremental-diff.js +54 -87
  31. package/dist/index.js +67 -28
  32. package/dist/input-fuzzer.js +93 -97
  33. package/dist/interactive-probe.js +715 -827
  34. package/dist/job-queue.js +2151 -2169
  35. package/dist/js-coverage.js +52 -56
  36. package/dist/js-router-detector.js +86 -117
  37. package/dist/language-detector.js +51 -63
  38. package/dist/link-classifier.js +32 -50
  39. package/dist/live-view.js +91 -108
  40. package/dist/locator-extractor.js +151 -187
  41. package/dist/logger.js +9 -7
  42. package/dist/markdown-extract.js +48 -61
  43. package/dist/native-crawler.js +780 -946
  44. package/dist/network-observer.js +135 -156
  45. package/dist/network-throttle.js +29 -40
  46. package/dist/openapi-generator.js +77 -92
  47. package/dist/pdf-extractor.js +72 -90
  48. package/dist/performance-metrics.js +80 -102
  49. package/dist/phash.js +125 -144
  50. package/dist/pii-scrubber.js +65 -68
  51. package/dist/playwright-recorder.js +70 -58
  52. package/dist/policies/crawl-safety.js +31 -34
  53. package/dist/profiles/domain-profile.js +130 -125
  54. package/dist/proxy.js +146 -172
  55. package/dist/rate-limiter.js +58 -65
  56. package/dist/redirect-tracker.js +60 -64
  57. package/dist/redis-frontier.js +116 -136
  58. package/dist/redis-queue.js +157 -144
  59. package/dist/response-cache.js +127 -140
  60. package/dist/responsive-detector.js +42 -46
  61. package/dist/retry-policy.js +46 -50
  62. package/dist/robots-txt.js +69 -83
  63. package/dist/screen-reader.js +45 -70
  64. package/dist/screenshot-masker.js +59 -77
  65. package/dist/seo-checker.js +88 -107
  66. package/dist/session-capture.js +192 -212
  67. package/dist/session-export.js +107 -133
  68. package/dist/sitemap-writer.js +38 -41
  69. package/dist/sitemap.js +141 -202
  70. package/dist/skyvern.js +123 -148
  71. package/dist/sqs-queue.js +42 -42
  72. package/dist/sso-token.js +74 -90
  73. package/dist/stealth/human-behavior.js +156 -173
  74. package/dist/telemetry.js +68 -82
  75. package/dist/test-executor.js +309 -335
  76. package/dist/tls-fingerprint.js +77 -88
  77. package/dist/url-pattern-completer.js +71 -90
  78. package/dist/visual-diff-verdict.js +23 -38
  79. package/dist/webhook-notifier.js +71 -69
  80. package/dist/websocket-detector.js +56 -68
  81. package/dist/worker.js +29 -38
  82. package/package.json +1 -1
package/dist/live-view.js CHANGED
@@ -1,118 +1,101 @@
1
- import { chromium } from 'playwright';
2
- import { randomBytes } from 'node:crypto';
3
- import { logger } from './logger.js';
4
- const sessions = new Map();
5
- const SESSION_TTL_MS = 15 * 60 * 1_000; // 15 min
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
- return randomBytes(16).toString('hex');
7
+ return randomBytes(16).toString("hex");
8
8
  }
9
- /**
10
- * Start a live view session: launches a headless browser, navigates to appUrl,
11
- * begins capturing frames. Returns the session ID.
12
- */
13
- export async function startLiveView(appUrl, storageState) {
14
- const id = generateId();
15
- const isDocker = !!process.env.PLAYWRIGHT_IN_DOCKER || process.platform === 'linux';
16
- const browser = await chromium.launch({
17
- headless: true,
18
- args: isDocker ? ['--no-sandbox', '--disable-setuid-sandbox', '--disable-dev-shm-usage', '--disable-gpu'] : [],
19
- });
20
- const context = await browser.newContext({ viewport: { width: 1280, height: 800 } });
21
- // Restore session if provided
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
- const page = await context.newPage();
31
- await page.goto(appUrl, { waitUntil: 'domcontentloaded', timeout: 30_000 });
32
- const session = {
33
- id, browser, context, page, appUrl,
34
- lastFrame: null, streaming: true, frameInterval: null,
35
- createdAt: Date.now(),
36
- };
37
- // Start capturing frames (every 500ms)
38
- session.frameInterval = setInterval(async () => {
39
- if (!session.streaming)
40
- return;
41
- try {
42
- const buf = await page.screenshot({ type: 'jpeg', quality: 60, fullPage: false });
43
- session.lastFrame = buf.toString('base64');
44
- }
45
- catch {
46
- session.streaming = false;
47
- }
48
- }, 500);
49
- sessions.set(id, session);
50
- // Auto-cleanup after TTL
51
- setTimeout(() => closeLiveView(id).catch(() => { }), SESSION_TTL_MS);
52
- logger.info({ id, appUrl }, '[live-view] Session started');
53
- return id;
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
- * Get the latest frame for a session (base64 JPEG).
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
- * Navigate the live view browser to a URL.
63
- */
64
- export async function liveViewNavigate(id, url) {
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
- * Capture storageState from the live view session, close the browser, return JSON string.
72
- */
73
- export async function completeLiveView(id) {
74
- const session = sessions.get(id);
75
- if (!session)
76
- throw new Error(`Live view session ${id} not found or expired`);
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
- * Send a mouse or keyboard input event to the live view browser.
84
- */
85
- export async function sendInput(id, event) {
86
- const session = sessions.get(id);
87
- if (!session)
88
- throw new Error(`Live view session ${id} not found`);
89
- if (event.type === 'click' && event.x !== undefined && event.y !== undefined) {
90
- await session.page.mouse.click(event.x, event.y, { button: event.button ?? 'left' });
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
- * Close a live view session without capturing.
101
- */
102
- export async function closeLiveView(id) {
103
- const session = sessions.get(id);
104
- if (!session)
105
- return;
106
- session.streaming = false;
107
- if (session.frameInterval)
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
- * List all active session IDs (for health checks / cleanup).
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
- if (!notes)
8
- return {};
9
- try {
10
- const parsed = typeof notes === 'string' ? JSON.parse(notes) : notes;
11
- if (parsed && typeof parsed === 'object')
12
- return parsed;
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
- if (!role)
20
- return null;
21
- const r = role.toLowerCase().trim();
22
- if (r === 'button' || r === 'submit' || r === 'reset')
23
- return 'button';
24
- if (r === 'link' || r === 'a' || r === 'anchor')
25
- return 'link';
26
- if (r === 'textbox' || r === 'input' || r === 'text' || r === 'email' || r === 'password' || r === 'tel' || r === 'number' || r === 'search')
27
- return 'textbox';
28
- if (r === 'combobox' || r === 'select' || r === 'dropdown')
29
- return 'combobox';
30
- if (r === 'checkbox')
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
- return text
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
- /** Convert a single DB Element record to a Playwright locator. Returns null if insufficient data. */
53
- export function elementToPlaywrightLocator(element) {
54
- const attrs = parseNotes(element.notes);
55
- const testId = attrs['data-testid'];
56
- const ariaLabel = attrs['aria-label'] || element.meaning;
57
- const id = attrs['id'];
58
- const placeholder = attrs['placeholder'];
59
- const name = attrs['name'];
60
- const text = (element.text ?? '').trim();
61
- const meaning = (element.meaning ?? '').trim();
62
- const role = mapRole(element.role);
63
- const label = meaning || text || testId || ariaLabel || placeholder || id || name || 'element';
64
- const key = toKey(label);
65
- // Priority 1: data-testid
66
- if (testId) {
67
- return {
68
- key,
69
- expression: `page.getByTestId(${JSON.stringify(testId)})`,
70
- elementType: role ?? 'element',
71
- confidence: 'high',
72
- source: 'testid',
73
- description: meaning || testId,
74
- };
75
- }
76
- // Priority 2: input/combobox/textarea with label → getByLabel; with placeholder → getByPlaceholder
77
- if (role === 'textbox' || role === 'combobox') {
78
- if (ariaLabel) {
79
- return {
80
- key,
81
- expression: `page.getByLabel(${JSON.stringify(ariaLabel)})`,
82
- elementType: 'input',
83
- confidence: 'high',
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
- // Priority 8: fallback to text content
156
- if (meaning || text) {
157
- const t = meaning || text;
158
- return {
159
- key,
160
- expression: `page.getByText(${JSON.stringify(t)}, { exact: false })`,
161
- elementType: role ?? 'text',
162
- confidence: 'low',
163
- source: 'text',
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
- return null;
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
- * Convert an array of DB Element records to deduplicated Playwright locators.
171
- * Deduplicates by key — higher confidence wins.
172
- */
173
- export function elementsToPlaywrightLocators(elements) {
174
- const CONFIDENCE_RANK = { high: 0, medium: 1, low: 2 };
175
- const byKey = new Map();
176
- for (const el of elements) {
177
- const loc = elementToPlaywrightLocator(el);
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
- return Array.from(byKey.values());
145
+ }
146
+ return Array.from(byKey.values());
186
147
  }
187
- /**
188
- * Format locators as a block for injection into LLM script generation prompts.
189
- * Returns empty string if no locators.
190
- */
191
- export function formatLocatorsForPrompt(locators) {
192
- if (locators.length === 0)
193
- return '';
194
- const lines = locators.map(l => ` ${l.key}: ${l.expression} // ${l.description} [${l.confidence}]`);
195
- return [
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 'pino';
2
- export const logger = pino({
3
- level: process.env.LOG_LEVEL ?? 'info',
4
- ...(process.env.NODE_ENV === 'development'
5
- ? { transport: { target: 'pino-pretty', options: { colorize: true } } }
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