@hasna/browser 0.0.2 → 0.0.3
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/cli/index.js +1410 -307
- package/dist/index.js +387 -150
- package/dist/lib/actions-ref.test.d.ts +2 -0
- package/dist/lib/actions-ref.test.d.ts.map +1 -0
- package/dist/lib/actions.d.ts +12 -0
- package/dist/lib/actions.d.ts.map +1 -1
- package/dist/lib/annotate.d.ts +18 -0
- package/dist/lib/annotate.d.ts.map +1 -0
- package/dist/lib/annotate.test.d.ts +2 -0
- package/dist/lib/annotate.test.d.ts.map +1 -0
- package/dist/lib/dialogs.d.ts +15 -0
- package/dist/lib/dialogs.d.ts.map +1 -0
- package/dist/lib/profiles.d.ts +23 -0
- package/dist/lib/profiles.d.ts.map +1 -0
- package/dist/lib/session-v3.test.d.ts +2 -0
- package/dist/lib/session-v3.test.d.ts.map +1 -0
- package/dist/lib/session.d.ts +5 -0
- package/dist/lib/session.d.ts.map +1 -1
- package/dist/lib/snapshot-diff.test.d.ts +2 -0
- package/dist/lib/snapshot-diff.test.d.ts.map +1 -0
- package/dist/lib/snapshot.d.ts +33 -0
- package/dist/lib/snapshot.d.ts.map +1 -0
- package/dist/lib/snapshot.test.d.ts +2 -0
- package/dist/lib/snapshot.test.d.ts.map +1 -0
- package/dist/lib/stealth.d.ts +5 -0
- package/dist/lib/stealth.d.ts.map +1 -0
- package/dist/lib/stealth.test.d.ts +2 -0
- package/dist/lib/stealth.test.d.ts.map +1 -0
- package/dist/lib/tabs.d.ts +18 -0
- package/dist/lib/tabs.d.ts.map +1 -0
- package/dist/mcp/index.js +1452 -359
- package/dist/server/index.js +309 -166
- package/dist/types/index.d.ts +35 -0
- package/dist/types/index.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7451,6 +7451,261 @@ function inferUseCase(label) {
|
|
|
7451
7451
|
};
|
|
7452
7452
|
return map[label.toLowerCase()] ?? "spa_navigate" /* SPA_NAVIGATE */;
|
|
7453
7453
|
}
|
|
7454
|
+
// src/lib/network.ts
|
|
7455
|
+
function enableNetworkLogging(page, sessionId) {
|
|
7456
|
+
const requestStart = new Map;
|
|
7457
|
+
const onRequest = (req) => {
|
|
7458
|
+
requestStart.set(req.url(), Date.now());
|
|
7459
|
+
};
|
|
7460
|
+
const onResponse = (res) => {
|
|
7461
|
+
const start = requestStart.get(res.url()) ?? Date.now();
|
|
7462
|
+
const duration = Date.now() - start;
|
|
7463
|
+
const req = res.request();
|
|
7464
|
+
try {
|
|
7465
|
+
logRequest({
|
|
7466
|
+
session_id: sessionId,
|
|
7467
|
+
method: req.method(),
|
|
7468
|
+
url: res.url(),
|
|
7469
|
+
status_code: res.status(),
|
|
7470
|
+
request_headers: JSON.stringify(req.headers()),
|
|
7471
|
+
response_headers: JSON.stringify(res.headers()),
|
|
7472
|
+
body_size: res.headers()["content-length"] != null ? parseInt(res.headers()["content-length"]) : undefined,
|
|
7473
|
+
duration_ms: duration,
|
|
7474
|
+
resource_type: req.resourceType()
|
|
7475
|
+
});
|
|
7476
|
+
} catch {}
|
|
7477
|
+
};
|
|
7478
|
+
page.on("request", onRequest);
|
|
7479
|
+
page.on("response", onResponse);
|
|
7480
|
+
return () => {
|
|
7481
|
+
page.off("request", onRequest);
|
|
7482
|
+
page.off("response", onResponse);
|
|
7483
|
+
};
|
|
7484
|
+
}
|
|
7485
|
+
async function addInterceptRule(page, rule) {
|
|
7486
|
+
await page.route(rule.pattern, async (route) => {
|
|
7487
|
+
if (rule.action === "block") {
|
|
7488
|
+
await route.abort();
|
|
7489
|
+
} else if (rule.action === "modify" && rule.response) {
|
|
7490
|
+
await route.fulfill({
|
|
7491
|
+
status: rule.response.status,
|
|
7492
|
+
body: rule.response.body,
|
|
7493
|
+
headers: rule.response.headers
|
|
7494
|
+
});
|
|
7495
|
+
} else {
|
|
7496
|
+
await route.continue();
|
|
7497
|
+
}
|
|
7498
|
+
});
|
|
7499
|
+
}
|
|
7500
|
+
async function clearInterceptRules(page) {
|
|
7501
|
+
await page.unrouteAll();
|
|
7502
|
+
}
|
|
7503
|
+
function startHAR(page) {
|
|
7504
|
+
const entries = [];
|
|
7505
|
+
const requestStart = new Map;
|
|
7506
|
+
const onRequest = (req) => {
|
|
7507
|
+
requestStart.set(req.url() + req.method(), {
|
|
7508
|
+
time: Date.now(),
|
|
7509
|
+
method: req.method(),
|
|
7510
|
+
headers: req.headers(),
|
|
7511
|
+
postData: req.postData() ?? undefined
|
|
7512
|
+
});
|
|
7513
|
+
};
|
|
7514
|
+
const onResponse = async (res) => {
|
|
7515
|
+
const key = res.url() + res.request().method();
|
|
7516
|
+
const start = requestStart.get(key);
|
|
7517
|
+
if (!start)
|
|
7518
|
+
return;
|
|
7519
|
+
const duration = Date.now() - start.time;
|
|
7520
|
+
const entry = {
|
|
7521
|
+
startedDateTime: new Date(start.time).toISOString(),
|
|
7522
|
+
time: duration,
|
|
7523
|
+
request: {
|
|
7524
|
+
method: start.method,
|
|
7525
|
+
url: res.url(),
|
|
7526
|
+
headers: Object.entries(start.headers).map(([name, value]) => ({ name, value })),
|
|
7527
|
+
postData: start.postData ? { text: start.postData } : undefined
|
|
7528
|
+
},
|
|
7529
|
+
response: {
|
|
7530
|
+
status: res.status(),
|
|
7531
|
+
statusText: res.statusText(),
|
|
7532
|
+
headers: Object.entries(res.headers()).map(([name, value]) => ({ name, value })),
|
|
7533
|
+
content: {
|
|
7534
|
+
size: parseInt(res.headers()["content-length"] ?? "0") || 0,
|
|
7535
|
+
mimeType: res.headers()["content-type"] ?? "application/octet-stream"
|
|
7536
|
+
}
|
|
7537
|
+
},
|
|
7538
|
+
timings: { send: 0, wait: duration, receive: 0 }
|
|
7539
|
+
};
|
|
7540
|
+
entries.push(entry);
|
|
7541
|
+
requestStart.delete(key);
|
|
7542
|
+
};
|
|
7543
|
+
page.on("request", onRequest);
|
|
7544
|
+
page.on("response", onResponse);
|
|
7545
|
+
return {
|
|
7546
|
+
entries,
|
|
7547
|
+
stop: () => {
|
|
7548
|
+
page.off("request", onRequest);
|
|
7549
|
+
page.off("response", onResponse);
|
|
7550
|
+
return {
|
|
7551
|
+
log: {
|
|
7552
|
+
version: "1.2",
|
|
7553
|
+
creator: { name: "@hasna/browser", version: "0.0.1" },
|
|
7554
|
+
entries
|
|
7555
|
+
}
|
|
7556
|
+
};
|
|
7557
|
+
}
|
|
7558
|
+
};
|
|
7559
|
+
}
|
|
7560
|
+
|
|
7561
|
+
// src/lib/console.ts
|
|
7562
|
+
function enableConsoleCapture(page, sessionId) {
|
|
7563
|
+
const onConsole = (msg) => {
|
|
7564
|
+
const levelMap = {
|
|
7565
|
+
log: "log",
|
|
7566
|
+
warn: "warn",
|
|
7567
|
+
error: "error",
|
|
7568
|
+
debug: "debug",
|
|
7569
|
+
info: "info",
|
|
7570
|
+
warning: "warn"
|
|
7571
|
+
};
|
|
7572
|
+
const level = levelMap[msg.type()] ?? "log";
|
|
7573
|
+
const location = msg.location();
|
|
7574
|
+
try {
|
|
7575
|
+
logConsoleMessage({
|
|
7576
|
+
session_id: sessionId,
|
|
7577
|
+
level,
|
|
7578
|
+
message: msg.text(),
|
|
7579
|
+
source: location.url || undefined,
|
|
7580
|
+
line_number: location.lineNumber || undefined
|
|
7581
|
+
});
|
|
7582
|
+
} catch {}
|
|
7583
|
+
};
|
|
7584
|
+
page.on("console", onConsole);
|
|
7585
|
+
return () => page.off("console", onConsole);
|
|
7586
|
+
}
|
|
7587
|
+
async function capturePageErrors(page, sessionId) {
|
|
7588
|
+
const onError = (err) => {
|
|
7589
|
+
try {
|
|
7590
|
+
logConsoleMessage({
|
|
7591
|
+
session_id: sessionId,
|
|
7592
|
+
level: "error",
|
|
7593
|
+
message: `[Page Error] ${err.message}`,
|
|
7594
|
+
source: err.stack?.split(`
|
|
7595
|
+
`)[1]?.trim()
|
|
7596
|
+
});
|
|
7597
|
+
} catch {}
|
|
7598
|
+
};
|
|
7599
|
+
page.on("pageerror", onError);
|
|
7600
|
+
return () => page.off("pageerror", onError);
|
|
7601
|
+
}
|
|
7602
|
+
|
|
7603
|
+
// src/lib/stealth.ts
|
|
7604
|
+
var REALISTIC_USER_AGENT = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.0.0 Safari/537.36";
|
|
7605
|
+
var STEALTH_SCRIPT = `
|
|
7606
|
+
// \u2500\u2500 1. Remove navigator.webdriver flag \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
7607
|
+
Object.defineProperty(navigator, 'webdriver', {
|
|
7608
|
+
get: () => false,
|
|
7609
|
+
configurable: true,
|
|
7610
|
+
});
|
|
7611
|
+
|
|
7612
|
+
// \u2500\u2500 2. Override navigator.plugins to show typical Chrome plugins \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
7613
|
+
Object.defineProperty(navigator, 'plugins', {
|
|
7614
|
+
get: () => {
|
|
7615
|
+
const plugins = [
|
|
7616
|
+
{ name: 'Chrome PDF Plugin', filename: 'internal-pdf-viewer', description: 'Portable Document Format', length: 1 },
|
|
7617
|
+
{ name: 'Chrome PDF Viewer', filename: 'mhjfbmdgcfjbbpaeojofohoefgiehjai', description: '', length: 1 },
|
|
7618
|
+
{ name: 'Native Client', filename: 'internal-nacl-plugin', description: '', length: 2 },
|
|
7619
|
+
];
|
|
7620
|
+
// Mimic PluginArray interface
|
|
7621
|
+
const pluginArray = Object.create(PluginArray.prototype);
|
|
7622
|
+
plugins.forEach((p, i) => {
|
|
7623
|
+
const plugin = Object.create(Plugin.prototype);
|
|
7624
|
+
Object.defineProperties(plugin, {
|
|
7625
|
+
name: { value: p.name, enumerable: true },
|
|
7626
|
+
filename: { value: p.filename, enumerable: true },
|
|
7627
|
+
description: { value: p.description, enumerable: true },
|
|
7628
|
+
length: { value: p.length, enumerable: true },
|
|
7629
|
+
});
|
|
7630
|
+
pluginArray[i] = plugin;
|
|
7631
|
+
});
|
|
7632
|
+
Object.defineProperty(pluginArray, 'length', { value: plugins.length });
|
|
7633
|
+
pluginArray.item = (i) => pluginArray[i] || null;
|
|
7634
|
+
pluginArray.namedItem = (name) => plugins.find(p => p.name === name) ? pluginArray[plugins.findIndex(p => p.name === name)] : null;
|
|
7635
|
+
pluginArray.refresh = () => {};
|
|
7636
|
+
return pluginArray;
|
|
7637
|
+
},
|
|
7638
|
+
configurable: true,
|
|
7639
|
+
});
|
|
7640
|
+
|
|
7641
|
+
// \u2500\u2500 3. Override navigator.languages \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
7642
|
+
Object.defineProperty(navigator, 'languages', {
|
|
7643
|
+
get: () => ['en-US', 'en'],
|
|
7644
|
+
configurable: true,
|
|
7645
|
+
});
|
|
7646
|
+
|
|
7647
|
+
// \u2500\u2500 4. Override chrome.runtime to appear like real Chrome \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
7648
|
+
if (!window.chrome) {
|
|
7649
|
+
window.chrome = {};
|
|
7650
|
+
}
|
|
7651
|
+
if (!window.chrome.runtime) {
|
|
7652
|
+
window.chrome.runtime = {
|
|
7653
|
+
connect: function() { return { onMessage: { addListener: function() {} }, postMessage: function() {} }; },
|
|
7654
|
+
sendMessage: function() {},
|
|
7655
|
+
onMessage: { addListener: function() {}, removeListener: function() {} },
|
|
7656
|
+
id: undefined,
|
|
7657
|
+
};
|
|
7658
|
+
}
|
|
7659
|
+
`;
|
|
7660
|
+
async function applyStealthPatches(page) {
|
|
7661
|
+
await page.context().addInitScript(STEALTH_SCRIPT);
|
|
7662
|
+
await page.context().setExtraHTTPHeaders({
|
|
7663
|
+
"User-Agent": REALISTIC_USER_AGENT
|
|
7664
|
+
});
|
|
7665
|
+
}
|
|
7666
|
+
|
|
7667
|
+
// src/lib/dialogs.ts
|
|
7668
|
+
var pendingDialogs = new Map;
|
|
7669
|
+
var AUTO_DISMISS_MS = 5000;
|
|
7670
|
+
function setupDialogHandler(page, sessionId) {
|
|
7671
|
+
const onDialog = (dialog) => {
|
|
7672
|
+
const info = {
|
|
7673
|
+
type: dialog.type(),
|
|
7674
|
+
message: dialog.message(),
|
|
7675
|
+
default_value: dialog.defaultValue(),
|
|
7676
|
+
timestamp: new Date().toISOString()
|
|
7677
|
+
};
|
|
7678
|
+
const autoTimer = setTimeout(() => {
|
|
7679
|
+
try {
|
|
7680
|
+
dialog.dismiss().catch(() => {});
|
|
7681
|
+
} catch {}
|
|
7682
|
+
const list = pendingDialogs.get(sessionId);
|
|
7683
|
+
if (list) {
|
|
7684
|
+
const idx = list.findIndex((p) => p.dialog === dialog);
|
|
7685
|
+
if (idx >= 0)
|
|
7686
|
+
list.splice(idx, 1);
|
|
7687
|
+
if (list.length === 0)
|
|
7688
|
+
pendingDialogs.delete(sessionId);
|
|
7689
|
+
}
|
|
7690
|
+
}, AUTO_DISMISS_MS);
|
|
7691
|
+
const pending = { dialog, info, autoTimer };
|
|
7692
|
+
if (!pendingDialogs.has(sessionId)) {
|
|
7693
|
+
pendingDialogs.set(sessionId, []);
|
|
7694
|
+
}
|
|
7695
|
+
pendingDialogs.get(sessionId).push(pending);
|
|
7696
|
+
};
|
|
7697
|
+
page.on("dialog", onDialog);
|
|
7698
|
+
return () => {
|
|
7699
|
+
page.off("dialog", onDialog);
|
|
7700
|
+
const list = pendingDialogs.get(sessionId);
|
|
7701
|
+
if (list) {
|
|
7702
|
+
for (const p of list)
|
|
7703
|
+
clearTimeout(p.autoTimer);
|
|
7704
|
+
pendingDialogs.delete(sessionId);
|
|
7705
|
+
}
|
|
7706
|
+
};
|
|
7707
|
+
}
|
|
7708
|
+
|
|
7454
7709
|
// src/lib/session.ts
|
|
7455
7710
|
var handles = new Map;
|
|
7456
7711
|
async function createSession2(opts = {}) {
|
|
@@ -7477,13 +7732,33 @@ async function createSession2(opts = {}) {
|
|
|
7477
7732
|
engine: resolvedEngine,
|
|
7478
7733
|
projectId: opts.projectId,
|
|
7479
7734
|
agentId: opts.agentId,
|
|
7480
|
-
startUrl: opts.startUrl
|
|
7735
|
+
startUrl: opts.startUrl,
|
|
7736
|
+
name: opts.name ?? (opts.startUrl ? new URL(opts.startUrl).hostname : undefined)
|
|
7481
7737
|
});
|
|
7482
|
-
|
|
7738
|
+
if (opts.stealth) {
|
|
7739
|
+
try {
|
|
7740
|
+
await applyStealthPatches(page);
|
|
7741
|
+
} catch {}
|
|
7742
|
+
}
|
|
7743
|
+
const cleanups = [];
|
|
7744
|
+
if (opts.captureNetwork !== false) {
|
|
7745
|
+
try {
|
|
7746
|
+
cleanups.push(enableNetworkLogging(page, session.id));
|
|
7747
|
+
} catch {}
|
|
7748
|
+
}
|
|
7749
|
+
if (opts.captureConsole !== false) {
|
|
7750
|
+
try {
|
|
7751
|
+
cleanups.push(enableConsoleCapture(page, session.id));
|
|
7752
|
+
} catch {}
|
|
7753
|
+
}
|
|
7754
|
+
try {
|
|
7755
|
+
cleanups.push(setupDialogHandler(page, session.id));
|
|
7756
|
+
} catch {}
|
|
7757
|
+
handles.set(session.id, { browser, page, engine: resolvedEngine, cleanups, tokenBudget: { total: 0, used: 0 } });
|
|
7483
7758
|
if (opts.startUrl) {
|
|
7484
7759
|
try {
|
|
7485
7760
|
await page.goto(opts.startUrl, { waitUntil: "domcontentloaded" });
|
|
7486
|
-
} catch
|
|
7761
|
+
} catch {}
|
|
7487
7762
|
}
|
|
7488
7763
|
return { session, page };
|
|
7489
7764
|
}
|
|
@@ -7491,6 +7766,12 @@ function getSessionPage(sessionId) {
|
|
|
7491
7766
|
const handle = handles.get(sessionId);
|
|
7492
7767
|
if (!handle)
|
|
7493
7768
|
throw new SessionNotFoundError(sessionId);
|
|
7769
|
+
try {
|
|
7770
|
+
handle.page.url();
|
|
7771
|
+
} catch {
|
|
7772
|
+
handles.delete(sessionId);
|
|
7773
|
+
throw new SessionNotFoundError(sessionId);
|
|
7774
|
+
}
|
|
7494
7775
|
return handle.page;
|
|
7495
7776
|
}
|
|
7496
7777
|
function getSessionBrowser(sessionId) {
|
|
@@ -7508,9 +7789,20 @@ function getSessionEngine(sessionId) {
|
|
|
7508
7789
|
function hasActiveHandle(sessionId) {
|
|
7509
7790
|
return handles.has(sessionId);
|
|
7510
7791
|
}
|
|
7792
|
+
function setSessionPage(sessionId, page) {
|
|
7793
|
+
const handle = handles.get(sessionId);
|
|
7794
|
+
if (!handle)
|
|
7795
|
+
throw new SessionNotFoundError(sessionId);
|
|
7796
|
+
handle.page = page;
|
|
7797
|
+
}
|
|
7511
7798
|
async function closeSession2(sessionId) {
|
|
7512
7799
|
const handle = handles.get(sessionId);
|
|
7513
7800
|
if (handle) {
|
|
7801
|
+
for (const cleanup of handle.cleanups) {
|
|
7802
|
+
try {
|
|
7803
|
+
cleanup();
|
|
7804
|
+
} catch {}
|
|
7805
|
+
}
|
|
7514
7806
|
try {
|
|
7515
7807
|
await handle.page.context().close();
|
|
7516
7808
|
} catch {}
|
|
@@ -7538,6 +7830,23 @@ function getSessionByName2(name) {
|
|
|
7538
7830
|
function renameSession2(id, name) {
|
|
7539
7831
|
return renameSession(id, name);
|
|
7540
7832
|
}
|
|
7833
|
+
function getTokenBudget(sessionId) {
|
|
7834
|
+
const handle = handles.get(sessionId);
|
|
7835
|
+
return handle ? handle.tokenBudget : null;
|
|
7836
|
+
}
|
|
7837
|
+
// src/lib/snapshot.ts
|
|
7838
|
+
var lastSnapshots = new Map;
|
|
7839
|
+
var sessionRefMaps = new Map;
|
|
7840
|
+
function getRefLocator(page, sessionId, ref) {
|
|
7841
|
+
const refMap = sessionRefMaps.get(sessionId);
|
|
7842
|
+
if (!refMap)
|
|
7843
|
+
throw new Error(`No snapshot taken for session ${sessionId}. Call browser_snapshot first.`);
|
|
7844
|
+
const entry = refMap.get(ref);
|
|
7845
|
+
if (!entry)
|
|
7846
|
+
throw new Error(`Ref ${ref} not found. Available refs: ${[...refMap.keys()].slice(0, 20).join(", ")}`);
|
|
7847
|
+
return page.getByRole(entry.role, { name: entry.name }).first();
|
|
7848
|
+
}
|
|
7849
|
+
|
|
7541
7850
|
// src/lib/actions.ts
|
|
7542
7851
|
async function click(page, selector, opts) {
|
|
7543
7852
|
try {
|
|
@@ -7780,6 +8089,73 @@ function stopWatch(watchId) {
|
|
|
7780
8089
|
activeWatches.delete(watchId);
|
|
7781
8090
|
}
|
|
7782
8091
|
}
|
|
8092
|
+
async function clickRef(page, sessionId, ref, opts) {
|
|
8093
|
+
try {
|
|
8094
|
+
const locator = getRefLocator(page, sessionId, ref);
|
|
8095
|
+
await locator.click({ timeout: opts?.timeout ?? 1e4 });
|
|
8096
|
+
} catch (err) {
|
|
8097
|
+
if (err instanceof Error && err.message.includes("Ref "))
|
|
8098
|
+
throw new ElementNotFoundError(ref);
|
|
8099
|
+
if (err instanceof Error && err.message.includes("No snapshot"))
|
|
8100
|
+
throw new BrowserError(err.message, "NO_SNAPSHOT");
|
|
8101
|
+
throw new BrowserError(`clickRef ${ref} failed: ${err instanceof Error ? err.message : String(err)}`, "CLICK_REF_FAILED");
|
|
8102
|
+
}
|
|
8103
|
+
}
|
|
8104
|
+
async function typeRef(page, sessionId, ref, text, opts) {
|
|
8105
|
+
try {
|
|
8106
|
+
const locator = getRefLocator(page, sessionId, ref);
|
|
8107
|
+
if (opts?.clear)
|
|
8108
|
+
await locator.fill("", { timeout: opts.timeout ?? 1e4 });
|
|
8109
|
+
await locator.pressSequentially(text, { delay: opts?.delay, timeout: opts?.timeout ?? 1e4 });
|
|
8110
|
+
} catch (err) {
|
|
8111
|
+
if (err instanceof Error && err.message.includes("Ref "))
|
|
8112
|
+
throw new ElementNotFoundError(ref);
|
|
8113
|
+
throw new BrowserError(`typeRef ${ref} failed: ${err instanceof Error ? err.message : String(err)}`, "TYPE_REF_FAILED");
|
|
8114
|
+
}
|
|
8115
|
+
}
|
|
8116
|
+
async function fillRef(page, sessionId, ref, value, timeout = 1e4) {
|
|
8117
|
+
try {
|
|
8118
|
+
const locator = getRefLocator(page, sessionId, ref);
|
|
8119
|
+
await locator.fill(value, { timeout });
|
|
8120
|
+
} catch (err) {
|
|
8121
|
+
if (err instanceof Error && err.message.includes("Ref "))
|
|
8122
|
+
throw new ElementNotFoundError(ref);
|
|
8123
|
+
throw new BrowserError(`fillRef ${ref} failed: ${err instanceof Error ? err.message : String(err)}`, "FILL_REF_FAILED");
|
|
8124
|
+
}
|
|
8125
|
+
}
|
|
8126
|
+
async function selectRef(page, sessionId, ref, value, timeout = 1e4) {
|
|
8127
|
+
try {
|
|
8128
|
+
const locator = getRefLocator(page, sessionId, ref);
|
|
8129
|
+
return await locator.selectOption(value, { timeout });
|
|
8130
|
+
} catch (err) {
|
|
8131
|
+
if (err instanceof Error && err.message.includes("Ref "))
|
|
8132
|
+
throw new ElementNotFoundError(ref);
|
|
8133
|
+
throw new BrowserError(`selectRef ${ref} failed: ${err instanceof Error ? err.message : String(err)}`, "SELECT_REF_FAILED");
|
|
8134
|
+
}
|
|
8135
|
+
}
|
|
8136
|
+
async function checkRef(page, sessionId, ref, checked, timeout = 1e4) {
|
|
8137
|
+
try {
|
|
8138
|
+
const locator = getRefLocator(page, sessionId, ref);
|
|
8139
|
+
if (checked)
|
|
8140
|
+
await locator.check({ timeout });
|
|
8141
|
+
else
|
|
8142
|
+
await locator.uncheck({ timeout });
|
|
8143
|
+
} catch (err) {
|
|
8144
|
+
if (err instanceof Error && err.message.includes("Ref "))
|
|
8145
|
+
throw new ElementNotFoundError(ref);
|
|
8146
|
+
throw new BrowserError(`checkRef ${ref} failed: ${err instanceof Error ? err.message : String(err)}`, "CHECK_REF_FAILED");
|
|
8147
|
+
}
|
|
8148
|
+
}
|
|
8149
|
+
async function hoverRef(page, sessionId, ref, timeout = 1e4) {
|
|
8150
|
+
try {
|
|
8151
|
+
const locator = getRefLocator(page, sessionId, ref);
|
|
8152
|
+
await locator.hover({ timeout });
|
|
8153
|
+
} catch (err) {
|
|
8154
|
+
if (err instanceof Error && err.message.includes("Ref "))
|
|
8155
|
+
throw new ElementNotFoundError(ref);
|
|
8156
|
+
throw new BrowserError(`hoverRef ${ref} failed: ${err instanceof Error ? err.message : String(err)}`, "HOVER_REF_FAILED");
|
|
8157
|
+
}
|
|
8158
|
+
}
|
|
7783
8159
|
// src/lib/extractor.ts
|
|
7784
8160
|
async function getText(page, selector) {
|
|
7785
8161
|
if (selector) {
|
|
@@ -7934,112 +8310,6 @@ async function getPageInfo(page) {
|
|
|
7934
8310
|
viewport
|
|
7935
8311
|
};
|
|
7936
8312
|
}
|
|
7937
|
-
// src/lib/network.ts
|
|
7938
|
-
function enableNetworkLogging(page, sessionId) {
|
|
7939
|
-
const requestStart = new Map;
|
|
7940
|
-
const onRequest = (req) => {
|
|
7941
|
-
requestStart.set(req.url(), Date.now());
|
|
7942
|
-
};
|
|
7943
|
-
const onResponse = (res) => {
|
|
7944
|
-
const start = requestStart.get(res.url()) ?? Date.now();
|
|
7945
|
-
const duration = Date.now() - start;
|
|
7946
|
-
const req = res.request();
|
|
7947
|
-
try {
|
|
7948
|
-
logRequest({
|
|
7949
|
-
session_id: sessionId,
|
|
7950
|
-
method: req.method(),
|
|
7951
|
-
url: res.url(),
|
|
7952
|
-
status_code: res.status(),
|
|
7953
|
-
request_headers: JSON.stringify(req.headers()),
|
|
7954
|
-
response_headers: JSON.stringify(res.headers()),
|
|
7955
|
-
body_size: res.headers()["content-length"] != null ? parseInt(res.headers()["content-length"]) : undefined,
|
|
7956
|
-
duration_ms: duration,
|
|
7957
|
-
resource_type: req.resourceType()
|
|
7958
|
-
});
|
|
7959
|
-
} catch {}
|
|
7960
|
-
};
|
|
7961
|
-
page.on("request", onRequest);
|
|
7962
|
-
page.on("response", onResponse);
|
|
7963
|
-
return () => {
|
|
7964
|
-
page.off("request", onRequest);
|
|
7965
|
-
page.off("response", onResponse);
|
|
7966
|
-
};
|
|
7967
|
-
}
|
|
7968
|
-
async function addInterceptRule(page, rule) {
|
|
7969
|
-
await page.route(rule.pattern, async (route) => {
|
|
7970
|
-
if (rule.action === "block") {
|
|
7971
|
-
await route.abort();
|
|
7972
|
-
} else if (rule.action === "modify" && rule.response) {
|
|
7973
|
-
await route.fulfill({
|
|
7974
|
-
status: rule.response.status,
|
|
7975
|
-
body: rule.response.body,
|
|
7976
|
-
headers: rule.response.headers
|
|
7977
|
-
});
|
|
7978
|
-
} else {
|
|
7979
|
-
await route.continue();
|
|
7980
|
-
}
|
|
7981
|
-
});
|
|
7982
|
-
}
|
|
7983
|
-
async function clearInterceptRules(page) {
|
|
7984
|
-
await page.unrouteAll();
|
|
7985
|
-
}
|
|
7986
|
-
function startHAR(page) {
|
|
7987
|
-
const entries = [];
|
|
7988
|
-
const requestStart = new Map;
|
|
7989
|
-
const onRequest = (req) => {
|
|
7990
|
-
requestStart.set(req.url() + req.method(), {
|
|
7991
|
-
time: Date.now(),
|
|
7992
|
-
method: req.method(),
|
|
7993
|
-
headers: req.headers(),
|
|
7994
|
-
postData: req.postData() ?? undefined
|
|
7995
|
-
});
|
|
7996
|
-
};
|
|
7997
|
-
const onResponse = async (res) => {
|
|
7998
|
-
const key = res.url() + res.request().method();
|
|
7999
|
-
const start = requestStart.get(key);
|
|
8000
|
-
if (!start)
|
|
8001
|
-
return;
|
|
8002
|
-
const duration = Date.now() - start.time;
|
|
8003
|
-
const entry = {
|
|
8004
|
-
startedDateTime: new Date(start.time).toISOString(),
|
|
8005
|
-
time: duration,
|
|
8006
|
-
request: {
|
|
8007
|
-
method: start.method,
|
|
8008
|
-
url: res.url(),
|
|
8009
|
-
headers: Object.entries(start.headers).map(([name, value]) => ({ name, value })),
|
|
8010
|
-
postData: start.postData ? { text: start.postData } : undefined
|
|
8011
|
-
},
|
|
8012
|
-
response: {
|
|
8013
|
-
status: res.status(),
|
|
8014
|
-
statusText: res.statusText(),
|
|
8015
|
-
headers: Object.entries(res.headers()).map(([name, value]) => ({ name, value })),
|
|
8016
|
-
content: {
|
|
8017
|
-
size: parseInt(res.headers()["content-length"] ?? "0") || 0,
|
|
8018
|
-
mimeType: res.headers()["content-type"] ?? "application/octet-stream"
|
|
8019
|
-
}
|
|
8020
|
-
},
|
|
8021
|
-
timings: { send: 0, wait: duration, receive: 0 }
|
|
8022
|
-
};
|
|
8023
|
-
entries.push(entry);
|
|
8024
|
-
requestStart.delete(key);
|
|
8025
|
-
};
|
|
8026
|
-
page.on("request", onRequest);
|
|
8027
|
-
page.on("response", onResponse);
|
|
8028
|
-
return {
|
|
8029
|
-
entries,
|
|
8030
|
-
stop: () => {
|
|
8031
|
-
page.off("request", onRequest);
|
|
8032
|
-
page.off("response", onResponse);
|
|
8033
|
-
return {
|
|
8034
|
-
log: {
|
|
8035
|
-
version: "1.2",
|
|
8036
|
-
creator: { name: "@hasna/browser", version: "0.0.1" },
|
|
8037
|
-
entries
|
|
8038
|
-
}
|
|
8039
|
-
};
|
|
8040
|
-
}
|
|
8041
|
-
};
|
|
8042
|
-
}
|
|
8043
8313
|
// src/lib/performance.ts
|
|
8044
8314
|
async function getPerformanceMetrics(page) {
|
|
8045
8315
|
const navTiming = await page.evaluate(() => {
|
|
@@ -8127,47 +8397,6 @@ async function startCoverage(page) {
|
|
|
8127
8397
|
}
|
|
8128
8398
|
};
|
|
8129
8399
|
}
|
|
8130
|
-
// src/lib/console.ts
|
|
8131
|
-
function enableConsoleCapture(page, sessionId) {
|
|
8132
|
-
const onConsole = (msg) => {
|
|
8133
|
-
const levelMap = {
|
|
8134
|
-
log: "log",
|
|
8135
|
-
warn: "warn",
|
|
8136
|
-
error: "error",
|
|
8137
|
-
debug: "debug",
|
|
8138
|
-
info: "info",
|
|
8139
|
-
warning: "warn"
|
|
8140
|
-
};
|
|
8141
|
-
const level = levelMap[msg.type()] ?? "log";
|
|
8142
|
-
const location = msg.location();
|
|
8143
|
-
try {
|
|
8144
|
-
logConsoleMessage({
|
|
8145
|
-
session_id: sessionId,
|
|
8146
|
-
level,
|
|
8147
|
-
message: msg.text(),
|
|
8148
|
-
source: location.url || undefined,
|
|
8149
|
-
line_number: location.lineNumber || undefined
|
|
8150
|
-
});
|
|
8151
|
-
} catch {}
|
|
8152
|
-
};
|
|
8153
|
-
page.on("console", onConsole);
|
|
8154
|
-
return () => page.off("console", onConsole);
|
|
8155
|
-
}
|
|
8156
|
-
async function capturePageErrors(page, sessionId) {
|
|
8157
|
-
const onError = (err) => {
|
|
8158
|
-
try {
|
|
8159
|
-
logConsoleMessage({
|
|
8160
|
-
session_id: sessionId,
|
|
8161
|
-
level: "error",
|
|
8162
|
-
message: `[Page Error] ${err.message}`,
|
|
8163
|
-
source: err.stack?.split(`
|
|
8164
|
-
`)[1]?.trim()
|
|
8165
|
-
});
|
|
8166
|
-
} catch {}
|
|
8167
|
-
};
|
|
8168
|
-
page.on("pageerror", onError);
|
|
8169
|
-
return () => page.off("pageerror", onError);
|
|
8170
|
-
}
|
|
8171
8400
|
// src/lib/screenshot.ts
|
|
8172
8401
|
var import_sharp = __toESM(require_lib(), 1);
|
|
8173
8402
|
import { join as join2 } from "path";
|
|
@@ -8673,6 +8902,7 @@ export {
|
|
|
8673
8902
|
updateRecording,
|
|
8674
8903
|
updateProject,
|
|
8675
8904
|
updateAgent,
|
|
8905
|
+
typeRef,
|
|
8676
8906
|
type,
|
|
8677
8907
|
takeScreenshot,
|
|
8678
8908
|
stopWatch,
|
|
@@ -8682,8 +8912,10 @@ export {
|
|
|
8682
8912
|
startHAR,
|
|
8683
8913
|
startCoverage,
|
|
8684
8914
|
setSessionStorage,
|
|
8915
|
+
setSessionPage,
|
|
8685
8916
|
setLocalStorage,
|
|
8686
8917
|
setCookie,
|
|
8918
|
+
selectRef,
|
|
8687
8919
|
selectOption,
|
|
8688
8920
|
selectEngine,
|
|
8689
8921
|
scrollTo,
|
|
@@ -8712,6 +8944,7 @@ export {
|
|
|
8712
8944
|
isEngineAvailable,
|
|
8713
8945
|
isAgentStale,
|
|
8714
8946
|
inferUseCase,
|
|
8947
|
+
hoverRef,
|
|
8715
8948
|
hover,
|
|
8716
8949
|
heartbeat2 as heartbeat,
|
|
8717
8950
|
hasActiveHandle,
|
|
@@ -8719,6 +8952,7 @@ export {
|
|
|
8719
8952
|
goBack,
|
|
8720
8953
|
getWatchChanges,
|
|
8721
8954
|
getUrl,
|
|
8955
|
+
getTokenBudget,
|
|
8722
8956
|
getTitle,
|
|
8723
8957
|
getTimingEntries,
|
|
8724
8958
|
getText,
|
|
@@ -8758,6 +8992,7 @@ export {
|
|
|
8758
8992
|
getActiveAgents,
|
|
8759
8993
|
generatePDF,
|
|
8760
8994
|
findElements,
|
|
8995
|
+
fillRef,
|
|
8761
8996
|
fillForm,
|
|
8762
8997
|
fill,
|
|
8763
8998
|
extractTable,
|
|
@@ -8795,6 +9030,7 @@ export {
|
|
|
8795
9030
|
closeBrowser,
|
|
8796
9031
|
closeAllSessions,
|
|
8797
9032
|
clickText,
|
|
9033
|
+
clickRef,
|
|
8798
9034
|
click,
|
|
8799
9035
|
clearSessionStorage,
|
|
8800
9036
|
clearNetworkLog,
|
|
@@ -8804,6 +9040,7 @@ export {
|
|
|
8804
9040
|
clearConsoleLog,
|
|
8805
9041
|
cleanStaleAgents,
|
|
8806
9042
|
cleanOldHeartbeats,
|
|
9043
|
+
checkRef,
|
|
8807
9044
|
checkBox,
|
|
8808
9045
|
capturePageErrors,
|
|
8809
9046
|
attachPageListeners,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"actions-ref.test.d.ts","sourceRoot":"","sources":["../../src/lib/actions-ref.test.ts"],"names":[],"mappings":""}
|
package/dist/lib/actions.d.ts
CHANGED
|
@@ -58,4 +58,16 @@ export declare function watchPage(page: Page, opts?: {
|
|
|
58
58
|
}): WatchHandle;
|
|
59
59
|
export declare function getWatchChanges(watchId: string): string[];
|
|
60
60
|
export declare function stopWatch(watchId: string): void;
|
|
61
|
+
export declare function clickRef(page: Page, sessionId: string, ref: string, opts?: {
|
|
62
|
+
timeout?: number;
|
|
63
|
+
}): Promise<void>;
|
|
64
|
+
export declare function typeRef(page: Page, sessionId: string, ref: string, text: string, opts?: {
|
|
65
|
+
delay?: number;
|
|
66
|
+
clear?: boolean;
|
|
67
|
+
timeout?: number;
|
|
68
|
+
}): Promise<void>;
|
|
69
|
+
export declare function fillRef(page: Page, sessionId: string, ref: string, value: string, timeout?: number): Promise<void>;
|
|
70
|
+
export declare function selectRef(page: Page, sessionId: string, ref: string, value: string, timeout?: number): Promise<string[]>;
|
|
71
|
+
export declare function checkRef(page: Page, sessionId: string, ref: string, checked: boolean, timeout?: number): Promise<void>;
|
|
72
|
+
export declare function hoverRef(page: Page, sessionId: string, ref: string, timeout?: number): Promise<void>;
|
|
61
73
|
//# sourceMappingURL=actions.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../../src/lib/actions.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"actions.d.ts","sourceRoot":"","sources":["../../src/lib/actions.ts"],"names":[],"mappings":"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"}
|