agentic-browser 2.3.0 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +1 -1
- package/README.md +4 -1
- package/dist/cli/index.d.mts +2 -0
- package/dist/cli/index.mjs +1 -1
- package/dist/index.d.mts +928 -0
- package/dist/index.mjs +1 -1
- package/dist/mcp/index.d.mts +5 -0
- package/dist/mcp/index.mjs +59 -3
- package/dist/{runtime-BCGndjhR.mjs → runtime-C3Y-UgC1.mjs} +519 -51
- package/package.json +30 -4
|
@@ -224,19 +224,78 @@ async function createTarget(cdpUrl, url = "about:blank") {
|
|
|
224
224
|
} catch {}
|
|
225
225
|
return await ensurePageWebSocketUrl(cdpUrl);
|
|
226
226
|
}
|
|
227
|
-
/**
|
|
227
|
+
/**
|
|
228
|
+
* Apply stealth patches that remove common automation fingerprints.
|
|
229
|
+
* These run before any page loads so sites cannot detect the bot.
|
|
230
|
+
*/
|
|
231
|
+
const STEALTH_SCRIPT = `
|
|
232
|
+
// Remove navigator.webdriver flag
|
|
233
|
+
Object.defineProperty(navigator, 'webdriver', { get: () => undefined });
|
|
234
|
+
|
|
235
|
+
// Provide realistic plugins array (empty = headless fingerprint)
|
|
236
|
+
Object.defineProperty(navigator, 'plugins', {
|
|
237
|
+
get: () => {
|
|
238
|
+
const plugins = [
|
|
239
|
+
{ name: 'Chrome PDF Plugin', filename: 'internal-pdf-viewer', description: 'Portable Document Format' },
|
|
240
|
+
{ name: 'Chrome PDF Viewer', filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai', description: '' },
|
|
241
|
+
{ name: 'Native Client', filename: 'internal-nacl-plugin', description: '' },
|
|
242
|
+
];
|
|
243
|
+
plugins.length = 3;
|
|
244
|
+
Object.setPrototypeOf(plugins, PluginArray.prototype);
|
|
245
|
+
return plugins;
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
// Provide realistic languages
|
|
250
|
+
Object.defineProperty(navigator, 'languages', { get: () => ['en-US', 'en'] });
|
|
251
|
+
|
|
252
|
+
// Patch chrome.runtime to look like a real Chrome
|
|
253
|
+
if (!window.chrome) window.chrome = {};
|
|
254
|
+
if (!window.chrome.runtime) window.chrome.runtime = { id: undefined };
|
|
255
|
+
|
|
256
|
+
// Remove the CDP-injected sourceURL comment from Runtime.evaluate calls
|
|
257
|
+
const origToString = Function.prototype.toString;
|
|
258
|
+
Function.prototype.toString = function() {
|
|
259
|
+
if (this === Function.prototype.toString) return 'function toString() { [native code] }';
|
|
260
|
+
return origToString.call(this);
|
|
261
|
+
};
|
|
262
|
+
|
|
263
|
+
// Patch permissions query to report "prompt" instead of "denied" for notifications
|
|
264
|
+
const origQuery = window.Permissions?.prototype?.query;
|
|
265
|
+
if (origQuery) {
|
|
266
|
+
window.Permissions.prototype.query = function(params) {
|
|
267
|
+
if (params?.name === 'notifications') {
|
|
268
|
+
return Promise.resolve({ state: 'prompt', onchange: null });
|
|
269
|
+
}
|
|
270
|
+
return origQuery.call(this, params);
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
`;
|
|
274
|
+
/** Verify the page is ready, apply stealth, and optionally set a custom user-agent. */
|
|
228
275
|
async function initTarget(targetWsUrl, userAgent) {
|
|
229
276
|
const conn = await CdpConnection.connect(targetWsUrl);
|
|
230
277
|
try {
|
|
231
278
|
const enables = [conn.send("Page.enable"), conn.send("Runtime.enable")];
|
|
232
|
-
|
|
279
|
+
enables.push(conn.send("Network.enable"));
|
|
233
280
|
await Promise.all(enables);
|
|
281
|
+
await conn.send("Page.addScriptToEvaluateOnNewDocument", { source: STEALTH_SCRIPT });
|
|
282
|
+
await conn.send("Runtime.evaluate", {
|
|
283
|
+
expression: STEALTH_SCRIPT,
|
|
284
|
+
returnByValue: true
|
|
285
|
+
});
|
|
234
286
|
await conn.send("Runtime.evaluate", {
|
|
235
287
|
expression: "window.location.href",
|
|
236
288
|
returnByValue: true,
|
|
237
289
|
awaitPromise: true
|
|
238
290
|
});
|
|
239
291
|
if (userAgent) await conn.send("Network.setUserAgentOverride", { userAgent });
|
|
292
|
+
else try {
|
|
293
|
+
const { userAgent: currentUA } = await conn.send("Browser.getVersion");
|
|
294
|
+
if (currentUA?.includes("HeadlessChrome")) {
|
|
295
|
+
const cleanUA = currentUA.replace(/HeadlessChrome/g, "Chrome");
|
|
296
|
+
await conn.send("Network.setUserAgentOverride", { userAgent: cleanUA });
|
|
297
|
+
}
|
|
298
|
+
} catch {}
|
|
240
299
|
} finally {
|
|
241
300
|
conn.close();
|
|
242
301
|
}
|
|
@@ -732,7 +791,10 @@ var ChromeCdpBrowserController = class {
|
|
|
732
791
|
`--remote-debugging-port=${port}`,
|
|
733
792
|
`--user-data-dir=${profileDir}`,
|
|
734
793
|
"--no-first-run",
|
|
735
|
-
"--no-default-browser-check"
|
|
794
|
+
"--no-default-browser-check",
|
|
795
|
+
"--disable-blink-features=AutomationControlled",
|
|
796
|
+
"--disable-infobars",
|
|
797
|
+
"--excludeSwitches=enable-automation"
|
|
736
798
|
];
|
|
737
799
|
if (attempt.headless) args.push("--headless=new");
|
|
738
800
|
args.push("about:blank");
|
|
@@ -1076,6 +1138,38 @@ var ChromeCdpBrowserController = class {
|
|
|
1076
1138
|
})).result.value ?? "";
|
|
1077
1139
|
});
|
|
1078
1140
|
}
|
|
1141
|
+
async screenshot(targetWsUrl, options) {
|
|
1142
|
+
return await this.withRetry(targetWsUrl, async (conn) => {
|
|
1143
|
+
await this.ensureEnabled(targetWsUrl);
|
|
1144
|
+
const format = options?.format ?? "jpeg";
|
|
1145
|
+
const params = {
|
|
1146
|
+
format,
|
|
1147
|
+
quality: format === "jpeg" ? options?.quality ?? 60 : void 0
|
|
1148
|
+
};
|
|
1149
|
+
if (options?.fullPage) {
|
|
1150
|
+
const metrics = await conn.send("Page.getLayoutMetrics");
|
|
1151
|
+
params.clip = {
|
|
1152
|
+
x: 0,
|
|
1153
|
+
y: 0,
|
|
1154
|
+
width: metrics.contentSize.width,
|
|
1155
|
+
height: metrics.contentSize.height,
|
|
1156
|
+
scale: 1
|
|
1157
|
+
};
|
|
1158
|
+
}
|
|
1159
|
+
return {
|
|
1160
|
+
data: (await conn.send("Page.captureScreenshot", params)).data,
|
|
1161
|
+
mimeType: format === "png" ? "image/png" : "image/jpeg"
|
|
1162
|
+
};
|
|
1163
|
+
});
|
|
1164
|
+
}
|
|
1165
|
+
async getCurrentUrl(targetWsUrl) {
|
|
1166
|
+
return await this.withRetry(targetWsUrl, async (conn) => {
|
|
1167
|
+
return (await conn.send("Runtime.evaluate", {
|
|
1168
|
+
expression: "window.location.href",
|
|
1169
|
+
returnByValue: true
|
|
1170
|
+
})).result.value ?? "";
|
|
1171
|
+
});
|
|
1172
|
+
}
|
|
1079
1173
|
async getContent(targetWsUrl, options) {
|
|
1080
1174
|
if (options.mode === "summary") return await this.getSummaryContent(targetWsUrl, options);
|
|
1081
1175
|
if (options.mode === "a11y") {
|
|
@@ -1149,7 +1243,8 @@ var ChromeCdpBrowserController = class {
|
|
|
1149
1243
|
fallbackSelectors: element.fallbackSelectors,
|
|
1150
1244
|
inputType: element.inputType,
|
|
1151
1245
|
placeholder: element.placeholder,
|
|
1152
|
-
ariaLabel: element.ariaLabel
|
|
1246
|
+
ariaLabel: element.ariaLabel,
|
|
1247
|
+
currentValue: element.currentValue
|
|
1153
1248
|
});
|
|
1154
1249
|
if (primaryActions.length >= 8 && inputs.length >= 8) break;
|
|
1155
1250
|
}
|
|
@@ -1261,11 +1356,84 @@ var ChromeCdpBrowserController = class {
|
|
|
1261
1356
|
};
|
|
1262
1357
|
});
|
|
1263
1358
|
|
|
1359
|
+
// Extract a readable content excerpt so the LLM understands what the page is about
|
|
1360
|
+
function extractContentExcerpt(root, maxChars) {
|
|
1361
|
+
const skipTags = new Set(['SCRIPT','STYLE','NOSCRIPT','SVG','PATH','META','LINK','BR','HR','IMG']);
|
|
1362
|
+
const blocks = [];
|
|
1363
|
+
let totalChars = 0;
|
|
1364
|
+
const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT, {
|
|
1365
|
+
acceptNode: function(node) {
|
|
1366
|
+
const parent = node.parentElement;
|
|
1367
|
+
if (!parent) return NodeFilter.FILTER_REJECT;
|
|
1368
|
+
if (skipTags.has(parent.tagName)) return NodeFilter.FILTER_REJECT;
|
|
1369
|
+
const view = (parent.ownerDocument && parent.ownerDocument.defaultView) ? parent.ownerDocument.defaultView : window;
|
|
1370
|
+
const style = view.getComputedStyle(parent);
|
|
1371
|
+
if (style.display === 'none' || style.visibility === 'hidden') return NodeFilter.FILTER_REJECT;
|
|
1372
|
+
return NodeFilter.FILTER_ACCEPT;
|
|
1373
|
+
}
|
|
1374
|
+
});
|
|
1375
|
+
let node;
|
|
1376
|
+
while ((node = walker.nextNode())) {
|
|
1377
|
+
const text = node.textContent.replace(/\\s+/g, ' ').trim();
|
|
1378
|
+
if (!text || text.length < 2) continue;
|
|
1379
|
+
if (totalChars + text.length > maxChars) {
|
|
1380
|
+
const remaining = maxChars - totalChars;
|
|
1381
|
+
if (remaining > 20) blocks.push(text.slice(0, remaining));
|
|
1382
|
+
break;
|
|
1383
|
+
}
|
|
1384
|
+
blocks.push(text);
|
|
1385
|
+
totalChars += text.length;
|
|
1386
|
+
}
|
|
1387
|
+
return blocks.join(' ').replace(/\\s+/g, ' ').trim();
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
// Group inputs by their parent form for contextual understanding
|
|
1391
|
+
function discoverForms(root) {
|
|
1392
|
+
const forms = [];
|
|
1393
|
+
for (const form of Array.from(root.querySelectorAll('form')).slice(0, 6)) {
|
|
1394
|
+
const formInputs = Array.from(form.querySelectorAll('input:not([type="hidden"]), select, textarea'));
|
|
1395
|
+
if (!formInputs.length) continue;
|
|
1396
|
+
const submitBtn = form.querySelector('button[type="submit"], input[type="submit"], button:not([type])');
|
|
1397
|
+
forms.push({
|
|
1398
|
+
action: form.getAttribute('action') || undefined,
|
|
1399
|
+
method: (form.getAttribute('method') || 'get').toUpperCase(),
|
|
1400
|
+
fields: formInputs.slice(0, 10).map(function(el) {
|
|
1401
|
+
const tag = el.tagName.toLowerCase();
|
|
1402
|
+
return {
|
|
1403
|
+
name: el.getAttribute('name') || undefined,
|
|
1404
|
+
type: tag === 'select' ? 'select' : tag === 'textarea' ? 'textarea' : (el.type || 'text'),
|
|
1405
|
+
label: (function() {
|
|
1406
|
+
const id = el.id;
|
|
1407
|
+
if (id) {
|
|
1408
|
+
const label = root.querySelector('label[for="' + CSS.escape(id) + '"]');
|
|
1409
|
+
if (label) return textOf(label).slice(0, 60);
|
|
1410
|
+
}
|
|
1411
|
+
const closest = el.closest('label');
|
|
1412
|
+
if (closest) return textOf(closest).slice(0, 60);
|
|
1413
|
+
const ariaLabel = el.getAttribute('aria-label');
|
|
1414
|
+
if (ariaLabel) return ariaLabel.slice(0, 60);
|
|
1415
|
+
return el.getAttribute('placeholder') || undefined;
|
|
1416
|
+
})(),
|
|
1417
|
+
required: el.required || el.getAttribute('aria-required') === 'true' || false,
|
|
1418
|
+
currentValue: ('value' in el && typeof el.value === 'string' && el.value) ? el.value.slice(0, 100) : undefined,
|
|
1419
|
+
};
|
|
1420
|
+
}),
|
|
1421
|
+
submitText: submitBtn ? textOf(submitBtn).slice(0, 40) : undefined,
|
|
1422
|
+
});
|
|
1423
|
+
}
|
|
1424
|
+
return forms;
|
|
1425
|
+
}
|
|
1426
|
+
|
|
1427
|
+
const contentExcerpt = extractContentExcerpt(scopeRoot, 1500);
|
|
1428
|
+
const forms = discoverForms(scopeRoot);
|
|
1429
|
+
|
|
1264
1430
|
return {
|
|
1265
1431
|
url: window.location.href,
|
|
1266
1432
|
title: document.title ?? '',
|
|
1433
|
+
contentExcerpt,
|
|
1267
1434
|
headings,
|
|
1268
1435
|
landmarks,
|
|
1436
|
+
forms: forms.length ? forms : undefined,
|
|
1269
1437
|
alerts,
|
|
1270
1438
|
frames,
|
|
1271
1439
|
crossOriginFrameCount: allFrames.filter((frame) => {
|
|
@@ -1697,6 +1865,9 @@ var ChromeCdpBrowserController = class {
|
|
|
1697
1865
|
if (ariaLabel) entry.ariaLabel = ariaLabel;
|
|
1698
1866
|
const placeholder = el.getAttribute('placeholder');
|
|
1699
1867
|
if (placeholder) entry.placeholder = placeholder;
|
|
1868
|
+
if ('value' in el && typeof el.value === 'string' && el.value) {
|
|
1869
|
+
entry.currentValue = el.value.slice(0, 200);
|
|
1870
|
+
}
|
|
1700
1871
|
|
|
1701
1872
|
results.push(entry);
|
|
1702
1873
|
}
|
|
@@ -1761,27 +1932,122 @@ var ChromeCdpBrowserController = class {
|
|
|
1761
1932
|
* Returns silently on failure — never blocks navigation.
|
|
1762
1933
|
*/
|
|
1763
1934
|
async dismissCookieBanner(targetWsUrl) {
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1935
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
1936
|
+
try {
|
|
1937
|
+
const domResult = await this.dismissViaDom(targetWsUrl);
|
|
1938
|
+
if (domResult.dismissed) return domResult;
|
|
1939
|
+
} catch {}
|
|
1940
|
+
try {
|
|
1941
|
+
const iframeResult = await this.dismissViaIframe(targetWsUrl);
|
|
1942
|
+
if (iframeResult.dismissed) return iframeResult;
|
|
1943
|
+
} catch {}
|
|
1944
|
+
try {
|
|
1945
|
+
const a11yResult = await this.dismissViaA11y(targetWsUrl);
|
|
1946
|
+
if (a11yResult.dismissed) return a11yResult;
|
|
1947
|
+
} catch {}
|
|
1948
|
+
if (attempt === 0) await new Promise((resolve) => setTimeout(resolve, 800));
|
|
1949
|
+
}
|
|
1772
1950
|
return { dismissed: false };
|
|
1773
1951
|
}
|
|
1774
1952
|
async dismissViaDom(targetWsUrl) {
|
|
1775
1953
|
const expression = `(() => {
|
|
1776
|
-
|
|
1954
|
+
function isVisible(el) {
|
|
1955
|
+
if (!el) return false;
|
|
1956
|
+
const style = getComputedStyle(el);
|
|
1957
|
+
return style.display !== 'none' && style.visibility !== 'hidden' && style.opacity !== '0' && el.offsetWidth > 0;
|
|
1958
|
+
}
|
|
1959
|
+
|
|
1960
|
+
// Comprehensive CMP selectors (ordered by specificity/popularity)
|
|
1777
1961
|
const selectors = [
|
|
1962
|
+
// OneTrust
|
|
1778
1963
|
'#onetrust-accept-btn-handler',
|
|
1964
|
+
// Cookiebot
|
|
1779
1965
|
'#CybotCookiebotDialogBodyLevelButtonLevelOptinAllowAll',
|
|
1780
1966
|
'#CybotCookiebotDialogBodyButtonAccept',
|
|
1967
|
+
// Usercentrics
|
|
1968
|
+
'#uc-btn-accept-banner',
|
|
1969
|
+
'[data-testid="uc-accept-all-button"]',
|
|
1970
|
+
'.uc-btn-accept',
|
|
1971
|
+
// Didomi
|
|
1972
|
+
'#didomi-notice-agree-button',
|
|
1973
|
+
'.didomi-button[data-action="accept"]',
|
|
1974
|
+
'.didomi-components-button--color.didomi-button-highlight',
|
|
1975
|
+
// Quantcast / GDPR
|
|
1976
|
+
'.qc-cmp2-summary-buttons button[mode="primary"]',
|
|
1977
|
+
'.qc-cmp-button[data-action="accept"]',
|
|
1978
|
+
'#qc-cmp2-ui button.css-1litn2c',
|
|
1979
|
+
// TrustArc / TrueVault
|
|
1980
|
+
'#truste-consent-button',
|
|
1981
|
+
'.trustarc-agree-btn',
|
|
1982
|
+
'#consent_agree',
|
|
1983
|
+
// Consentmanager (zeit.de, etc.)
|
|
1984
|
+
'#cmpbox .cmpboxbtn.cmpboxbtnyes',
|
|
1985
|
+
'#cmpwelcomebtnyes a',
|
|
1986
|
+
'#cmpbntyestxt',
|
|
1987
|
+
'.cmpboxbtnyes',
|
|
1988
|
+
'a.cmpboxbtn.cmpboxbtnyes',
|
|
1989
|
+
// iubenda
|
|
1990
|
+
'.iubenda-cs-accept-btn',
|
|
1991
|
+
'#iubenda-cs-banner .iubenda-cs-accept-btn',
|
|
1992
|
+
// Klaro
|
|
1993
|
+
'.klaro .cm-btn-accept',
|
|
1994
|
+
'.klaro .cm-btn-success',
|
|
1995
|
+
// Osano
|
|
1996
|
+
'.osano-cm-accept-all',
|
|
1997
|
+
'.osano-cm-button--type_accept',
|
|
1998
|
+
// CookieYes / GDPR Cookie Consent
|
|
1999
|
+
'#cookie_action_close_header',
|
|
2000
|
+
'.cky-btn-accept',
|
|
2001
|
+
'[data-cky-tag="accept-button"]',
|
|
2002
|
+
// Termly
|
|
2003
|
+
'.t-acceptAllButton',
|
|
2004
|
+
'#termly-code-snippet-support .t-acceptAllButton',
|
|
2005
|
+
// Axeptio
|
|
2006
|
+
'#axeptio_btn_acceptAll',
|
|
2007
|
+
'[data-axeptio="accept"]',
|
|
2008
|
+
// Complianz / Really Simple Cookie Consent
|
|
2009
|
+
'.cmplz-accept',
|
|
2010
|
+
'#cmplz-cookiebanner-container .cmplz-accept',
|
|
2011
|
+
// CookieFirst
|
|
2012
|
+
'[data-cookiefirst-action="accept"]',
|
|
2013
|
+
// Cookie Notice / Cookie Law Info
|
|
2014
|
+
'#cookie_action_close_header_reject',
|
|
2015
|
+
'#cn-accept-cookie',
|
|
2016
|
+
// Borlabs Cookie (WordPress)
|
|
2017
|
+
'#CookieBoxSaveButton',
|
|
2018
|
+
'a[data-cookie-accept]',
|
|
2019
|
+
// Piwik PRO
|
|
2020
|
+
'#PiwikPROConsentForm-agree-to-all',
|
|
2021
|
+
'.PiwikPROConsentForm-agree-to-all-button',
|
|
2022
|
+
// Civic Cookie Control
|
|
2023
|
+
'#ccc-recommended-settings',
|
|
2024
|
+
'#ccc-dismiss-button',
|
|
2025
|
+
'.ccc-accept-settings',
|
|
2026
|
+
// Moove GDPR (WordPress)
|
|
2027
|
+
'.moove-gdpr-infobar-allow-all',
|
|
2028
|
+
'#moove_gdpr_cookie_modal .moove-gdpr-modal-allow-all',
|
|
2029
|
+
// CookieHub
|
|
2030
|
+
'.cookiehub-accept',
|
|
2031
|
+
'#cookiehub-accept-btn',
|
|
2032
|
+
'.ch2-btn-primary',
|
|
2033
|
+
// Cookie Script
|
|
2034
|
+
'#cookiescript_accept',
|
|
2035
|
+
'#cookiescript_injected .cookiescript_accept',
|
|
2036
|
+
// HubSpot Cookie Banner
|
|
2037
|
+
'#hs-eu-confirmation-button',
|
|
2038
|
+
'#hs-eu-cookie-confirmation .hs-eu-cta',
|
|
2039
|
+
// MediaVine
|
|
2040
|
+
'[data-name="mediavine-gdpr-cmp"] .pur-accept',
|
|
2041
|
+
// Sirdata
|
|
2042
|
+
'#sd-cmp .sd-cmp-JEaB',
|
|
2043
|
+
// Google Consent Mode / Funding Choices
|
|
2044
|
+
'.fc-cta-consent',
|
|
2045
|
+
'.fc-button-background[aria-label="Consent"]',
|
|
2046
|
+
// Generic selectors
|
|
1781
2047
|
'#accept-cookies', '#acceptCookies',
|
|
1782
2048
|
'#cookie-accept', '#cookieAccept',
|
|
1783
2049
|
'#gdpr-accept', '#consent-accept',
|
|
1784
|
-
'#
|
|
2050
|
+
'#cookies-accept-all', '#accept-all-cookies',
|
|
1785
2051
|
'.cc-accept', '.cc-btn.cc-dismiss',
|
|
1786
2052
|
'.cookie-consent-accept', '.cookie-accept-btn',
|
|
1787
2053
|
'.js-cookie-accept', '.js-accept-cookies',
|
|
@@ -1789,39 +2055,99 @@ var ChromeCdpBrowserController = class {
|
|
|
1789
2055
|
'.gdpr-accept',
|
|
1790
2056
|
'[data-testid="cookie-accept"]',
|
|
1791
2057
|
'[data-action="accept-cookies"]',
|
|
1792
|
-
'[data-
|
|
2058
|
+
'[data-gdpr="accept"]',
|
|
2059
|
+
'[data-consent="accept"]',
|
|
1793
2060
|
];
|
|
1794
2061
|
|
|
1795
|
-
|
|
2062
|
+
// Query selector in main DOM and open shadow roots
|
|
2063
|
+
function deepQuery(sel) {
|
|
1796
2064
|
const el = document.querySelector(sel);
|
|
1797
|
-
if (el
|
|
1798
|
-
|
|
1799
|
-
|
|
2065
|
+
if (el) return el;
|
|
2066
|
+
for (const host of document.querySelectorAll('*')) {
|
|
2067
|
+
if (host.shadowRoot) {
|
|
2068
|
+
const inner = host.shadowRoot.querySelector(sel);
|
|
2069
|
+
if (inner) return inner;
|
|
2070
|
+
}
|
|
1800
2071
|
}
|
|
2072
|
+
return null;
|
|
2073
|
+
}
|
|
2074
|
+
|
|
2075
|
+
for (const sel of selectors) {
|
|
2076
|
+
try {
|
|
2077
|
+
const el = deepQuery(sel);
|
|
2078
|
+
if (el && isVisible(el)) {
|
|
2079
|
+
el.click();
|
|
2080
|
+
return JSON.stringify({ dismissed: true, method: 'selector', detail: sel });
|
|
2081
|
+
}
|
|
2082
|
+
} catch { /* invalid selector — skip */ }
|
|
1801
2083
|
}
|
|
1802
2084
|
|
|
1803
2085
|
// Text-based fallback: find visible buttons with consent text
|
|
2086
|
+
// Patterns are intentionally loose — they use .* instead of strict ^/$ anchors
|
|
1804
2087
|
const patterns = [
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1809
|
-
|
|
1810
|
-
/^got\\s*it$/i,
|
|
2088
|
+
// English
|
|
2089
|
+
/accept\\s*(all)?\\s*(cookies)?/i,
|
|
2090
|
+
/allow\\s*(all)?\\s*(cookies)?/i,
|
|
2091
|
+
/(i\\s+)?agree(\\s+to\\s+all)?/i,
|
|
2092
|
+
/got\\s*it/i,
|
|
1811
2093
|
/^ok$/i,
|
|
1812
2094
|
/^consent$/i,
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
1819
|
-
|
|
2095
|
+
/continue\\s+without\\s+accepting/i,
|
|
2096
|
+
// German
|
|
2097
|
+
/(alle\\s+)?akzeptieren/i,
|
|
2098
|
+
/alle\\s+cookies\\s+akzeptieren/i,
|
|
2099
|
+
/(alle\\s+)?zustimmen/i,
|
|
2100
|
+
/einwilligen/i,
|
|
2101
|
+
/einverstanden/i,
|
|
2102
|
+
/alles\\s+erlauben/i,
|
|
2103
|
+
/alles\\s+akzeptieren/i,
|
|
2104
|
+
/geht\\s+klar/i,
|
|
2105
|
+
/verstanden/i,
|
|
2106
|
+
// French
|
|
2107
|
+
/(j')?accepte(r)?(\\s+tout)?/i,
|
|
2108
|
+
/(tout\\s+)?accepter/i,
|
|
2109
|
+
/autoriser/i,
|
|
2110
|
+
// Spanish
|
|
2111
|
+
/aceptar(\\s+todo)?/i,
|
|
2112
|
+
/aceptar\\s+cookies/i,
|
|
2113
|
+
// Italian
|
|
2114
|
+
/accett(a|o)(\\s+tutt[oi])?/i,
|
|
2115
|
+
/accetta\\s+cookies/i,
|
|
2116
|
+
// Dutch
|
|
2117
|
+
/(alle\\s+)?accepteren/i,
|
|
2118
|
+
/alles\\s+accepteren/i,
|
|
2119
|
+
// Portuguese
|
|
2120
|
+
/aceitar(\\s+tudo)?/i,
|
|
2121
|
+
/aceitar\\s+cookies/i,
|
|
2122
|
+
// Polish
|
|
2123
|
+
/akceptuj(\\s+wszystk[oi]e)?/i,
|
|
2124
|
+
/zgadzam\\s+si/i,
|
|
2125
|
+
// Swedish
|
|
2126
|
+
/acceptera(\\s+alla)?/i,
|
|
2127
|
+
// Danish
|
|
2128
|
+
/accepter(\\s+alle)?/i,
|
|
2129
|
+
// Norwegian
|
|
2130
|
+
/aksepter(\\s+alle)?/i,
|
|
1820
2131
|
];
|
|
2132
|
+
// Collect candidates from main DOM and open shadow roots
|
|
2133
|
+
function collectButtons(root) {
|
|
2134
|
+
const results = [];
|
|
2135
|
+
for (const tag of ['button', 'a[role="button"]', '[role="button"]', 'div[role="button"]', 'span[role="button"]']) {
|
|
2136
|
+
results.push(...root.querySelectorAll(tag));
|
|
2137
|
+
}
|
|
2138
|
+
// Traverse open shadow roots for CMPs that use web components
|
|
2139
|
+
for (const el of root.querySelectorAll('*')) {
|
|
2140
|
+
if (el.shadowRoot) {
|
|
2141
|
+
results.push(...collectButtons(el.shadowRoot));
|
|
2142
|
+
}
|
|
2143
|
+
}
|
|
2144
|
+
return results;
|
|
2145
|
+
}
|
|
2146
|
+
const candidates = collectButtons(document);
|
|
1821
2147
|
for (const el of candidates) {
|
|
1822
|
-
if (!el
|
|
2148
|
+
if (!isVisible(el)) continue;
|
|
1823
2149
|
const text = (el.textContent || '').trim();
|
|
1824
|
-
if (text.length >
|
|
2150
|
+
if (text.length > 60) continue;
|
|
1825
2151
|
for (const pat of patterns) {
|
|
1826
2152
|
if (pat.test(text)) {
|
|
1827
2153
|
el.click();
|
|
@@ -1830,6 +2156,90 @@ var ChromeCdpBrowserController = class {
|
|
|
1830
2156
|
}
|
|
1831
2157
|
}
|
|
1832
2158
|
|
|
2159
|
+
return JSON.stringify({ dismissed: false });
|
|
2160
|
+
})()`;
|
|
2161
|
+
return await this.withRetry(targetWsUrl, async (conn) => {
|
|
2162
|
+
await this.ensureEnabled(targetWsUrl);
|
|
2163
|
+
const raw = (await conn.send("Runtime.evaluate", {
|
|
2164
|
+
expression,
|
|
2165
|
+
returnByValue: true
|
|
2166
|
+
})).result.value ?? "{\"dismissed\":false}";
|
|
2167
|
+
try {
|
|
2168
|
+
return JSON.parse(raw);
|
|
2169
|
+
} catch {
|
|
2170
|
+
return { dismissed: false };
|
|
2171
|
+
}
|
|
2172
|
+
});
|
|
2173
|
+
}
|
|
2174
|
+
/**
|
|
2175
|
+
* Handle iframe-based consent banners (Sourcepoint, some OneTrust configurations, etc.)
|
|
2176
|
+
* These banners render inside an iframe that needs to be accessed separately.
|
|
2177
|
+
*/
|
|
2178
|
+
async dismissViaIframe(targetWsUrl) {
|
|
2179
|
+
const expression = `(() => {
|
|
2180
|
+
// Known consent iframe selectors
|
|
2181
|
+
const iframeSelectors = [
|
|
2182
|
+
'iframe[id^="sp_message_iframe"]', // Sourcepoint (dynamic ID suffix)
|
|
2183
|
+
'#sp_message_iframe', // Sourcepoint (static ID)
|
|
2184
|
+
'iframe[id*="sp_message"]', // Sourcepoint variants
|
|
2185
|
+
'div[id^="sp_message_container"] iframe', // Sourcepoint container with iframe
|
|
2186
|
+
'iframe[title*="cookie"]', // Generic cookie iframes
|
|
2187
|
+
'iframe[title*="consent"]', // Generic consent iframes
|
|
2188
|
+
'iframe[title*="Cookie"]',
|
|
2189
|
+
'iframe[title*="Consent"]',
|
|
2190
|
+
'iframe[title*="Privacy"]',
|
|
2191
|
+
'iframe[title*="privacy"]',
|
|
2192
|
+
'iframe[title*="GDPR"]',
|
|
2193
|
+
'iframe[src*="consent"]',
|
|
2194
|
+
'iframe[src*="cookie"]',
|
|
2195
|
+
'iframe[src*="privacy"]',
|
|
2196
|
+
'iframe[src*="sourcepoint"]',
|
|
2197
|
+
'iframe[src*="consentmanager"]',
|
|
2198
|
+
];
|
|
2199
|
+
|
|
2200
|
+
for (const sel of iframeSelectors) {
|
|
2201
|
+
try {
|
|
2202
|
+
const iframe = document.querySelector(sel);
|
|
2203
|
+
if (!iframe) continue;
|
|
2204
|
+
const doc = iframe.contentDocument || iframe.contentWindow?.document;
|
|
2205
|
+
if (!doc) continue;
|
|
2206
|
+
|
|
2207
|
+
// Try known selectors inside the iframe
|
|
2208
|
+
const innerSelectors = [
|
|
2209
|
+
// Sourcepoint specific
|
|
2210
|
+
'button.sp_choice_type_11', // Accept all
|
|
2211
|
+
'button.sp_choice_type_ACCEPT_ALL', // Accept all variant
|
|
2212
|
+
'button.sp_choice_type_SAVE_AND_EXIT', // Save & exit
|
|
2213
|
+
'.priv-save-btn', // Sourcepoint frame
|
|
2214
|
+
'.message-button', // Generic Sourcepoint
|
|
2215
|
+
// Title-based
|
|
2216
|
+
'button[title*="Accept"]', 'button[title*="accept"]',
|
|
2217
|
+
'button[title*="Agree"]', 'button[title*="agree"]',
|
|
2218
|
+
'button[title*="Allow"]',
|
|
2219
|
+
'button[title*="Akzeptieren"]', 'button[title*="akzeptieren"]',
|
|
2220
|
+
'button[title*="Zustimmen"]',
|
|
2221
|
+
];
|
|
2222
|
+
for (const innerSel of innerSelectors) {
|
|
2223
|
+
const btn = doc.querySelector(innerSel);
|
|
2224
|
+
if (btn && btn.offsetWidth > 0) {
|
|
2225
|
+
btn.click();
|
|
2226
|
+
return JSON.stringify({ dismissed: true, method: 'iframe', detail: sel + ' > ' + innerSel });
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
|
|
2230
|
+
// Text-based fallback inside iframe
|
|
2231
|
+
const buttons = doc.querySelectorAll('button, [role="button"]');
|
|
2232
|
+
for (const btn of buttons) {
|
|
2233
|
+
const text = (btn.textContent || '').trim();
|
|
2234
|
+
if (text.length > 60) continue;
|
|
2235
|
+
if (/accept|agree|allow|akzeptieren|zustimmen|einwilligen|accepter|aceptar/i.test(text)) {
|
|
2236
|
+
btn.click();
|
|
2237
|
+
return JSON.stringify({ dismissed: true, method: 'iframe-text', detail: text });
|
|
2238
|
+
}
|
|
2239
|
+
}
|
|
2240
|
+
} catch { /* cross-origin iframe — skip */ }
|
|
2241
|
+
}
|
|
2242
|
+
|
|
1833
2243
|
return JSON.stringify({ dismissed: false });
|
|
1834
2244
|
})()`;
|
|
1835
2245
|
return await this.withRetry(targetWsUrl, async (conn) => {
|
|
@@ -1847,32 +2257,60 @@ var ChromeCdpBrowserController = class {
|
|
|
1847
2257
|
}
|
|
1848
2258
|
/**
|
|
1849
2259
|
* Use the CDP Accessibility tree to find and click consent buttons.
|
|
1850
|
-
* This works even when consent banners use shadow DOM
|
|
2260
|
+
* This works even when consent banners use shadow DOM or obfuscated class names.
|
|
2261
|
+
* Also queries cross-origin iframe frames via Page.getFrameTree for Sourcepoint etc.
|
|
1851
2262
|
*/
|
|
1852
2263
|
async dismissViaA11y(targetWsUrl) {
|
|
1853
2264
|
return await this.withRetry(targetWsUrl, async (conn) => {
|
|
1854
2265
|
await conn.send("Accessibility.enable");
|
|
2266
|
+
await conn.send("Page.enable");
|
|
1855
2267
|
const { nodes } = await conn.send("Accessibility.getFullAXTree");
|
|
2268
|
+
const allNodes = [...nodes];
|
|
2269
|
+
try {
|
|
2270
|
+
const { frameTree } = await conn.send("Page.getFrameTree");
|
|
2271
|
+
for (const child of frameTree.childFrames ?? []) {
|
|
2272
|
+
const url = child.frame.url ?? "";
|
|
2273
|
+
if (/consent|cookie|privacy|sourcepoint|didomi|cmp|gdpr|trustarc|consentmanager|sp_message|onetrust|usercentrics|consent-cdn/i.test(url) || url === "about:blank") try {
|
|
2274
|
+
const frameResult = await conn.send("Accessibility.getFullAXTree", { frameId: child.frame.id });
|
|
2275
|
+
allNodes.push(...frameResult.nodes);
|
|
2276
|
+
} catch {}
|
|
2277
|
+
}
|
|
2278
|
+
} catch {}
|
|
1856
2279
|
const consentPatterns = [
|
|
1857
|
-
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
2280
|
+
/accept\s*(all)?\s*(cookies)?/i,
|
|
2281
|
+
/allow\s*(all)?\s*(cookies)?/i,
|
|
2282
|
+
/(i\s+)?agree(\s+to\s+all)?/i,
|
|
2283
|
+
/got\s*it/i,
|
|
1861
2284
|
/^ok$/i,
|
|
1862
2285
|
/^consent$/i,
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
1870
|
-
|
|
1871
|
-
|
|
1872
|
-
|
|
2286
|
+
/(alle\s+)?akzeptieren/i,
|
|
2287
|
+
/alle\s+cookies\s+akzeptieren/i,
|
|
2288
|
+
/(alle\s+)?zustimmen/i,
|
|
2289
|
+
/einwilligen/i,
|
|
2290
|
+
/einverstanden/i,
|
|
2291
|
+
/alles\s+erlauben/i,
|
|
2292
|
+
/alles\s+akzeptieren/i,
|
|
2293
|
+
/geht\s+klar/i,
|
|
2294
|
+
/verstanden/i,
|
|
2295
|
+
/(j')?accepte(r)?(\s+tout)?/i,
|
|
2296
|
+
/(tout\s+)?accepter/i,
|
|
2297
|
+
/autoriser/i,
|
|
2298
|
+
/aceptar(\s+todo)?/i,
|
|
2299
|
+
/aceptar\s+cookies/i,
|
|
2300
|
+
/accett(a|o)(\s+tutt[oi])?/i,
|
|
2301
|
+
/accetta\s+cookies/i,
|
|
2302
|
+
/(alle\s+)?accepteren/i,
|
|
2303
|
+
/alles\s+accepteren/i,
|
|
2304
|
+
/aceitar(\s+tudo)?/i,
|
|
2305
|
+
/aceitar\s+cookies/i,
|
|
2306
|
+
/akceptuj(\s+wszystk[oi]e)?/i,
|
|
2307
|
+
/zgadzam\s+si/i,
|
|
2308
|
+
/acceptera(\s+alla)?/i,
|
|
2309
|
+
/accepter(\s+alle)?/i,
|
|
2310
|
+
/aksepter(\s+alle)?/i
|
|
1873
2311
|
];
|
|
1874
2312
|
const candidates = [];
|
|
1875
|
-
for (const node of
|
|
2313
|
+
for (const node of allNodes) {
|
|
1876
2314
|
if (node.ignored) continue;
|
|
1877
2315
|
const role = node.role?.value;
|
|
1878
2316
|
if (role !== "button" && role !== "link") continue;
|
|
@@ -1978,6 +2416,15 @@ var MockBrowserController = class {
|
|
|
1978
2416
|
if (payload.action === "dialog") return payload.text === "dismiss" ? "dismissed confirm: mock dialog" : "accepted confirm: mock dialog";
|
|
1979
2417
|
return `interacted:${payload.action}`;
|
|
1980
2418
|
}
|
|
2419
|
+
async screenshot(_targetWsUrl, options) {
|
|
2420
|
+
return {
|
|
2421
|
+
data: "bW9jaw==",
|
|
2422
|
+
mimeType: (options?.format ?? "jpeg") === "png" ? "image/png" : "image/jpeg"
|
|
2423
|
+
};
|
|
2424
|
+
}
|
|
2425
|
+
async getCurrentUrl(cdpUrl) {
|
|
2426
|
+
return this.pages.get(cdpUrl)?.url ?? "about:blank";
|
|
2427
|
+
}
|
|
1981
2428
|
async getContent(cdpUrl, options) {
|
|
1982
2429
|
const page = this.pages.get(cdpUrl);
|
|
1983
2430
|
if (!page) throw new Error("mock page missing");
|
|
@@ -2253,6 +2700,14 @@ var SessionManager = class {
|
|
|
2253
2700
|
const record = await this.ensureSession(sessionId);
|
|
2254
2701
|
return await this.browser.getInteractiveElements(record.targetWsUrl, options);
|
|
2255
2702
|
}
|
|
2703
|
+
async screenshot(sessionId, options) {
|
|
2704
|
+
const record = await this.ensureSession(sessionId);
|
|
2705
|
+
return await this.browser.screenshot(record.targetWsUrl, options);
|
|
2706
|
+
}
|
|
2707
|
+
async getCurrentUrl(sessionId) {
|
|
2708
|
+
const record = await this.ensureSession(sessionId);
|
|
2709
|
+
return await this.browser.getCurrentUrl(record.targetWsUrl);
|
|
2710
|
+
}
|
|
2256
2711
|
async dismissCookieBanner(sessionId) {
|
|
2257
2712
|
const record = await this.ensureSession(sessionId);
|
|
2258
2713
|
const result = await this.browser.dismissCookieBanner(record.targetWsUrl);
|
|
@@ -2528,6 +2983,12 @@ var ControlApi = class {
|
|
|
2528
2983
|
async dismissCookieBanner(sessionId) {
|
|
2529
2984
|
return await this.sessions.dismissCookieBanner(sessionId);
|
|
2530
2985
|
}
|
|
2986
|
+
async screenshot(sessionId, options) {
|
|
2987
|
+
return await this.sessions.screenshot(sessionId, options);
|
|
2988
|
+
}
|
|
2989
|
+
async getCurrentUrl(sessionId) {
|
|
2990
|
+
return await this.sessions.getCurrentUrl(sessionId);
|
|
2991
|
+
}
|
|
2531
2992
|
listEvents(sessionId, limit = 100) {
|
|
2532
2993
|
return { events: this.eventStore.list(sessionId, limit) };
|
|
2533
2994
|
}
|
|
@@ -3566,8 +4027,9 @@ var AgenticBrowserCore = class {
|
|
|
3566
4027
|
return this.api.getSession(sessionId);
|
|
3567
4028
|
}
|
|
3568
4029
|
async runCommand(input) {
|
|
4030
|
+
const commandId = input.commandId ?? `${input.type}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
3569
4031
|
return await this.api.executeCommand(input.sessionId, {
|
|
3570
|
-
commandId
|
|
4032
|
+
commandId,
|
|
3571
4033
|
type: input.type,
|
|
3572
4034
|
payload: input.payload
|
|
3573
4035
|
});
|
|
@@ -3590,6 +4052,12 @@ var AgenticBrowserCore = class {
|
|
|
3590
4052
|
async dismissCookieBanner(sessionId) {
|
|
3591
4053
|
return await this.api.dismissCookieBanner(sessionId);
|
|
3592
4054
|
}
|
|
4055
|
+
async screenshot(sessionId, options) {
|
|
4056
|
+
return await this.api.screenshot(sessionId, options);
|
|
4057
|
+
}
|
|
4058
|
+
async getCurrentUrl(sessionId) {
|
|
4059
|
+
return await this.api.getCurrentUrl(sessionId);
|
|
4060
|
+
}
|
|
3593
4061
|
async restartSession(sessionId) {
|
|
3594
4062
|
return await this.api.restartSession(sessionId);
|
|
3595
4063
|
}
|