browserclaw 0.11.2 → 0.11.4
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/index.cjs +308 -311
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +34 -28
- package/dist/index.d.ts +34 -28
- package/dist/index.js +308 -312
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
package/dist/index.cjs
CHANGED
|
@@ -1319,7 +1319,10 @@ function normalizeCdpHttpBaseForJsonEndpoints(cdpUrl) {
|
|
|
1319
1319
|
url.pathname = url.pathname.replace(/\/cdp$/, "");
|
|
1320
1320
|
return url.toString().replace(/\/$/, "");
|
|
1321
1321
|
} catch {
|
|
1322
|
-
|
|
1322
|
+
let normalized = cdpUrl.replace(/^ws:/, "http:").replace(/^wss:/, "https:");
|
|
1323
|
+
const dtIdx = normalized.indexOf("/devtools/browser/");
|
|
1324
|
+
if (dtIdx >= 0) normalized = normalized.slice(0, dtIdx);
|
|
1325
|
+
return normalized.replace(/\/cdp$/, "").replace(/\/$/, "");
|
|
1323
1326
|
}
|
|
1324
1327
|
}
|
|
1325
1328
|
function appendCdpPath(cdpUrl, cdpPath) {
|
|
@@ -1782,205 +1785,25 @@ var STEALTH_SCRIPT = `(function() {
|
|
|
1782
1785
|
});
|
|
1783
1786
|
})()`;
|
|
1784
1787
|
|
|
1785
|
-
// src/
|
|
1786
|
-
var
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
this.name = "BrowserTabNotFoundError";
|
|
1790
|
-
}
|
|
1791
|
-
};
|
|
1792
|
-
async function fetchJsonForCdp(url, timeoutMs) {
|
|
1793
|
-
const ctrl = new AbortController();
|
|
1794
|
-
const t = setTimeout(() => {
|
|
1795
|
-
ctrl.abort();
|
|
1796
|
-
}, timeoutMs);
|
|
1797
|
-
try {
|
|
1798
|
-
const res = await fetch(url, { signal: ctrl.signal });
|
|
1799
|
-
if (!res.ok) return null;
|
|
1800
|
-
return await res.json();
|
|
1801
|
-
} catch {
|
|
1802
|
-
return null;
|
|
1803
|
-
} finally {
|
|
1804
|
-
clearTimeout(t);
|
|
1805
|
-
}
|
|
1806
|
-
}
|
|
1807
|
-
function appendCdpPath2(cdpUrl, cdpPath) {
|
|
1808
|
-
try {
|
|
1809
|
-
const url = new URL(cdpUrl);
|
|
1810
|
-
url.pathname = `${url.pathname.replace(/\/$/, "")}${cdpPath.startsWith("/") ? cdpPath : `/${cdpPath}`}`;
|
|
1811
|
-
return url.toString();
|
|
1812
|
-
} catch {
|
|
1813
|
-
return `${cdpUrl.replace(/\/$/, "")}${cdpPath}`;
|
|
1814
|
-
}
|
|
1815
|
-
}
|
|
1816
|
-
async function withPlaywrightPageCdpSession(page, fn) {
|
|
1817
|
-
const CDP_SESSION_TIMEOUT_MS = 1e4;
|
|
1818
|
-
const session = await Promise.race([
|
|
1819
|
-
page.context().newCDPSession(page),
|
|
1820
|
-
new Promise((_, reject) => {
|
|
1821
|
-
setTimeout(() => {
|
|
1822
|
-
reject(new Error("newCDPSession timed out after 10s"));
|
|
1823
|
-
}, CDP_SESSION_TIMEOUT_MS);
|
|
1824
|
-
})
|
|
1825
|
-
]);
|
|
1826
|
-
try {
|
|
1827
|
-
return await fn(session);
|
|
1828
|
-
} finally {
|
|
1829
|
-
await session.detach().catch(() => {
|
|
1830
|
-
});
|
|
1831
|
-
}
|
|
1832
|
-
}
|
|
1833
|
-
async function withPageScopedCdpClient(opts) {
|
|
1834
|
-
return await withPlaywrightPageCdpSession(opts.page, async (session) => {
|
|
1835
|
-
return await opts.fn((method, params) => session.send(method, params));
|
|
1836
|
-
});
|
|
1837
|
-
}
|
|
1838
|
-
var LOOPBACK_ENTRIES = "localhost,127.0.0.1,[::1]";
|
|
1839
|
-
function noProxyAlreadyCoversLocalhost() {
|
|
1840
|
-
const current = process.env.NO_PROXY ?? process.env.no_proxy ?? "";
|
|
1841
|
-
return current.includes("localhost") && current.includes("127.0.0.1") && current.includes("[::1]");
|
|
1842
|
-
}
|
|
1843
|
-
function isLoopbackCdpUrl(url) {
|
|
1844
|
-
try {
|
|
1845
|
-
return isLoopbackHost(new URL(url).hostname);
|
|
1846
|
-
} catch {
|
|
1847
|
-
return false;
|
|
1848
|
-
}
|
|
1849
|
-
}
|
|
1850
|
-
var NoProxyLeaseManager = class {
|
|
1851
|
-
leaseCount = 0;
|
|
1852
|
-
snapshot = null;
|
|
1853
|
-
acquire(url) {
|
|
1854
|
-
if (!isLoopbackCdpUrl(url) || !hasProxyEnvConfigured()) return null;
|
|
1855
|
-
if (this.leaseCount === 0 && !noProxyAlreadyCoversLocalhost()) {
|
|
1856
|
-
const noProxy = process.env.NO_PROXY;
|
|
1857
|
-
const noProxyLower = process.env.no_proxy;
|
|
1858
|
-
const current = noProxy ?? noProxyLower ?? "";
|
|
1859
|
-
const applied = current ? `${current},${LOOPBACK_ENTRIES}` : LOOPBACK_ENTRIES;
|
|
1860
|
-
process.env.NO_PROXY = applied;
|
|
1861
|
-
process.env.no_proxy = applied;
|
|
1862
|
-
this.snapshot = { noProxy, noProxyLower, applied };
|
|
1863
|
-
}
|
|
1864
|
-
this.leaseCount += 1;
|
|
1865
|
-
let released = false;
|
|
1866
|
-
return () => {
|
|
1867
|
-
if (released) return;
|
|
1868
|
-
released = true;
|
|
1869
|
-
this.release();
|
|
1870
|
-
};
|
|
1871
|
-
}
|
|
1872
|
-
release() {
|
|
1873
|
-
if (this.leaseCount <= 0) return;
|
|
1874
|
-
this.leaseCount -= 1;
|
|
1875
|
-
if (this.leaseCount > 0 || !this.snapshot) return;
|
|
1876
|
-
const { noProxy, noProxyLower, applied } = this.snapshot;
|
|
1877
|
-
const currentNoProxy = process.env.NO_PROXY;
|
|
1878
|
-
const currentNoProxyLower = process.env.no_proxy;
|
|
1879
|
-
if (currentNoProxy === applied && (currentNoProxyLower === applied || currentNoProxyLower === void 0)) {
|
|
1880
|
-
if (noProxy !== void 0) process.env.NO_PROXY = noProxy;
|
|
1881
|
-
else delete process.env.NO_PROXY;
|
|
1882
|
-
if (noProxyLower !== void 0) process.env.no_proxy = noProxyLower;
|
|
1883
|
-
else delete process.env.no_proxy;
|
|
1884
|
-
}
|
|
1885
|
-
this.snapshot = null;
|
|
1886
|
-
}
|
|
1887
|
-
};
|
|
1888
|
-
var noProxyLeaseManager = new NoProxyLeaseManager();
|
|
1889
|
-
async function withNoProxyForCdpUrl(url, fn) {
|
|
1890
|
-
const release = noProxyLeaseManager.acquire(url);
|
|
1891
|
-
try {
|
|
1892
|
-
return await fn();
|
|
1893
|
-
} finally {
|
|
1894
|
-
release?.();
|
|
1895
|
-
}
|
|
1896
|
-
}
|
|
1897
|
-
new http__default.default.Agent();
|
|
1898
|
-
new https__default.default.Agent();
|
|
1899
|
-
function getHeadersWithAuth(endpoint, baseHeaders = {}) {
|
|
1900
|
-
const headers = { ...baseHeaders };
|
|
1901
|
-
try {
|
|
1902
|
-
const parsed = new URL(endpoint);
|
|
1903
|
-
if (Object.keys(headers).some((k) => k.toLowerCase() === "authorization")) return headers;
|
|
1904
|
-
if (parsed.username || parsed.password) {
|
|
1905
|
-
const credentials = Buffer.from(
|
|
1906
|
-
`${decodeURIComponent(parsed.username)}:${decodeURIComponent(parsed.password)}`
|
|
1907
|
-
).toString("base64");
|
|
1908
|
-
headers.Authorization = `Basic ${credentials}`;
|
|
1909
|
-
}
|
|
1910
|
-
} catch {
|
|
1911
|
-
}
|
|
1912
|
-
return headers;
|
|
1913
|
-
}
|
|
1914
|
-
var cachedByCdpUrl = /* @__PURE__ */ new Map();
|
|
1915
|
-
var connectingByCdpUrl = /* @__PURE__ */ new Map();
|
|
1788
|
+
// src/page-utils.ts
|
|
1789
|
+
var MAX_CONSOLE_MESSAGES = 500;
|
|
1790
|
+
var MAX_PAGE_ERRORS = 200;
|
|
1791
|
+
var MAX_NETWORK_REQUESTS = 500;
|
|
1916
1792
|
var pageStates = /* @__PURE__ */ new WeakMap();
|
|
1917
1793
|
var contextStates = /* @__PURE__ */ new WeakMap();
|
|
1918
1794
|
var observedContexts = /* @__PURE__ */ new WeakSet();
|
|
1919
1795
|
var observedPages = /* @__PURE__ */ new WeakSet();
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
function bumpUploadArmId() {
|
|
1924
|
-
nextUploadArmId += 1;
|
|
1925
|
-
return nextUploadArmId;
|
|
1926
|
-
}
|
|
1927
|
-
function bumpDialogArmId() {
|
|
1928
|
-
nextDialogArmId += 1;
|
|
1929
|
-
return nextDialogArmId;
|
|
1930
|
-
}
|
|
1931
|
-
function bumpDownloadArmId() {
|
|
1932
|
-
nextDownloadArmId += 1;
|
|
1933
|
-
return nextDownloadArmId;
|
|
1934
|
-
}
|
|
1935
|
-
var BlockedBrowserTargetError = class extends Error {
|
|
1936
|
-
constructor() {
|
|
1937
|
-
super("Browser target is unavailable after SSRF policy blocked its navigation.");
|
|
1938
|
-
this.name = "BlockedBrowserTargetError";
|
|
1939
|
-
}
|
|
1940
|
-
};
|
|
1941
|
-
var blockedTargetsByCdpUrl = /* @__PURE__ */ new Set();
|
|
1942
|
-
var blockedPageRefsByCdpUrl = /* @__PURE__ */ new Map();
|
|
1943
|
-
function blockedTargetKey(cdpUrl, targetId) {
|
|
1944
|
-
return `${normalizeCdpUrl(cdpUrl)}::${targetId}`;
|
|
1796
|
+
function bumpUploadArmId(state) {
|
|
1797
|
+
state.nextArmIdUpload += 1;
|
|
1798
|
+
return state.nextArmIdUpload;
|
|
1945
1799
|
}
|
|
1946
|
-
function
|
|
1947
|
-
|
|
1948
|
-
|
|
1949
|
-
return blockedTargetsByCdpUrl.has(blockedTargetKey(cdpUrl, normalized));
|
|
1800
|
+
function bumpDialogArmId(state) {
|
|
1801
|
+
state.nextArmIdDialog += 1;
|
|
1802
|
+
return state.nextArmIdDialog;
|
|
1950
1803
|
}
|
|
1951
|
-
function
|
|
1952
|
-
|
|
1953
|
-
|
|
1954
|
-
blockedTargetsByCdpUrl.add(blockedTargetKey(cdpUrl, normalized));
|
|
1955
|
-
}
|
|
1956
|
-
function clearBlockedTarget(cdpUrl, targetId) {
|
|
1957
|
-
const normalized = targetId?.trim() ?? "";
|
|
1958
|
-
if (normalized === "") return;
|
|
1959
|
-
blockedTargetsByCdpUrl.delete(blockedTargetKey(cdpUrl, normalized));
|
|
1960
|
-
}
|
|
1961
|
-
function hasBlockedTargetsForCdpUrl(cdpUrl) {
|
|
1962
|
-
const prefix = `${normalizeCdpUrl(cdpUrl)}::`;
|
|
1963
|
-
for (const key of blockedTargetsByCdpUrl) {
|
|
1964
|
-
if (key.startsWith(prefix)) return true;
|
|
1965
|
-
}
|
|
1966
|
-
return false;
|
|
1967
|
-
}
|
|
1968
|
-
function blockedPageRefsForCdpUrl(cdpUrl) {
|
|
1969
|
-
const normalized = normalizeCdpUrl(cdpUrl);
|
|
1970
|
-
const existing = blockedPageRefsByCdpUrl.get(normalized);
|
|
1971
|
-
if (existing) return existing;
|
|
1972
|
-
const created = /* @__PURE__ */ new WeakSet();
|
|
1973
|
-
blockedPageRefsByCdpUrl.set(normalized, created);
|
|
1974
|
-
return created;
|
|
1975
|
-
}
|
|
1976
|
-
function isBlockedPageRef(cdpUrl, page) {
|
|
1977
|
-
return blockedPageRefsByCdpUrl.get(normalizeCdpUrl(cdpUrl))?.has(page) ?? false;
|
|
1978
|
-
}
|
|
1979
|
-
function markPageRefBlocked(cdpUrl, page) {
|
|
1980
|
-
blockedPageRefsForCdpUrl(cdpUrl).add(page);
|
|
1981
|
-
}
|
|
1982
|
-
function clearBlockedPageRef(cdpUrl, page) {
|
|
1983
|
-
blockedPageRefsByCdpUrl.get(normalizeCdpUrl(cdpUrl))?.delete(page);
|
|
1804
|
+
function bumpDownloadArmId(state) {
|
|
1805
|
+
state.nextArmIdDownload += 1;
|
|
1806
|
+
return state.nextArmIdDownload;
|
|
1984
1807
|
}
|
|
1985
1808
|
function ensureContextState(context) {
|
|
1986
1809
|
const existing = contextStates.get(context);
|
|
@@ -1989,16 +1812,8 @@ function ensureContextState(context) {
|
|
|
1989
1812
|
contextStates.set(context, state);
|
|
1990
1813
|
return state;
|
|
1991
1814
|
}
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
var MAX_CONSOLE_MESSAGES = 500;
|
|
1995
|
-
var MAX_PAGE_ERRORS = 200;
|
|
1996
|
-
var MAX_NETWORK_REQUESTS = 500;
|
|
1997
|
-
function normalizeCdpUrl(raw) {
|
|
1998
|
-
return raw.replace(/\/$/, "");
|
|
1999
|
-
}
|
|
2000
|
-
function roleRefsKey(cdpUrl, targetId) {
|
|
2001
|
-
return `${normalizeCdpUrl(cdpUrl)}::${targetId}`;
|
|
1815
|
+
function getPageState(page) {
|
|
1816
|
+
return pageStates.get(page);
|
|
2002
1817
|
}
|
|
2003
1818
|
function findNetworkRequestById(state, id) {
|
|
2004
1819
|
for (let i = state.requests.length - 1; i >= 0; i--) {
|
|
@@ -2018,7 +1833,10 @@ function ensurePageState(page) {
|
|
|
2018
1833
|
nextRequestId: 0,
|
|
2019
1834
|
armIdUpload: 0,
|
|
2020
1835
|
armIdDialog: 0,
|
|
2021
|
-
armIdDownload: 0
|
|
1836
|
+
armIdDownload: 0,
|
|
1837
|
+
nextArmIdUpload: 0,
|
|
1838
|
+
nextArmIdDialog: 0,
|
|
1839
|
+
nextArmIdDownload: 0
|
|
2022
1840
|
};
|
|
2023
1841
|
pageStates.set(page, state);
|
|
2024
1842
|
if (!observedPages.has(page)) {
|
|
@@ -2121,10 +1939,9 @@ function ensurePageState(page) {
|
|
|
2121
1939
|
}
|
|
2122
1940
|
return state;
|
|
2123
1941
|
}
|
|
2124
|
-
|
|
2125
|
-
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
1942
|
+
function setDialogHandlerOnPage(page, handler) {
|
|
2126
1943
|
const state = ensurePageState(page);
|
|
2127
|
-
state.dialogHandler =
|
|
1944
|
+
state.dialogHandler = handler;
|
|
2128
1945
|
}
|
|
2129
1946
|
function applyStealthToPage(page) {
|
|
2130
1947
|
page.evaluate(STEALTH_SCRIPT).catch((e) => {
|
|
@@ -2152,6 +1969,47 @@ function observeContext(context) {
|
|
|
2152
1969
|
function observeBrowser(browser) {
|
|
2153
1970
|
for (const context of browser.contexts()) observeContext(context);
|
|
2154
1971
|
}
|
|
1972
|
+
function toAIFriendlyError(error, selector) {
|
|
1973
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1974
|
+
if (message.includes("strict mode violation")) {
|
|
1975
|
+
const countMatch = /resolved to (\d+) elements/.exec(message);
|
|
1976
|
+
const count = countMatch ? countMatch[1] : "multiple";
|
|
1977
|
+
return new Error(
|
|
1978
|
+
`Selector "${selector}" matched ${count} elements. Run a new snapshot to get updated refs, or use a different ref.`
|
|
1979
|
+
);
|
|
1980
|
+
}
|
|
1981
|
+
if ((message.includes("Timeout") || message.includes("waiting for")) && (message.includes("to be visible") || message.includes("not visible"))) {
|
|
1982
|
+
return new Error(
|
|
1983
|
+
`Element "${selector}" not found or not visible. Run a new snapshot to see current page elements.`
|
|
1984
|
+
);
|
|
1985
|
+
}
|
|
1986
|
+
if (message.includes("intercepts pointer events") || message.includes("not visible") || message.includes("not receive pointer events")) {
|
|
1987
|
+
return new Error(
|
|
1988
|
+
`Element "${selector}" is not interactable (hidden or covered). Try scrolling it into view, closing overlays, or re-snapshotting.`
|
|
1989
|
+
);
|
|
1990
|
+
}
|
|
1991
|
+
const timeoutMatch = /Timeout (\d+)ms exceeded/.exec(message);
|
|
1992
|
+
if (timeoutMatch) {
|
|
1993
|
+
return new Error(
|
|
1994
|
+
`Element "${selector}" timed out after ${timeoutMatch[1]}ms \u2014 element may be hidden or not interactable. Run a new snapshot to see current page elements.`
|
|
1995
|
+
);
|
|
1996
|
+
}
|
|
1997
|
+
const cleaned = message.replace(/locator\([^)]*\)\./g, "").replace(/waiting for locator\([^)]*\)/g, "").trim();
|
|
1998
|
+
return new Error(cleaned || message);
|
|
1999
|
+
}
|
|
2000
|
+
function normalizeTimeoutMs(timeoutMs, fallback, maxMs = 12e4) {
|
|
2001
|
+
return Math.max(500, Math.min(maxMs, timeoutMs ?? fallback));
|
|
2002
|
+
}
|
|
2003
|
+
|
|
2004
|
+
// src/ref-resolver.ts
|
|
2005
|
+
var roleRefsByTarget = /* @__PURE__ */ new Map();
|
|
2006
|
+
var MAX_ROLE_REFS_CACHE = 50;
|
|
2007
|
+
function normalizeCdpUrl(raw) {
|
|
2008
|
+
return raw.replace(/\/$/, "");
|
|
2009
|
+
}
|
|
2010
|
+
function roleRefsKey(cdpUrl, targetId) {
|
|
2011
|
+
return `${normalizeCdpUrl(cdpUrl)}::${targetId}`;
|
|
2012
|
+
}
|
|
2155
2013
|
function rememberRoleRefsForTarget(opts) {
|
|
2156
2014
|
const targetId = opts.targetId.trim();
|
|
2157
2015
|
if (targetId === "") return;
|
|
@@ -2191,6 +2049,232 @@ function restoreRoleRefsForTarget(opts) {
|
|
|
2191
2049
|
state.roleRefsFrameSelector = entry.frameSelector;
|
|
2192
2050
|
state.roleRefsMode = entry.mode;
|
|
2193
2051
|
}
|
|
2052
|
+
function clearRoleRefsForCdpUrl(cdpUrl) {
|
|
2053
|
+
const normalized = normalizeCdpUrl(cdpUrl);
|
|
2054
|
+
for (const key of roleRefsByTarget.keys()) {
|
|
2055
|
+
if (key.startsWith(normalized + "::")) roleRefsByTarget.delete(key);
|
|
2056
|
+
}
|
|
2057
|
+
}
|
|
2058
|
+
function parseRoleRef(raw) {
|
|
2059
|
+
const trimmed = raw.trim();
|
|
2060
|
+
if (!trimmed) return null;
|
|
2061
|
+
const normalized = trimmed.startsWith("@") ? trimmed.slice(1) : trimmed.startsWith("ref=") ? trimmed.slice(4) : trimmed;
|
|
2062
|
+
return /^e\d+$/.test(normalized) ? normalized : null;
|
|
2063
|
+
}
|
|
2064
|
+
function requireRef(value) {
|
|
2065
|
+
const raw = typeof value === "string" ? value.trim() : "";
|
|
2066
|
+
const ref = (raw ? parseRoleRef(raw) : null) ?? (raw.startsWith("@") ? raw.slice(1) : raw);
|
|
2067
|
+
if (!ref) throw new Error("ref is required");
|
|
2068
|
+
return ref;
|
|
2069
|
+
}
|
|
2070
|
+
function requireRefOrSelector(ref, selector) {
|
|
2071
|
+
const trimmedRef = typeof ref === "string" ? ref.trim() : "";
|
|
2072
|
+
const trimmedSelector = typeof selector === "string" ? selector.trim() : "";
|
|
2073
|
+
if (!trimmedRef && !trimmedSelector) throw new Error("ref or selector is required");
|
|
2074
|
+
return { ref: trimmedRef || void 0, selector: trimmedSelector || void 0 };
|
|
2075
|
+
}
|
|
2076
|
+
function resolveInteractionTimeoutMs(timeoutMs) {
|
|
2077
|
+
return Math.max(500, Math.min(6e4, Math.floor(timeoutMs ?? 8e3)));
|
|
2078
|
+
}
|
|
2079
|
+
function resolveBoundedDelayMs(value, label, maxMs) {
|
|
2080
|
+
const normalized = Math.floor(value ?? 0);
|
|
2081
|
+
if (!Number.isFinite(normalized) || normalized < 0) throw new Error(`${label} must be >= 0`);
|
|
2082
|
+
if (normalized > maxMs) throw new Error(`${label} exceeds maximum of ${String(maxMs)}ms`);
|
|
2083
|
+
return normalized;
|
|
2084
|
+
}
|
|
2085
|
+
function refLocator(page, ref) {
|
|
2086
|
+
const normalized = ref.startsWith("@") ? ref.slice(1) : ref.startsWith("ref=") ? ref.slice(4) : ref;
|
|
2087
|
+
if (normalized.trim() === "") throw new Error("ref is required");
|
|
2088
|
+
if (/^e\d+$/.test(normalized)) {
|
|
2089
|
+
const state = getPageState(page);
|
|
2090
|
+
if (state?.roleRefsMode === "aria") {
|
|
2091
|
+
return (state.roleRefsFrameSelector !== void 0 && state.roleRefsFrameSelector !== "" ? page.frameLocator(state.roleRefsFrameSelector) : page).locator(`aria-ref=${normalized}`);
|
|
2092
|
+
}
|
|
2093
|
+
const info = state?.roleRefs?.[normalized];
|
|
2094
|
+
if (!info) throw new Error(`Unknown ref "${normalized}". Run a new snapshot and use a ref from that snapshot.`);
|
|
2095
|
+
const locAny = state.roleRefsFrameSelector !== void 0 && state.roleRefsFrameSelector !== "" ? page.frameLocator(state.roleRefsFrameSelector) : page;
|
|
2096
|
+
const role = info.role;
|
|
2097
|
+
const locator = info.name !== void 0 && info.name !== "" ? locAny.getByRole(role, { name: info.name, exact: true }) : locAny.getByRole(role);
|
|
2098
|
+
return info.nth !== void 0 ? locator.nth(info.nth) : locator;
|
|
2099
|
+
}
|
|
2100
|
+
return page.locator(`aria-ref=${normalized}`);
|
|
2101
|
+
}
|
|
2102
|
+
|
|
2103
|
+
// src/connection.ts
|
|
2104
|
+
var BrowserTabNotFoundError = class extends Error {
|
|
2105
|
+
constructor(message = "Tab not found") {
|
|
2106
|
+
super(message);
|
|
2107
|
+
this.name = "BrowserTabNotFoundError";
|
|
2108
|
+
}
|
|
2109
|
+
};
|
|
2110
|
+
async function fetchJsonForCdp(url, timeoutMs) {
|
|
2111
|
+
const ctrl = new AbortController();
|
|
2112
|
+
const t = setTimeout(() => {
|
|
2113
|
+
ctrl.abort();
|
|
2114
|
+
}, timeoutMs);
|
|
2115
|
+
try {
|
|
2116
|
+
const res = await fetch(url, { signal: ctrl.signal });
|
|
2117
|
+
if (!res.ok) return null;
|
|
2118
|
+
return await res.json();
|
|
2119
|
+
} catch {
|
|
2120
|
+
return null;
|
|
2121
|
+
} finally {
|
|
2122
|
+
clearTimeout(t);
|
|
2123
|
+
}
|
|
2124
|
+
}
|
|
2125
|
+
function appendCdpPath2(cdpUrl, cdpPath) {
|
|
2126
|
+
try {
|
|
2127
|
+
const url = new URL(cdpUrl);
|
|
2128
|
+
url.pathname = `${url.pathname.replace(/\/$/, "")}${cdpPath.startsWith("/") ? cdpPath : `/${cdpPath}`}`;
|
|
2129
|
+
return url.toString();
|
|
2130
|
+
} catch {
|
|
2131
|
+
return `${cdpUrl.replace(/\/$/, "")}${cdpPath}`;
|
|
2132
|
+
}
|
|
2133
|
+
}
|
|
2134
|
+
async function withPlaywrightPageCdpSession(page, fn) {
|
|
2135
|
+
const CDP_SESSION_TIMEOUT_MS = 1e4;
|
|
2136
|
+
const session = await Promise.race([
|
|
2137
|
+
page.context().newCDPSession(page),
|
|
2138
|
+
new Promise((_, reject) => {
|
|
2139
|
+
setTimeout(() => {
|
|
2140
|
+
reject(new Error("newCDPSession timed out after 10s"));
|
|
2141
|
+
}, CDP_SESSION_TIMEOUT_MS);
|
|
2142
|
+
})
|
|
2143
|
+
]);
|
|
2144
|
+
try {
|
|
2145
|
+
return await fn(session);
|
|
2146
|
+
} finally {
|
|
2147
|
+
await session.detach().catch(() => {
|
|
2148
|
+
});
|
|
2149
|
+
}
|
|
2150
|
+
}
|
|
2151
|
+
async function withPageScopedCdpClient(opts) {
|
|
2152
|
+
return await withPlaywrightPageCdpSession(opts.page, async (session) => {
|
|
2153
|
+
return await opts.fn((method, params) => session.send(method, params));
|
|
2154
|
+
});
|
|
2155
|
+
}
|
|
2156
|
+
var LOOPBACK_ENTRIES = "localhost,127.0.0.1,[::1]";
|
|
2157
|
+
function noProxyAlreadyCoversLocalhost() {
|
|
2158
|
+
const current = process.env.NO_PROXY ?? process.env.no_proxy ?? "";
|
|
2159
|
+
return current.includes("localhost") && current.includes("127.0.0.1") && current.includes("[::1]");
|
|
2160
|
+
}
|
|
2161
|
+
function isLoopbackCdpUrl(url) {
|
|
2162
|
+
try {
|
|
2163
|
+
return isLoopbackHost(new URL(url).hostname);
|
|
2164
|
+
} catch {
|
|
2165
|
+
return false;
|
|
2166
|
+
}
|
|
2167
|
+
}
|
|
2168
|
+
var envMutexPromise = Promise.resolve();
|
|
2169
|
+
async function withNoProxyForCdpUrl(url, fn) {
|
|
2170
|
+
if (!isLoopbackCdpUrl(url) || !hasProxyEnvConfigured()) return fn();
|
|
2171
|
+
const prev = envMutexPromise;
|
|
2172
|
+
let release = () => {
|
|
2173
|
+
};
|
|
2174
|
+
envMutexPromise = new Promise((r) => {
|
|
2175
|
+
release = r;
|
|
2176
|
+
});
|
|
2177
|
+
await prev;
|
|
2178
|
+
if (noProxyAlreadyCoversLocalhost()) {
|
|
2179
|
+
try {
|
|
2180
|
+
return await fn();
|
|
2181
|
+
} finally {
|
|
2182
|
+
release();
|
|
2183
|
+
}
|
|
2184
|
+
}
|
|
2185
|
+
const savedNoProxy = process.env.NO_PROXY;
|
|
2186
|
+
const savedNoProxyLower = process.env.no_proxy;
|
|
2187
|
+
const current = savedNoProxy ?? savedNoProxyLower ?? "";
|
|
2188
|
+
const applied = current ? `${current},${LOOPBACK_ENTRIES}` : LOOPBACK_ENTRIES;
|
|
2189
|
+
process.env.NO_PROXY = applied;
|
|
2190
|
+
process.env.no_proxy = applied;
|
|
2191
|
+
try {
|
|
2192
|
+
return await fn();
|
|
2193
|
+
} finally {
|
|
2194
|
+
if (process.env.NO_PROXY === applied) {
|
|
2195
|
+
if (savedNoProxy !== void 0) process.env.NO_PROXY = savedNoProxy;
|
|
2196
|
+
else delete process.env.NO_PROXY;
|
|
2197
|
+
}
|
|
2198
|
+
if (process.env.no_proxy === applied) {
|
|
2199
|
+
if (savedNoProxyLower !== void 0) process.env.no_proxy = savedNoProxyLower;
|
|
2200
|
+
else delete process.env.no_proxy;
|
|
2201
|
+
}
|
|
2202
|
+
release();
|
|
2203
|
+
}
|
|
2204
|
+
}
|
|
2205
|
+
new http__default.default.Agent();
|
|
2206
|
+
new https__default.default.Agent();
|
|
2207
|
+
function getHeadersWithAuth(endpoint, baseHeaders = {}) {
|
|
2208
|
+
const headers = { ...baseHeaders };
|
|
2209
|
+
try {
|
|
2210
|
+
const parsed = new URL(endpoint);
|
|
2211
|
+
if (Object.keys(headers).some((k) => k.toLowerCase() === "authorization")) return headers;
|
|
2212
|
+
if (parsed.username || parsed.password) {
|
|
2213
|
+
const credentials = Buffer.from(
|
|
2214
|
+
`${decodeURIComponent(parsed.username)}:${decodeURIComponent(parsed.password)}`
|
|
2215
|
+
).toString("base64");
|
|
2216
|
+
headers.Authorization = `Basic ${credentials}`;
|
|
2217
|
+
}
|
|
2218
|
+
} catch {
|
|
2219
|
+
}
|
|
2220
|
+
return headers;
|
|
2221
|
+
}
|
|
2222
|
+
var cachedByCdpUrl = /* @__PURE__ */ new Map();
|
|
2223
|
+
var connectingByCdpUrl = /* @__PURE__ */ new Map();
|
|
2224
|
+
var BlockedBrowserTargetError = class extends Error {
|
|
2225
|
+
constructor() {
|
|
2226
|
+
super("Browser target is unavailable after SSRF policy blocked its navigation.");
|
|
2227
|
+
this.name = "BlockedBrowserTargetError";
|
|
2228
|
+
}
|
|
2229
|
+
};
|
|
2230
|
+
var blockedTargetsByCdpUrl = /* @__PURE__ */ new Set();
|
|
2231
|
+
var blockedPageRefsByCdpUrl = /* @__PURE__ */ new Map();
|
|
2232
|
+
function blockedTargetKey(cdpUrl, targetId) {
|
|
2233
|
+
return `${normalizeCdpUrl(cdpUrl)}::${targetId}`;
|
|
2234
|
+
}
|
|
2235
|
+
function isBlockedTarget(cdpUrl, targetId) {
|
|
2236
|
+
const normalized = targetId?.trim() ?? "";
|
|
2237
|
+
if (normalized === "") return false;
|
|
2238
|
+
return blockedTargetsByCdpUrl.has(blockedTargetKey(cdpUrl, normalized));
|
|
2239
|
+
}
|
|
2240
|
+
function markTargetBlocked(cdpUrl, targetId) {
|
|
2241
|
+
const normalized = targetId?.trim() ?? "";
|
|
2242
|
+
if (normalized === "") return;
|
|
2243
|
+
blockedTargetsByCdpUrl.add(blockedTargetKey(cdpUrl, normalized));
|
|
2244
|
+
}
|
|
2245
|
+
function clearBlockedTarget(cdpUrl, targetId) {
|
|
2246
|
+
const normalized = targetId?.trim() ?? "";
|
|
2247
|
+
if (normalized === "") return;
|
|
2248
|
+
blockedTargetsByCdpUrl.delete(blockedTargetKey(cdpUrl, normalized));
|
|
2249
|
+
}
|
|
2250
|
+
function hasBlockedTargetsForCdpUrl(cdpUrl) {
|
|
2251
|
+
const prefix = `${normalizeCdpUrl(cdpUrl)}::`;
|
|
2252
|
+
for (const key of blockedTargetsByCdpUrl) {
|
|
2253
|
+
if (key.startsWith(prefix)) return true;
|
|
2254
|
+
}
|
|
2255
|
+
return false;
|
|
2256
|
+
}
|
|
2257
|
+
function blockedPageRefsForCdpUrl(cdpUrl) {
|
|
2258
|
+
const normalized = normalizeCdpUrl(cdpUrl);
|
|
2259
|
+
const existing = blockedPageRefsByCdpUrl.get(normalized);
|
|
2260
|
+
if (existing) return existing;
|
|
2261
|
+
const created = /* @__PURE__ */ new WeakSet();
|
|
2262
|
+
blockedPageRefsByCdpUrl.set(normalized, created);
|
|
2263
|
+
return created;
|
|
2264
|
+
}
|
|
2265
|
+
function isBlockedPageRef(cdpUrl, page) {
|
|
2266
|
+
return blockedPageRefsByCdpUrl.get(normalizeCdpUrl(cdpUrl))?.has(page) ?? false;
|
|
2267
|
+
}
|
|
2268
|
+
function markPageRefBlocked(cdpUrl, page) {
|
|
2269
|
+
blockedPageRefsForCdpUrl(cdpUrl).add(page);
|
|
2270
|
+
}
|
|
2271
|
+
function clearBlockedPageRef(cdpUrl, page) {
|
|
2272
|
+
blockedPageRefsByCdpUrl.get(normalizeCdpUrl(cdpUrl))?.delete(page);
|
|
2273
|
+
}
|
|
2274
|
+
async function setDialogHandler(opts) {
|
|
2275
|
+
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
2276
|
+
setDialogHandlerOnPage(page, opts.handler);
|
|
2277
|
+
}
|
|
2194
2278
|
async function connectBrowser(cdpUrl, authToken) {
|
|
2195
2279
|
const normalized = normalizeCdpUrl(cdpUrl);
|
|
2196
2280
|
const existing_cached = cachedByCdpUrl.get(normalized);
|
|
@@ -2213,9 +2297,7 @@ async function connectBrowser(cdpUrl, authToken) {
|
|
|
2213
2297
|
const onDisconnected = () => {
|
|
2214
2298
|
if (cachedByCdpUrl.get(normalized)?.browser === browser) {
|
|
2215
2299
|
cachedByCdpUrl.delete(normalized);
|
|
2216
|
-
|
|
2217
|
-
if (key.startsWith(normalized + "::")) roleRefsByTarget.delete(key);
|
|
2218
|
-
}
|
|
2300
|
+
clearRoleRefsForCdpUrl(normalized);
|
|
2219
2301
|
}
|
|
2220
2302
|
};
|
|
2221
2303
|
const connected = { browser, cdpUrl: normalized, onDisconnected };
|
|
@@ -2347,7 +2429,7 @@ async function tryTerminateExecutionViaCdp(cdpUrl, targetId) {
|
|
|
2347
2429
|
};
|
|
2348
2430
|
});
|
|
2349
2431
|
}
|
|
2350
|
-
async function
|
|
2432
|
+
async function forceDisconnectPlaywrightConnection(opts) {
|
|
2351
2433
|
const normalized = normalizeCdpUrl(opts.cdpUrl);
|
|
2352
2434
|
const cur = cachedByCdpUrl.get(normalized);
|
|
2353
2435
|
if (!cur) return;
|
|
@@ -2364,6 +2446,7 @@ async function forceDisconnectPlaywrightForTarget(opts) {
|
|
|
2364
2446
|
cur.browser.close().catch(() => {
|
|
2365
2447
|
});
|
|
2366
2448
|
}
|
|
2449
|
+
var forceDisconnectPlaywrightForTarget = forceDisconnectPlaywrightConnection;
|
|
2367
2450
|
function getAllPages(browser) {
|
|
2368
2451
|
return browser.contexts().flatMap((c) => c.pages());
|
|
2369
2452
|
}
|
|
@@ -2479,87 +2562,12 @@ async function resolvePageByTargetIdOrThrow(opts) {
|
|
|
2479
2562
|
if (!page) throw new BrowserTabNotFoundError();
|
|
2480
2563
|
return page;
|
|
2481
2564
|
}
|
|
2482
|
-
function parseRoleRef(raw) {
|
|
2483
|
-
const trimmed = raw.trim();
|
|
2484
|
-
if (!trimmed) return null;
|
|
2485
|
-
const normalized = trimmed.startsWith("@") ? trimmed.slice(1) : trimmed.startsWith("ref=") ? trimmed.slice(4) : trimmed;
|
|
2486
|
-
return /^e\d+$/.test(normalized) ? normalized : null;
|
|
2487
|
-
}
|
|
2488
|
-
function requireRef(value) {
|
|
2489
|
-
const raw = typeof value === "string" ? value.trim() : "";
|
|
2490
|
-
const ref = (raw ? parseRoleRef(raw) : null) ?? (raw.startsWith("@") ? raw.slice(1) : raw);
|
|
2491
|
-
if (!ref) throw new Error("ref is required");
|
|
2492
|
-
return ref;
|
|
2493
|
-
}
|
|
2494
|
-
function requireRefOrSelector(ref, selector) {
|
|
2495
|
-
const trimmedRef = typeof ref === "string" ? ref.trim() : "";
|
|
2496
|
-
const trimmedSelector = typeof selector === "string" ? selector.trim() : "";
|
|
2497
|
-
if (!trimmedRef && !trimmedSelector) throw new Error("ref or selector is required");
|
|
2498
|
-
return { ref: trimmedRef || void 0, selector: trimmedSelector || void 0 };
|
|
2499
|
-
}
|
|
2500
|
-
function resolveInteractionTimeoutMs(timeoutMs) {
|
|
2501
|
-
return Math.max(500, Math.min(6e4, Math.floor(timeoutMs ?? 8e3)));
|
|
2502
|
-
}
|
|
2503
|
-
function resolveBoundedDelayMs(value, label, maxMs) {
|
|
2504
|
-
const normalized = Math.floor(value ?? 0);
|
|
2505
|
-
if (!Number.isFinite(normalized) || normalized < 0) throw new Error(`${label} must be >= 0`);
|
|
2506
|
-
if (normalized > maxMs) throw new Error(`${label} exceeds maximum of ${String(maxMs)}ms`);
|
|
2507
|
-
return normalized;
|
|
2508
|
-
}
|
|
2509
2565
|
async function getRestoredPageForTarget(opts) {
|
|
2510
2566
|
const page = await getPageForTargetId(opts);
|
|
2511
2567
|
ensurePageState(page);
|
|
2512
2568
|
restoreRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, page });
|
|
2513
2569
|
return page;
|
|
2514
2570
|
}
|
|
2515
|
-
function refLocator(page, ref) {
|
|
2516
|
-
const normalized = ref.startsWith("@") ? ref.slice(1) : ref.startsWith("ref=") ? ref.slice(4) : ref;
|
|
2517
|
-
if (normalized.trim() === "") throw new Error("ref is required");
|
|
2518
|
-
if (/^e\d+$/.test(normalized)) {
|
|
2519
|
-
const state = pageStates.get(page);
|
|
2520
|
-
if (state?.roleRefsMode === "aria") {
|
|
2521
|
-
return (state.roleRefsFrameSelector !== void 0 && state.roleRefsFrameSelector !== "" ? page.frameLocator(state.roleRefsFrameSelector) : page).locator(`aria-ref=${normalized}`);
|
|
2522
|
-
}
|
|
2523
|
-
const info = state?.roleRefs?.[normalized];
|
|
2524
|
-
if (!info) throw new Error(`Unknown ref "${normalized}". Run a new snapshot and use a ref from that snapshot.`);
|
|
2525
|
-
const locAny = state.roleRefsFrameSelector !== void 0 && state.roleRefsFrameSelector !== "" ? page.frameLocator(state.roleRefsFrameSelector) : page;
|
|
2526
|
-
const role = info.role;
|
|
2527
|
-
const locator = info.name !== void 0 && info.name !== "" ? locAny.getByRole(role, { name: info.name, exact: true }) : locAny.getByRole(role);
|
|
2528
|
-
return info.nth !== void 0 ? locator.nth(info.nth) : locator;
|
|
2529
|
-
}
|
|
2530
|
-
return page.locator(`aria-ref=${normalized}`);
|
|
2531
|
-
}
|
|
2532
|
-
function toAIFriendlyError(error, selector) {
|
|
2533
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
2534
|
-
if (message.includes("strict mode violation")) {
|
|
2535
|
-
const countMatch = /resolved to (\d+) elements/.exec(message);
|
|
2536
|
-
const count = countMatch ? countMatch[1] : "multiple";
|
|
2537
|
-
return new Error(
|
|
2538
|
-
`Selector "${selector}" matched ${count} elements. Run a new snapshot to get updated refs, or use a different ref.`
|
|
2539
|
-
);
|
|
2540
|
-
}
|
|
2541
|
-
if ((message.includes("Timeout") || message.includes("waiting for")) && (message.includes("to be visible") || message.includes("not visible"))) {
|
|
2542
|
-
return new Error(
|
|
2543
|
-
`Element "${selector}" not found or not visible. Run a new snapshot to see current page elements.`
|
|
2544
|
-
);
|
|
2545
|
-
}
|
|
2546
|
-
if (message.includes("intercepts pointer events") || message.includes("not visible") || message.includes("not receive pointer events")) {
|
|
2547
|
-
return new Error(
|
|
2548
|
-
`Element "${selector}" is not interactable (hidden or covered). Try scrolling it into view, closing overlays, or re-snapshotting.`
|
|
2549
|
-
);
|
|
2550
|
-
}
|
|
2551
|
-
const timeoutMatch = /Timeout (\d+)ms exceeded/.exec(message);
|
|
2552
|
-
if (timeoutMatch) {
|
|
2553
|
-
return new Error(
|
|
2554
|
-
`Element "${selector}" timed out after ${timeoutMatch[1]}ms \u2014 element may be hidden or not interactable. Run a new snapshot to see current page elements.`
|
|
2555
|
-
);
|
|
2556
|
-
}
|
|
2557
|
-
const cleaned = message.replace(/locator\([^)]*\)\./g, "").replace(/waiting for locator\([^)]*\)/g, "").trim();
|
|
2558
|
-
return new Error(cleaned || message);
|
|
2559
|
-
}
|
|
2560
|
-
function normalizeTimeoutMs(timeoutMs, fallback, maxMs = 12e4) {
|
|
2561
|
-
return Math.max(500, Math.min(maxMs, timeoutMs ?? fallback));
|
|
2562
|
-
}
|
|
2563
2571
|
|
|
2564
2572
|
// src/actions/evaluate.ts
|
|
2565
2573
|
async function evaluateInAllFramesViaPlaywright(opts) {
|
|
@@ -2584,7 +2592,8 @@ async function evaluateInAllFramesViaPlaywright(opts) {
|
|
|
2584
2592
|
frameName: frame.name(),
|
|
2585
2593
|
result
|
|
2586
2594
|
});
|
|
2587
|
-
} catch {
|
|
2595
|
+
} catch (err) {
|
|
2596
|
+
console.warn("[browserclaw] frame evaluate failed:", err instanceof Error ? err.message : String(err));
|
|
2588
2597
|
}
|
|
2589
2598
|
}
|
|
2590
2599
|
return results;
|
|
@@ -2670,7 +2679,7 @@ async function evaluateViaPlaywright(opts) {
|
|
|
2670
2679
|
}
|
|
2671
2680
|
if (signal !== void 0) {
|
|
2672
2681
|
const disconnect = () => {
|
|
2673
|
-
|
|
2682
|
+
forceDisconnectPlaywrightConnection({
|
|
2674
2683
|
cdpUrl: opts.cdpUrl,
|
|
2675
2684
|
targetId: opts.targetId}).catch(() => {
|
|
2676
2685
|
});
|
|
@@ -2737,7 +2746,6 @@ function withBrowserNavigationPolicy(ssrfPolicy) {
|
|
|
2737
2746
|
}
|
|
2738
2747
|
var NETWORK_NAVIGATION_PROTOCOLS = /* @__PURE__ */ new Set(["http:", "https:"]);
|
|
2739
2748
|
var SAFE_NON_NETWORK_URLS = /* @__PURE__ */ new Set(["about:blank"]);
|
|
2740
|
-
var PROXY_ENV_KEYS2 = ["HTTP_PROXY", "HTTPS_PROXY", "ALL_PROXY", "http_proxy", "https_proxy", "all_proxy"];
|
|
2741
2749
|
var BLOCKED_HOSTNAMES = /* @__PURE__ */ new Set(["localhost", "localhost.localdomain", "metadata.google.internal"]);
|
|
2742
2750
|
function isAllowedNonNetworkNavigationUrl(parsed) {
|
|
2743
2751
|
return SAFE_NON_NETWORK_URLS.has(parsed.href);
|
|
@@ -2745,13 +2753,6 @@ function isAllowedNonNetworkNavigationUrl(parsed) {
|
|
|
2745
2753
|
function isPrivateNetworkAllowedByPolicy(policy) {
|
|
2746
2754
|
return policy?.dangerouslyAllowPrivateNetwork === true || policy?.allowPrivateNetwork === true;
|
|
2747
2755
|
}
|
|
2748
|
-
function hasProxyEnvConfigured2(env = process.env) {
|
|
2749
|
-
for (const key of PROXY_ENV_KEYS2) {
|
|
2750
|
-
const value = env[key];
|
|
2751
|
-
if (typeof value === "string" && value.trim().length > 0) return true;
|
|
2752
|
-
}
|
|
2753
|
-
return false;
|
|
2754
|
-
}
|
|
2755
2756
|
function normalizeHostname(hostname) {
|
|
2756
2757
|
let h = hostname.trim().toLowerCase();
|
|
2757
2758
|
if (h.startsWith("[") && h.endsWith("]")) h = h.slice(1, -1);
|
|
@@ -3062,7 +3063,7 @@ async function assertBrowserNavigationAllowed(opts) {
|
|
|
3062
3063
|
if (isAllowedNonNetworkNavigationUrl(parsed)) return;
|
|
3063
3064
|
throw new InvalidBrowserNavigationUrlError(`Navigation blocked: unsupported protocol "${parsed.protocol}"`);
|
|
3064
3065
|
}
|
|
3065
|
-
if (
|
|
3066
|
+
if (hasProxyEnvConfigured() && !isPrivateNetworkAllowedByPolicy(opts.ssrfPolicy)) {
|
|
3066
3067
|
throw new InvalidBrowserNavigationUrlError(
|
|
3067
3068
|
"Navigation blocked: strict browser SSRF policy cannot be enforced while env proxy variables are set"
|
|
3068
3069
|
);
|
|
@@ -3299,7 +3300,7 @@ async function pressAndHoldViaCdp(opts) {
|
|
|
3299
3300
|
async function clickByTextViaPlaywright(opts) {
|
|
3300
3301
|
const page = await getRestoredPageForTarget(opts);
|
|
3301
3302
|
const timeout = resolveInteractionTimeoutMs(opts.timeoutMs);
|
|
3302
|
-
const locator = page.getByText(opts.text, { exact: opts.exact }).
|
|
3303
|
+
const locator = page.getByText(opts.text, { exact: opts.exact }).or(page.getByTitle(opts.text, { exact: opts.exact })).and(page.locator(":visible")).first();
|
|
3303
3304
|
try {
|
|
3304
3305
|
await locator.click({ timeout, button: opts.button, modifiers: opts.modifiers });
|
|
3305
3306
|
} catch (err) {
|
|
@@ -3517,7 +3518,7 @@ async function armDialogViaPlaywright(opts) {
|
|
|
3517
3518
|
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
3518
3519
|
const state = ensurePageState(page);
|
|
3519
3520
|
const timeout = normalizeTimeoutMs(opts.timeoutMs, 12e4);
|
|
3520
|
-
state.armIdDialog = bumpDialogArmId();
|
|
3521
|
+
state.armIdDialog = bumpDialogArmId(state);
|
|
3521
3522
|
const armId = state.armIdDialog;
|
|
3522
3523
|
page.waitForEvent("dialog", { timeout }).then(async (dialog) => {
|
|
3523
3524
|
if (state.armIdDialog !== armId) return;
|
|
@@ -3535,7 +3536,7 @@ async function armFileUploadViaPlaywright(opts) {
|
|
|
3535
3536
|
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
3536
3537
|
const state = ensurePageState(page);
|
|
3537
3538
|
const timeout = normalizeTimeoutMs(opts.timeoutMs, 12e4);
|
|
3538
|
-
state.armIdUpload = bumpUploadArmId();
|
|
3539
|
+
state.armIdUpload = bumpUploadArmId(state);
|
|
3539
3540
|
const armId = state.armIdUpload;
|
|
3540
3541
|
page.waitForEvent("filechooser", { timeout }).then(async (fileChooser) => {
|
|
3541
3542
|
if (state.armIdUpload !== armId) return;
|
|
@@ -3695,7 +3696,7 @@ async function navigateViaPlaywright(opts) {
|
|
|
3695
3696
|
response = await navigate();
|
|
3696
3697
|
} catch (err) {
|
|
3697
3698
|
if (!isRetryableNavigateError(err)) throw err;
|
|
3698
|
-
await
|
|
3699
|
+
await forceDisconnectPlaywrightConnection({
|
|
3699
3700
|
cdpUrl: opts.cdpUrl,
|
|
3700
3701
|
targetId: opts.targetId}).catch(() => {
|
|
3701
3702
|
});
|
|
@@ -3837,18 +3838,12 @@ async function resizeViewportViaPlaywright(opts) {
|
|
|
3837
3838
|
|
|
3838
3839
|
// src/actions/wait.ts
|
|
3839
3840
|
var MAX_WAIT_TIME_MS = 3e4;
|
|
3840
|
-
function resolveBoundedDelayMs2(value, label, maxMs) {
|
|
3841
|
-
const normalized = Math.floor(value ?? 0);
|
|
3842
|
-
if (!Number.isFinite(normalized) || normalized < 0) throw new Error(`${label} must be >= 0`);
|
|
3843
|
-
if (normalized > maxMs) throw new Error(`${label} exceeds maximum of ${String(maxMs)}ms`);
|
|
3844
|
-
return normalized;
|
|
3845
|
-
}
|
|
3846
3841
|
async function waitForViaPlaywright(opts) {
|
|
3847
3842
|
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
3848
3843
|
ensurePageState(page);
|
|
3849
3844
|
const timeout = normalizeTimeoutMs(opts.timeoutMs, 2e4);
|
|
3850
3845
|
if (typeof opts.timeMs === "number" && Number.isFinite(opts.timeMs)) {
|
|
3851
|
-
await page.waitForTimeout(
|
|
3846
|
+
await page.waitForTimeout(resolveBoundedDelayMs(opts.timeMs, "wait timeMs", MAX_WAIT_TIME_MS));
|
|
3852
3847
|
}
|
|
3853
3848
|
if (opts.text !== void 0 && opts.text !== "") {
|
|
3854
3849
|
await page.waitForFunction((text) => (document.body?.innerText ?? "").includes(text), opts.text, { timeout });
|
|
@@ -4105,7 +4100,7 @@ async function downloadViaPlaywright(opts) {
|
|
|
4105
4100
|
const timeout = normalizeTimeoutMs(opts.timeoutMs, 12e4);
|
|
4106
4101
|
const outPath = opts.path.trim();
|
|
4107
4102
|
if (!outPath) throw new Error("path is required");
|
|
4108
|
-
state.armIdDownload = bumpDownloadArmId();
|
|
4103
|
+
state.armIdDownload = bumpDownloadArmId(state);
|
|
4109
4104
|
const armId = state.armIdDownload;
|
|
4110
4105
|
const waiter = createPageDownloadWaiter(page, timeout);
|
|
4111
4106
|
try {
|
|
@@ -4125,7 +4120,7 @@ async function waitForDownloadViaPlaywright(opts) {
|
|
|
4125
4120
|
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
4126
4121
|
const state = ensurePageState(page);
|
|
4127
4122
|
const timeout = normalizeTimeoutMs(opts.timeoutMs, 12e4);
|
|
4128
|
-
state.armIdDownload = bumpDownloadArmId();
|
|
4123
|
+
state.armIdDownload = bumpDownloadArmId(state);
|
|
4129
4124
|
const armId = state.armIdDownload;
|
|
4130
4125
|
const waiter = createPageDownloadWaiter(page, timeout);
|
|
4131
4126
|
try {
|
|
@@ -5067,6 +5062,7 @@ function axValue(v) {
|
|
|
5067
5062
|
return "";
|
|
5068
5063
|
}
|
|
5069
5064
|
function formatAriaNodes(nodes, limit) {
|
|
5065
|
+
if (nodes.length === 0) return [];
|
|
5070
5066
|
const byId = /* @__PURE__ */ new Map();
|
|
5071
5067
|
for (const n of nodes) if (n.nodeId) byId.set(n.nodeId, n);
|
|
5072
5068
|
const referenced = /* @__PURE__ */ new Set();
|
|
@@ -6575,6 +6571,7 @@ exports.createPinnedLookup = createPinnedLookup;
|
|
|
6575
6571
|
exports.detectChallengeViaPlaywright = detectChallengeViaPlaywright;
|
|
6576
6572
|
exports.ensureContextState = ensureContextState;
|
|
6577
6573
|
exports.executeSingleAction = executeSingleAction;
|
|
6574
|
+
exports.forceDisconnectPlaywrightConnection = forceDisconnectPlaywrightConnection;
|
|
6578
6575
|
exports.forceDisconnectPlaywrightForTarget = forceDisconnectPlaywrightForTarget;
|
|
6579
6576
|
exports.getChromeWebSocketUrl = getChromeWebSocketUrl;
|
|
6580
6577
|
exports.getRestoredPageForTarget = getRestoredPageForTarget;
|