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.
@@ -224,19 +224,78 @@ async function createTarget(cdpUrl, url = "about:blank") {
224
224
  } catch {}
225
225
  return await ensurePageWebSocketUrl(cdpUrl);
226
226
  }
227
- /** Verify the page is ready and optionally set a custom user-agent, using a single connection. */
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
- if (userAgent) enables.push(conn.send("Network.enable"));
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
- try {
1765
- const domResult = await this.dismissViaDom(targetWsUrl);
1766
- if (domResult.dismissed) return domResult;
1767
- } catch {}
1768
- try {
1769
- const a11yResult = await this.dismissViaA11y(targetWsUrl);
1770
- if (a11yResult.dismissed) return a11yResult;
1771
- } catch {}
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
- // Common cookie-consent button selectors (ordered by specificity)
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
- '#uc-btn-accept-banner',
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-cookiefirst-action="accept"]',
2058
+ '[data-gdpr="accept"]',
2059
+ '[data-consent="accept"]',
1793
2060
  ];
1794
2061
 
1795
- for (const sel of selectors) {
2062
+ // Query selector in main DOM and open shadow roots
2063
+ function deepQuery(sel) {
1796
2064
  const el = document.querySelector(sel);
1797
- if (el && el.offsetParent !== null) {
1798
- el.click();
1799
- return JSON.stringify({ dismissed: true, method: 'selector', detail: sel });
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
- /^accept\\s*(all)?\\s*(cookies)?$/i,
1806
- /^(alle\\s+)?akzeptieren$/i,
1807
- /^(i\\s+)?agree$/i,
1808
- /^allow\\s*(all)?\\s*(cookies)?$/i,
1809
- /^(alle\\s+)?zustimmen$/i,
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
- /^(j')?accepte(r)?$/i,
1814
- /^(tout\\s+)?accepter$/i,
1815
- ];
1816
- const candidates = [
1817
- ...document.querySelectorAll('button'),
1818
- ...document.querySelectorAll('a[role="button"]'),
1819
- ...document.querySelectorAll('[role="button"]'),
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.offsetParent) continue;
2148
+ if (!isVisible(el)) continue;
1823
2149
  const text = (el.textContent || '').trim();
1824
- if (text.length > 40) continue;
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, iframes, or obfuscated class names.
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
- /^accept\s*(all)?\s*(cookies)?$/i,
1858
- /^(i\s+)?agree(\s+to\s+all)?$/i,
1859
- /^allow\s*(all)?\s*(cookies)?$/i,
1860
- /^got\s*it$/i,
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
- /^(alle\s+)?akzeptieren$/i,
1864
- /^(alle\s+)?zustimmen$/i,
1865
- /^einverstanden$/i,
1866
- /^(j')?accepte(r)?(\s+tout)?$/i,
1867
- /^(tout\s+)?accepter$/i,
1868
- /^aceptar(\s+todo)?$/i,
1869
- /^accetta(\s+tutto)?$/i,
1870
- /^accepteren$/i,
1871
- /^alle\s+accepteren$/i,
1872
- /^aceitar(\s+tudo)?$/i
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 nodes) {
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: input.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
  }