@hasna/browser 0.4.0 → 0.4.1
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 +99 -2
- package/dist/mcp/capture.d.ts.map +1 -1
- package/dist/mcp/index.js +157 -51
- package/dist/mcp/meta.d.ts.map +1 -1
- package/dist/mcp/network.d.ts.map +1 -1
- package/dist/mcp/recordings.d.ts.map +1 -1
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -28807,6 +28807,40 @@ function register6(server) {
|
|
|
28807
28807
|
return err(e);
|
|
28808
28808
|
}
|
|
28809
28809
|
});
|
|
28810
|
+
server.tool("browser_diff", "Visual diff between two URLs or a URL and a gallery entry. Screenshots both, returns a pixel diff image highlighting changes in red.", {
|
|
28811
|
+
session_id: exports_external2.string().optional(),
|
|
28812
|
+
url1: exports_external2.string().describe("First URL to screenshot"),
|
|
28813
|
+
url2: exports_external2.string().describe("Second URL to screenshot"),
|
|
28814
|
+
threshold: exports_external2.number().optional().default(10).describe("Pixel difference threshold (0-255, default 10)"),
|
|
28815
|
+
wait_ms: exports_external2.number().optional().default(1000).describe("Wait time after navigation before screenshot (ms)")
|
|
28816
|
+
}, async ({ session_id, url1, url2, threshold, wait_ms }) => {
|
|
28817
|
+
try {
|
|
28818
|
+
const sid = resolveSessionId(session_id);
|
|
28819
|
+
const page = getSessionPage(sid);
|
|
28820
|
+
await page.goto(url1, { waitUntil: "domcontentloaded" });
|
|
28821
|
+
await new Promise((r) => setTimeout(r, wait_ms));
|
|
28822
|
+
const ss1 = await takeScreenshot(page, { maxWidth: 1280, compress: true, track: false });
|
|
28823
|
+
await page.goto(url2, { waitUntil: "domcontentloaded" });
|
|
28824
|
+
await new Promise((r) => setTimeout(r, wait_ms));
|
|
28825
|
+
const ss2 = await takeScreenshot(page, { maxWidth: 1280, compress: true, track: false });
|
|
28826
|
+
const { diffImages: diffImages2 } = await Promise.resolve().then(() => (init_gallery_diff(), exports_gallery_diff));
|
|
28827
|
+
const diff = await diffImages2(ss1.path, ss2.path);
|
|
28828
|
+
logEvent(sid, "diff", { url1, url2, changed_percent: diff.changed_percent });
|
|
28829
|
+
return json({
|
|
28830
|
+
url1,
|
|
28831
|
+
url2,
|
|
28832
|
+
changed_pixels: diff.changed_pixels,
|
|
28833
|
+
total_pixels: diff.total_pixels,
|
|
28834
|
+
changed_percent: Math.round(diff.changed_percent * 100) / 100,
|
|
28835
|
+
diff_path: diff.diff_path,
|
|
28836
|
+
diff_base64: diff.diff_base64.length > 50000 ? undefined : diff.diff_base64,
|
|
28837
|
+
screenshot1_path: ss1.path,
|
|
28838
|
+
screenshot2_path: ss2.path
|
|
28839
|
+
});
|
|
28840
|
+
} catch (e) {
|
|
28841
|
+
return err(e);
|
|
28842
|
+
}
|
|
28843
|
+
});
|
|
28810
28844
|
}
|
|
28811
28845
|
var init_capture = __esm(() => {
|
|
28812
28846
|
init_helpers();
|
|
@@ -31942,6 +31976,53 @@ function register7(server) {
|
|
|
31942
31976
|
return err(e);
|
|
31943
31977
|
}
|
|
31944
31978
|
});
|
|
31979
|
+
server.tool("browser_performance_budget", "Check page performance against a budget. Set thresholds for LCP, FCP, CLS, TTFB, DOM complete, and load event. Returns pass/fail per metric with actual values.", {
|
|
31980
|
+
session_id: exports_external2.string().optional(),
|
|
31981
|
+
lcp_ms: exports_external2.number().optional().describe("Largest Contentful Paint budget in ms (good: <2500)"),
|
|
31982
|
+
fcp_ms: exports_external2.number().optional().describe("First Contentful Paint budget in ms (good: <1800)"),
|
|
31983
|
+
cls: exports_external2.number().optional().describe("Cumulative Layout Shift budget (good: <0.1)"),
|
|
31984
|
+
ttfb_ms: exports_external2.number().optional().describe("Time to First Byte budget in ms (good: <800)"),
|
|
31985
|
+
dom_complete_ms: exports_external2.number().optional().describe("DOM complete budget in ms"),
|
|
31986
|
+
load_event_ms: exports_external2.number().optional().describe("Load event budget in ms"),
|
|
31987
|
+
js_heap_mb: exports_external2.number().optional().describe("JS heap size budget in MB")
|
|
31988
|
+
}, async ({ session_id, lcp_ms, fcp_ms, cls, ttfb_ms, dom_complete_ms, load_event_ms, js_heap_mb }) => {
|
|
31989
|
+
try {
|
|
31990
|
+
const sid = resolveSessionId(session_id);
|
|
31991
|
+
const page = getSessionPage(sid);
|
|
31992
|
+
const metrics = await getPerformanceMetrics(page);
|
|
31993
|
+
const checks = [];
|
|
31994
|
+
let allPassed = true;
|
|
31995
|
+
const check = (name, budget, actual) => {
|
|
31996
|
+
if (budget === undefined)
|
|
31997
|
+
return;
|
|
31998
|
+
const passed = actual !== undefined && actual <= budget;
|
|
31999
|
+
if (!passed)
|
|
32000
|
+
allPassed = false;
|
|
32001
|
+
checks.push({ metric: name, budget, actual, passed });
|
|
32002
|
+
};
|
|
32003
|
+
check("lcp", lcp_ms, metrics.lcp);
|
|
32004
|
+
check("fcp", fcp_ms, metrics.fcp);
|
|
32005
|
+
check("cls", cls, metrics.cls);
|
|
32006
|
+
check("ttfb", ttfb_ms, metrics.ttfb);
|
|
32007
|
+
check("dom_complete", dom_complete_ms, metrics.dom_complete);
|
|
32008
|
+
check("load_event", load_event_ms, metrics.load_event);
|
|
32009
|
+
if (js_heap_mb !== undefined && metrics.js_heap_size_used !== undefined) {
|
|
32010
|
+
const heapMb = metrics.js_heap_size_used / (1024 * 1024);
|
|
32011
|
+
const passed = heapMb <= js_heap_mb;
|
|
32012
|
+
if (!passed)
|
|
32013
|
+
allPassed = false;
|
|
32014
|
+
checks.push({ metric: "js_heap_mb", budget: js_heap_mb, actual: Math.round(heapMb * 100) / 100, passed });
|
|
32015
|
+
}
|
|
32016
|
+
return json({
|
|
32017
|
+
passed: allPassed,
|
|
32018
|
+
checks,
|
|
32019
|
+
metrics,
|
|
32020
|
+
url: page.url()
|
|
32021
|
+
});
|
|
32022
|
+
} catch (e) {
|
|
32023
|
+
return err(e);
|
|
32024
|
+
}
|
|
32025
|
+
});
|
|
31945
32026
|
}
|
|
31946
32027
|
var init_network2 = __esm(() => {
|
|
31947
32028
|
init_helpers();
|
|
@@ -32390,6 +32471,19 @@ function register8(server) {
|
|
|
32390
32471
|
return err(e);
|
|
32391
32472
|
}
|
|
32392
32473
|
});
|
|
32474
|
+
server.tool("browser_record_export", "Export a recording as a Playwright test (.spec.ts), Puppeteer script, or JSON. Returns the generated code as text.", {
|
|
32475
|
+
recording_id: exports_external2.string().describe("ID of the recording to export"),
|
|
32476
|
+
format: exports_external2.enum(["playwright", "puppeteer", "json"]).optional().default("playwright").describe("Export format")
|
|
32477
|
+
}, async ({ recording_id, format }) => {
|
|
32478
|
+
try {
|
|
32479
|
+
const { exportRecording: exportRecording2 } = await Promise.resolve().then(() => (init_recorder(), exports_recorder));
|
|
32480
|
+
const code = exportRecording2(recording_id, format);
|
|
32481
|
+
const ext = format === "json" ? ".json" : format === "playwright" ? ".spec.ts" : ".js";
|
|
32482
|
+
return json({ format, filename: `recording-${recording_id}${ext}`, code });
|
|
32483
|
+
} catch (e) {
|
|
32484
|
+
return err(e);
|
|
32485
|
+
}
|
|
32486
|
+
});
|
|
32393
32487
|
}
|
|
32394
32488
|
var init_recordings2 = __esm(() => {
|
|
32395
32489
|
init_helpers();
|
|
@@ -47967,7 +48061,8 @@ function register10(server) {
|
|
|
47967
48061
|
{ tool: "browser_screenshot", description: "Take a screenshot (PNG/JPEG/WebP, annotate=true for labels)" },
|
|
47968
48062
|
{ tool: "browser_pdf", description: "Generate a PDF of the page" },
|
|
47969
48063
|
{ tool: "browser_scroll_and_screenshot", description: "Scroll then screenshot in one call" },
|
|
47970
|
-
{ tool: "browser_scroll_to_element", description: "Scroll element into view + screenshot" }
|
|
48064
|
+
{ tool: "browser_scroll_to_element", description: "Scroll element into view + screenshot" },
|
|
48065
|
+
{ tool: "browser_diff", description: "Visual diff between two URLs \u2014 highlights changes in red" }
|
|
47971
48066
|
],
|
|
47972
48067
|
Storage: [
|
|
47973
48068
|
{ tool: "browser_cookies_get", description: "Get cookies" },
|
|
@@ -47992,7 +48087,8 @@ function register10(server) {
|
|
|
47992
48087
|
{ tool: "browser_intercept_clear", description: "Remove all response intercepts" }
|
|
47993
48088
|
],
|
|
47994
48089
|
Performance: [
|
|
47995
|
-
{ tool: "browser_performance", description: "Get performance metrics" }
|
|
48090
|
+
{ tool: "browser_performance", description: "Get performance metrics" },
|
|
48091
|
+
{ tool: "browser_performance_budget", description: "Check perf against budget thresholds (LCP, FCP, CLS, TTFB)" }
|
|
47996
48092
|
],
|
|
47997
48093
|
Console: [
|
|
47998
48094
|
{ tool: "browser_console_log", description: "Get console messages" },
|
|
@@ -48005,6 +48101,7 @@ function register10(server) {
|
|
|
48005
48101
|
{ tool: "browser_record_step", description: "Add a step to recording" },
|
|
48006
48102
|
{ tool: "browser_record_stop", description: "Stop and save recording" },
|
|
48007
48103
|
{ tool: "browser_record_replay", description: "Replay a recorded sequence" },
|
|
48104
|
+
{ tool: "browser_record_export", description: "Export recording as Playwright test, Puppeteer script, or JSON" },
|
|
48008
48105
|
{ tool: "browser_recordings_list", description: "List all recordings" }
|
|
48009
48106
|
],
|
|
48010
48107
|
Auth: [
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"capture.d.ts","sourceRoot":"","sources":["../../src/mcp/capture.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AA6BzE,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,
|
|
1
|
+
{"version":3,"file":"capture.d.ts","sourceRoot":"","sources":["../../src/mcp/capture.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AA6BzE,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,QAogBzC"}
|
package/dist/mcp/index.js
CHANGED
|
@@ -19493,6 +19493,63 @@ var init_agents2 = __esm(() => {
|
|
|
19493
19493
|
init_agents();
|
|
19494
19494
|
});
|
|
19495
19495
|
|
|
19496
|
+
// src/lib/gallery-diff.ts
|
|
19497
|
+
var exports_gallery_diff = {};
|
|
19498
|
+
__export(exports_gallery_diff, {
|
|
19499
|
+
diffImages: () => diffImages
|
|
19500
|
+
});
|
|
19501
|
+
import { join as join11 } from "path";
|
|
19502
|
+
import { mkdirSync as mkdirSync9 } from "fs";
|
|
19503
|
+
async function diffImages(path1, path2) {
|
|
19504
|
+
const img1 = import_sharp2.default(path1);
|
|
19505
|
+
const img2 = import_sharp2.default(path2);
|
|
19506
|
+
const [meta1, meta2] = await Promise.all([img1.metadata(), img2.metadata()]);
|
|
19507
|
+
const w = Math.min(meta1.width ?? 1280, meta2.width ?? 1280);
|
|
19508
|
+
const h = Math.min(meta1.height ?? 720, meta2.height ?? 720);
|
|
19509
|
+
const [raw1, raw2] = await Promise.all([
|
|
19510
|
+
import_sharp2.default(path1).resize(w, h, { fit: "fill" }).raw().toBuffer(),
|
|
19511
|
+
import_sharp2.default(path2).resize(w, h, { fit: "fill" }).raw().toBuffer()
|
|
19512
|
+
]);
|
|
19513
|
+
const totalPixels = w * h;
|
|
19514
|
+
const channels = 3;
|
|
19515
|
+
const diffBuffer = Buffer.alloc(raw1.length);
|
|
19516
|
+
let changedPixels = 0;
|
|
19517
|
+
for (let i = 0;i < raw1.length; i += channels) {
|
|
19518
|
+
const dr = Math.abs(raw1[i] - raw2[i]);
|
|
19519
|
+
const dg = Math.abs(raw1[i + 1] - raw2[i + 1]);
|
|
19520
|
+
const db = Math.abs(raw1[i + 2] - raw2[i + 2]);
|
|
19521
|
+
const diff = (dr + dg + db) / 3;
|
|
19522
|
+
if (diff > 10) {
|
|
19523
|
+
changedPixels++;
|
|
19524
|
+
diffBuffer[i] = 255;
|
|
19525
|
+
diffBuffer[i + 1] = 0;
|
|
19526
|
+
diffBuffer[i + 2] = 0;
|
|
19527
|
+
} else {
|
|
19528
|
+
diffBuffer[i] = Math.round(raw1[i] * 0.4);
|
|
19529
|
+
diffBuffer[i + 1] = Math.round(raw1[i + 1] * 0.4);
|
|
19530
|
+
diffBuffer[i + 2] = Math.round(raw1[i + 2] * 0.4);
|
|
19531
|
+
}
|
|
19532
|
+
}
|
|
19533
|
+
const dataDir = getDataDir2();
|
|
19534
|
+
const diffDir = join11(dataDir, "diffs");
|
|
19535
|
+
mkdirSync9(diffDir, { recursive: true });
|
|
19536
|
+
const diffPath = join11(diffDir, `diff-${Date.now()}.webp`);
|
|
19537
|
+
const diffImageBuffer = await import_sharp2.default(diffBuffer, { raw: { width: w, height: h, channels } }).webp({ quality: 85 }).toBuffer();
|
|
19538
|
+
await Bun.write(diffPath, diffImageBuffer);
|
|
19539
|
+
return {
|
|
19540
|
+
diff_path: diffPath,
|
|
19541
|
+
diff_base64: diffImageBuffer.toString("base64"),
|
|
19542
|
+
changed_pixels: changedPixels,
|
|
19543
|
+
total_pixels: totalPixels,
|
|
19544
|
+
changed_percent: changedPixels / totalPixels * 100
|
|
19545
|
+
};
|
|
19546
|
+
}
|
|
19547
|
+
var import_sharp2;
|
|
19548
|
+
var init_gallery_diff = __esm(() => {
|
|
19549
|
+
init_schema();
|
|
19550
|
+
import_sharp2 = __toESM(require_lib3(), 1);
|
|
19551
|
+
});
|
|
19552
|
+
|
|
19496
19553
|
// src/lib/profiles.ts
|
|
19497
19554
|
var exports_profiles = {};
|
|
19498
19555
|
__export(exports_profiles, {
|
|
@@ -43580,57 +43637,9 @@ function detectType(filename) {
|
|
|
43580
43637
|
};
|
|
43581
43638
|
return map[ext] ?? "file";
|
|
43582
43639
|
}
|
|
43583
|
-
// src/lib/gallery-diff.ts
|
|
43584
|
-
init_schema();
|
|
43585
|
-
var import_sharp2 = __toESM(require_lib3(), 1);
|
|
43586
|
-
import { join as join11 } from "path";
|
|
43587
|
-
import { mkdirSync as mkdirSync9 } from "fs";
|
|
43588
|
-
async function diffImages(path1, path2) {
|
|
43589
|
-
const img1 = import_sharp2.default(path1);
|
|
43590
|
-
const img2 = import_sharp2.default(path2);
|
|
43591
|
-
const [meta1, meta2] = await Promise.all([img1.metadata(), img2.metadata()]);
|
|
43592
|
-
const w = Math.min(meta1.width ?? 1280, meta2.width ?? 1280);
|
|
43593
|
-
const h = Math.min(meta1.height ?? 720, meta2.height ?? 720);
|
|
43594
|
-
const [raw1, raw2] = await Promise.all([
|
|
43595
|
-
import_sharp2.default(path1).resize(w, h, { fit: "fill" }).raw().toBuffer(),
|
|
43596
|
-
import_sharp2.default(path2).resize(w, h, { fit: "fill" }).raw().toBuffer()
|
|
43597
|
-
]);
|
|
43598
|
-
const totalPixels = w * h;
|
|
43599
|
-
const channels = 3;
|
|
43600
|
-
const diffBuffer = Buffer.alloc(raw1.length);
|
|
43601
|
-
let changedPixels = 0;
|
|
43602
|
-
for (let i = 0;i < raw1.length; i += channels) {
|
|
43603
|
-
const dr = Math.abs(raw1[i] - raw2[i]);
|
|
43604
|
-
const dg = Math.abs(raw1[i + 1] - raw2[i + 1]);
|
|
43605
|
-
const db = Math.abs(raw1[i + 2] - raw2[i + 2]);
|
|
43606
|
-
const diff = (dr + dg + db) / 3;
|
|
43607
|
-
if (diff > 10) {
|
|
43608
|
-
changedPixels++;
|
|
43609
|
-
diffBuffer[i] = 255;
|
|
43610
|
-
diffBuffer[i + 1] = 0;
|
|
43611
|
-
diffBuffer[i + 2] = 0;
|
|
43612
|
-
} else {
|
|
43613
|
-
diffBuffer[i] = Math.round(raw1[i] * 0.4);
|
|
43614
|
-
diffBuffer[i + 1] = Math.round(raw1[i + 1] * 0.4);
|
|
43615
|
-
diffBuffer[i + 2] = Math.round(raw1[i + 2] * 0.4);
|
|
43616
|
-
}
|
|
43617
|
-
}
|
|
43618
|
-
const dataDir = getDataDir2();
|
|
43619
|
-
const diffDir = join11(dataDir, "diffs");
|
|
43620
|
-
mkdirSync9(diffDir, { recursive: true });
|
|
43621
|
-
const diffPath = join11(diffDir, `diff-${Date.now()}.webp`);
|
|
43622
|
-
const diffImageBuffer = await import_sharp2.default(diffBuffer, { raw: { width: w, height: h, channels } }).webp({ quality: 85 }).toBuffer();
|
|
43623
|
-
await Bun.write(diffPath, diffImageBuffer);
|
|
43624
|
-
return {
|
|
43625
|
-
diff_path: diffPath,
|
|
43626
|
-
diff_base64: diffImageBuffer.toString("base64"),
|
|
43627
|
-
changed_pixels: changedPixels,
|
|
43628
|
-
total_pixels: totalPixels,
|
|
43629
|
-
changed_percent: changedPixels / totalPixels * 100
|
|
43630
|
-
};
|
|
43631
|
-
}
|
|
43632
43640
|
|
|
43633
43641
|
// src/mcp/helpers.ts
|
|
43642
|
+
init_gallery_diff();
|
|
43634
43643
|
init_snapshot();
|
|
43635
43644
|
|
|
43636
43645
|
// src/lib/files-integration.ts
|
|
@@ -44879,6 +44888,40 @@ function register3(server) {
|
|
|
44879
44888
|
return err(e);
|
|
44880
44889
|
}
|
|
44881
44890
|
});
|
|
44891
|
+
server.tool("browser_diff", "Visual diff between two URLs or a URL and a gallery entry. Screenshots both, returns a pixel diff image highlighting changes in red.", {
|
|
44892
|
+
session_id: exports_external.string().optional(),
|
|
44893
|
+
url1: exports_external.string().describe("First URL to screenshot"),
|
|
44894
|
+
url2: exports_external.string().describe("Second URL to screenshot"),
|
|
44895
|
+
threshold: exports_external.number().optional().default(10).describe("Pixel difference threshold (0-255, default 10)"),
|
|
44896
|
+
wait_ms: exports_external.number().optional().default(1000).describe("Wait time after navigation before screenshot (ms)")
|
|
44897
|
+
}, async ({ session_id, url1, url2, threshold, wait_ms }) => {
|
|
44898
|
+
try {
|
|
44899
|
+
const sid = resolveSessionId(session_id);
|
|
44900
|
+
const page = getSessionPage(sid);
|
|
44901
|
+
await page.goto(url1, { waitUntil: "domcontentloaded" });
|
|
44902
|
+
await new Promise((r) => setTimeout(r, wait_ms));
|
|
44903
|
+
const ss1 = await takeScreenshot(page, { maxWidth: 1280, compress: true, track: false });
|
|
44904
|
+
await page.goto(url2, { waitUntil: "domcontentloaded" });
|
|
44905
|
+
await new Promise((r) => setTimeout(r, wait_ms));
|
|
44906
|
+
const ss2 = await takeScreenshot(page, { maxWidth: 1280, compress: true, track: false });
|
|
44907
|
+
const { diffImages: diffImages2 } = await Promise.resolve().then(() => (init_gallery_diff(), exports_gallery_diff));
|
|
44908
|
+
const diff = await diffImages2(ss1.path, ss2.path);
|
|
44909
|
+
logEvent(sid, "diff", { url1, url2, changed_percent: diff.changed_percent });
|
|
44910
|
+
return json({
|
|
44911
|
+
url1,
|
|
44912
|
+
url2,
|
|
44913
|
+
changed_pixels: diff.changed_pixels,
|
|
44914
|
+
total_pixels: diff.total_pixels,
|
|
44915
|
+
changed_percent: Math.round(diff.changed_percent * 100) / 100,
|
|
44916
|
+
diff_path: diff.diff_path,
|
|
44917
|
+
diff_base64: diff.diff_base64.length > 50000 ? undefined : diff.diff_base64,
|
|
44918
|
+
screenshot1_path: ss1.path,
|
|
44919
|
+
screenshot2_path: ss2.path
|
|
44920
|
+
});
|
|
44921
|
+
} catch (e) {
|
|
44922
|
+
return err(e);
|
|
44923
|
+
}
|
|
44924
|
+
});
|
|
44882
44925
|
}
|
|
44883
44926
|
|
|
44884
44927
|
// src/mcp/network.ts
|
|
@@ -45223,6 +45266,53 @@ function register4(server) {
|
|
|
45223
45266
|
return err(e);
|
|
45224
45267
|
}
|
|
45225
45268
|
});
|
|
45269
|
+
server.tool("browser_performance_budget", "Check page performance against a budget. Set thresholds for LCP, FCP, CLS, TTFB, DOM complete, and load event. Returns pass/fail per metric with actual values.", {
|
|
45270
|
+
session_id: exports_external.string().optional(),
|
|
45271
|
+
lcp_ms: exports_external.number().optional().describe("Largest Contentful Paint budget in ms (good: <2500)"),
|
|
45272
|
+
fcp_ms: exports_external.number().optional().describe("First Contentful Paint budget in ms (good: <1800)"),
|
|
45273
|
+
cls: exports_external.number().optional().describe("Cumulative Layout Shift budget (good: <0.1)"),
|
|
45274
|
+
ttfb_ms: exports_external.number().optional().describe("Time to First Byte budget in ms (good: <800)"),
|
|
45275
|
+
dom_complete_ms: exports_external.number().optional().describe("DOM complete budget in ms"),
|
|
45276
|
+
load_event_ms: exports_external.number().optional().describe("Load event budget in ms"),
|
|
45277
|
+
js_heap_mb: exports_external.number().optional().describe("JS heap size budget in MB")
|
|
45278
|
+
}, async ({ session_id, lcp_ms, fcp_ms, cls, ttfb_ms, dom_complete_ms, load_event_ms, js_heap_mb }) => {
|
|
45279
|
+
try {
|
|
45280
|
+
const sid = resolveSessionId(session_id);
|
|
45281
|
+
const page = getSessionPage(sid);
|
|
45282
|
+
const metrics = await getPerformanceMetrics(page);
|
|
45283
|
+
const checks = [];
|
|
45284
|
+
let allPassed = true;
|
|
45285
|
+
const check = (name, budget, actual) => {
|
|
45286
|
+
if (budget === undefined)
|
|
45287
|
+
return;
|
|
45288
|
+
const passed = actual !== undefined && actual <= budget;
|
|
45289
|
+
if (!passed)
|
|
45290
|
+
allPassed = false;
|
|
45291
|
+
checks.push({ metric: name, budget, actual, passed });
|
|
45292
|
+
};
|
|
45293
|
+
check("lcp", lcp_ms, metrics.lcp);
|
|
45294
|
+
check("fcp", fcp_ms, metrics.fcp);
|
|
45295
|
+
check("cls", cls, metrics.cls);
|
|
45296
|
+
check("ttfb", ttfb_ms, metrics.ttfb);
|
|
45297
|
+
check("dom_complete", dom_complete_ms, metrics.dom_complete);
|
|
45298
|
+
check("load_event", load_event_ms, metrics.load_event);
|
|
45299
|
+
if (js_heap_mb !== undefined && metrics.js_heap_size_used !== undefined) {
|
|
45300
|
+
const heapMb = metrics.js_heap_size_used / (1024 * 1024);
|
|
45301
|
+
const passed = heapMb <= js_heap_mb;
|
|
45302
|
+
if (!passed)
|
|
45303
|
+
allPassed = false;
|
|
45304
|
+
checks.push({ metric: "js_heap_mb", budget: js_heap_mb, actual: Math.round(heapMb * 100) / 100, passed });
|
|
45305
|
+
}
|
|
45306
|
+
return json({
|
|
45307
|
+
passed: allPassed,
|
|
45308
|
+
checks,
|
|
45309
|
+
metrics,
|
|
45310
|
+
url: page.url()
|
|
45311
|
+
});
|
|
45312
|
+
} catch (e) {
|
|
45313
|
+
return err(e);
|
|
45314
|
+
}
|
|
45315
|
+
});
|
|
45226
45316
|
}
|
|
45227
45317
|
|
|
45228
45318
|
// src/mcp/recordings.ts
|
|
@@ -45397,6 +45487,19 @@ function register5(server) {
|
|
|
45397
45487
|
return err(e);
|
|
45398
45488
|
}
|
|
45399
45489
|
});
|
|
45490
|
+
server.tool("browser_record_export", "Export a recording as a Playwright test (.spec.ts), Puppeteer script, or JSON. Returns the generated code as text.", {
|
|
45491
|
+
recording_id: exports_external.string().describe("ID of the recording to export"),
|
|
45492
|
+
format: exports_external.enum(["playwright", "puppeteer", "json"]).optional().default("playwright").describe("Export format")
|
|
45493
|
+
}, async ({ recording_id, format }) => {
|
|
45494
|
+
try {
|
|
45495
|
+
const { exportRecording: exportRecording2 } = await Promise.resolve().then(() => (init_recorder(), exports_recorder));
|
|
45496
|
+
const code = exportRecording2(recording_id, format);
|
|
45497
|
+
const ext = format === "json" ? ".json" : format === "playwright" ? ".spec.ts" : ".js";
|
|
45498
|
+
return json({ format, filename: `recording-${recording_id}${ext}`, code });
|
|
45499
|
+
} catch (e) {
|
|
45500
|
+
return err(e);
|
|
45501
|
+
}
|
|
45502
|
+
});
|
|
45400
45503
|
}
|
|
45401
45504
|
|
|
45402
45505
|
// src/mcp/scripts.ts
|
|
@@ -45809,7 +45912,8 @@ function register7(server) {
|
|
|
45809
45912
|
{ tool: "browser_screenshot", description: "Take a screenshot (PNG/JPEG/WebP, annotate=true for labels)" },
|
|
45810
45913
|
{ tool: "browser_pdf", description: "Generate a PDF of the page" },
|
|
45811
45914
|
{ tool: "browser_scroll_and_screenshot", description: "Scroll then screenshot in one call" },
|
|
45812
|
-
{ tool: "browser_scroll_to_element", description: "Scroll element into view + screenshot" }
|
|
45915
|
+
{ tool: "browser_scroll_to_element", description: "Scroll element into view + screenshot" },
|
|
45916
|
+
{ tool: "browser_diff", description: "Visual diff between two URLs \u2014 highlights changes in red" }
|
|
45813
45917
|
],
|
|
45814
45918
|
Storage: [
|
|
45815
45919
|
{ tool: "browser_cookies_get", description: "Get cookies" },
|
|
@@ -45834,7 +45938,8 @@ function register7(server) {
|
|
|
45834
45938
|
{ tool: "browser_intercept_clear", description: "Remove all response intercepts" }
|
|
45835
45939
|
],
|
|
45836
45940
|
Performance: [
|
|
45837
|
-
{ tool: "browser_performance", description: "Get performance metrics" }
|
|
45941
|
+
{ tool: "browser_performance", description: "Get performance metrics" },
|
|
45942
|
+
{ tool: "browser_performance_budget", description: "Check perf against budget thresholds (LCP, FCP, CLS, TTFB)" }
|
|
45838
45943
|
],
|
|
45839
45944
|
Console: [
|
|
45840
45945
|
{ tool: "browser_console_log", description: "Get console messages" },
|
|
@@ -45847,6 +45952,7 @@ function register7(server) {
|
|
|
45847
45952
|
{ tool: "browser_record_step", description: "Add a step to recording" },
|
|
45848
45953
|
{ tool: "browser_record_stop", description: "Stop and save recording" },
|
|
45849
45954
|
{ tool: "browser_record_replay", description: "Replay a recorded sequence" },
|
|
45955
|
+
{ tool: "browser_record_export", description: "Export recording as Playwright test, Puppeteer script, or JSON" },
|
|
45850
45956
|
{ tool: "browser_recordings_list", description: "List all recordings" }
|
|
45851
45957
|
],
|
|
45852
45958
|
Auth: [
|
package/dist/mcp/meta.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"meta.d.ts","sourceRoot":"","sources":["../../src/mcp/meta.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AA8CzE,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,
|
|
1
|
+
{"version":3,"file":"meta.d.ts","sourceRoot":"","sources":["../../src/mcp/meta.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AA8CzE,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,QAq7BzC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"network.d.ts","sourceRoot":"","sources":["../../src/mcp/network.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAiCzE,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,
|
|
1
|
+
{"version":3,"file":"network.d.ts","sourceRoot":"","sources":["../../src/mcp/network.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAiCzE,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,QAmgBzC"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"recordings.d.ts","sourceRoot":"","sources":["../../src/mcp/recordings.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAkBzE,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,
|
|
1
|
+
{"version":3,"file":"recordings.d.ts","sourceRoot":"","sources":["../../src/mcp/recordings.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,yCAAyC,CAAC;AAkBzE,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,QAiQzC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hasna/browser",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "General-purpose browser agent toolkit — Playwright, Chrome DevTools Protocol, Lightpanda with auto engine selection. CLI + MCP + REST + SDK.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|