@vercel/next-browser 0.5.1 → 0.7.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -8
- package/dist/browser.js +29 -0
- package/dist/cli.js +27 -2
- package/dist/cookies.js +8 -3
- package/dist/daemon.js +13 -2
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -38,7 +38,7 @@ Requires Node >= 20.
|
|
|
38
38
|
|
|
39
39
|
| Command | Description |
|
|
40
40
|
| ------------------------------------ | -------------------------------------------------- |
|
|
41
|
-
| `open <url> [--cookies
|
|
41
|
+
| `open <url> [--cookies <file>]` | Launch browser and navigate (with optional cookies) |
|
|
42
42
|
| `close` | Close browser and kill daemon |
|
|
43
43
|
|
|
44
44
|
### Navigation
|
|
@@ -62,9 +62,9 @@ Requires Node >= 20.
|
|
|
62
62
|
| `snapshot` | Accessibility tree with `[ref=eN]` markers on interactive elements |
|
|
63
63
|
| `errors` | Build and runtime errors for the current page |
|
|
64
64
|
| `logs` | Recent dev server log output |
|
|
65
|
+
| `browser-logs` | Browser console output (log, warn, error, info) |
|
|
65
66
|
| `network [idx]` | List network requests, or inspect one (headers, body) |
|
|
66
|
-
| `
|
|
67
|
-
| `screenshot` | Viewport PNG to a temp file (`--full-page` for entire page) |
|
|
67
|
+
| `screenshot [caption] [--full-page]` | Viewport PNG to a temp file (caption shown in Screenshot Log) |
|
|
68
68
|
|
|
69
69
|
### Interaction
|
|
70
70
|
|
|
@@ -77,11 +77,20 @@ Requires Node >= 20.
|
|
|
77
77
|
|
|
78
78
|
### Performance & PPR
|
|
79
79
|
|
|
80
|
-
| Command
|
|
81
|
-
|
|
|
82
|
-
| `perf [url]`
|
|
83
|
-
| `
|
|
84
|
-
| `
|
|
80
|
+
| Command | Description |
|
|
81
|
+
| ------------------------------ | ---------------------------------------------------- |
|
|
82
|
+
| `perf [url]` | Core Web Vitals + React hydration timing in one pass |
|
|
83
|
+
| `renders start` | Start recording React re-renders |
|
|
84
|
+
| `renders stop [--json]` | Stop and print per-component render profile |
|
|
85
|
+
| `ppr lock` | Freeze dynamic content to inspect the static shell |
|
|
86
|
+
| `ppr unlock` | Resume dynamic content and print shell analysis |
|
|
87
|
+
|
|
88
|
+
### Instrumentation
|
|
89
|
+
|
|
90
|
+
| Command | Description |
|
|
91
|
+
| ------------------------------ | ---------------------------------------------------- |
|
|
92
|
+
| `instrumentation set <path>` | Inject script before page scripts on every navigation |
|
|
93
|
+
| `instrumentation clear` | Remove instrumentation script |
|
|
85
94
|
|
|
86
95
|
### Next.js MCP
|
|
87
96
|
|
package/dist/browser.js
CHANGED
|
@@ -30,6 +30,7 @@ let page = null;
|
|
|
30
30
|
let profileDirPath = null;
|
|
31
31
|
let initialOrigin = null;
|
|
32
32
|
let ssrLocked = false;
|
|
33
|
+
let instrumentationVersion = 0;
|
|
33
34
|
let screenshotBrowser = null;
|
|
34
35
|
let screenshotPage = null;
|
|
35
36
|
let screenshotEntries = [];
|
|
@@ -87,6 +88,7 @@ export async function close() {
|
|
|
87
88
|
release = null;
|
|
88
89
|
settled = null;
|
|
89
90
|
ssrLocked = false;
|
|
91
|
+
instrumentationVersion = 0;
|
|
90
92
|
// Clean up temp profile directory.
|
|
91
93
|
if (profileDirPath) {
|
|
92
94
|
const { rmSync } = await import("node:fs");
|
|
@@ -95,6 +97,30 @@ export async function close() {
|
|
|
95
97
|
initialOrigin = null;
|
|
96
98
|
}
|
|
97
99
|
}
|
|
100
|
+
// ── Instrumentation ─────────────────────────────────────────────────────────
|
|
101
|
+
//
|
|
102
|
+
// addInitScript can't be removed, so we version-gate: each `set` bumps the
|
|
103
|
+
// version and registers a new script that only runs when its version matches.
|
|
104
|
+
// `clear` bumps the version with no matching script, disabling the old one.
|
|
105
|
+
export async function instrumentationSet(script) {
|
|
106
|
+
if (!page)
|
|
107
|
+
throw new Error("browser not open");
|
|
108
|
+
const ctx = page.context();
|
|
109
|
+
const v = ++instrumentationVersion;
|
|
110
|
+
// The version gate runs first, then the guarded script.
|
|
111
|
+
await ctx.addInitScript(`window.__NB_IV__=${v}`);
|
|
112
|
+
await ctx.addInitScript(`if(window.__NB_IV__===${v}){${script}}`);
|
|
113
|
+
// Apply immediately on the current page.
|
|
114
|
+
await page.evaluate(script);
|
|
115
|
+
}
|
|
116
|
+
export async function instrumentationClear() {
|
|
117
|
+
if (!page)
|
|
118
|
+
throw new Error("browser not open");
|
|
119
|
+
const ctx = page.context();
|
|
120
|
+
// Bump version — no matching script, so nothing runs.
|
|
121
|
+
const v = ++instrumentationVersion;
|
|
122
|
+
await ctx.addInitScript(`window.__NB_IV__=${v}`);
|
|
123
|
+
}
|
|
98
124
|
// ── PPR lock/unlock ──────────────────────────────────────────────────────────
|
|
99
125
|
//
|
|
100
126
|
// The lock uses @next/playwright's `instant()` which sets the
|
|
@@ -451,6 +477,9 @@ export async function push(path) {
|
|
|
451
477
|
if (!page)
|
|
452
478
|
throw new Error("browser not open");
|
|
453
479
|
const before = page.url();
|
|
480
|
+
const resolved = new URL(path, before).href;
|
|
481
|
+
if (resolved === before)
|
|
482
|
+
throw new Error("already on this URL");
|
|
454
483
|
await page.evaluate((p) => window.next.router.push(p), path);
|
|
455
484
|
await page.waitForURL((u) => u.href !== before, { timeout: 10_000 }).catch(() => { });
|
|
456
485
|
return page.url();
|
package/dist/cli.js
CHANGED
|
@@ -150,7 +150,15 @@ if (cmd === "screenshot") {
|
|
|
150
150
|
const fullPage = args.includes("--full-page");
|
|
151
151
|
const caption = args.slice(1).filter((a) => a !== "--full-page").join(" ") || undefined;
|
|
152
152
|
const res = await send("screenshot", { fullPage, caption });
|
|
153
|
-
|
|
153
|
+
if (res.ok) {
|
|
154
|
+
const { path, errors } = res.data;
|
|
155
|
+
const parts = [path];
|
|
156
|
+
if (errors)
|
|
157
|
+
parts.push("\nerrors:\n" + json(errors));
|
|
158
|
+
console.log(parts.join(""));
|
|
159
|
+
process.exit(0);
|
|
160
|
+
}
|
|
161
|
+
exit(res, "");
|
|
154
162
|
}
|
|
155
163
|
if (cmd === "snapshot") {
|
|
156
164
|
const res = await send("snapshot");
|
|
@@ -369,6 +377,20 @@ if (cmd === "viewport") {
|
|
|
369
377
|
const data = res.data;
|
|
370
378
|
exit(res, `${data.width}x${data.height}`);
|
|
371
379
|
}
|
|
380
|
+
if (cmd === "instrumentation" && arg === "set") {
|
|
381
|
+
const filePath = args[2];
|
|
382
|
+
if (!filePath) {
|
|
383
|
+
console.error("usage: next-browser instrumentation set <path>");
|
|
384
|
+
process.exit(1);
|
|
385
|
+
}
|
|
386
|
+
const script = readFileSync(filePath, "utf-8");
|
|
387
|
+
const res = await send("instrumentation-set", { instrumentationScript: script });
|
|
388
|
+
exit(res, "instrumentation set");
|
|
389
|
+
}
|
|
390
|
+
if (cmd === "instrumentation" && arg === "clear") {
|
|
391
|
+
const res = await send("instrumentation-clear");
|
|
392
|
+
exit(res, "instrumentation cleared");
|
|
393
|
+
}
|
|
372
394
|
if (cmd === "close") {
|
|
373
395
|
const res = await send("close");
|
|
374
396
|
exit(res, "closed");
|
|
@@ -477,5 +499,8 @@ function printUsage() {
|
|
|
477
499
|
" page show current page segments and router info\n" +
|
|
478
500
|
" project show project path and dev server url\n" +
|
|
479
501
|
" routes list app routes\n" +
|
|
480
|
-
" action <id> inspect a server action by id"
|
|
502
|
+
" action <id> inspect a server action by id\n" +
|
|
503
|
+
"\n" +
|
|
504
|
+
" instrumentation set <path> inject script before page scripts\n" +
|
|
505
|
+
" instrumentation clear remove instrumentation script");
|
|
481
506
|
}
|
package/dist/cookies.js
CHANGED
|
@@ -4,7 +4,8 @@
|
|
|
4
4
|
*
|
|
5
5
|
* 1. JSON array — Playwright-style `[{"name": "x", "value": "y"}, ...]`.
|
|
6
6
|
* 2. cURL command — as produced by DevTools → Network → Copy as cURL.
|
|
7
|
-
*
|
|
7
|
+
* Cookies are extracted from `-H 'cookie: …'` or `-b '…'`/`--cookie '…'`
|
|
8
|
+
* (macOS Chrome uses `-b` instead of `-H`).
|
|
8
9
|
* 3. Bare cookie header — `name=v; name=v; ...` (e.g. the value of the
|
|
9
10
|
* Cookie row in DevTools → Network → Request Headers).
|
|
10
11
|
*
|
|
@@ -43,8 +44,12 @@ function extractCookieHeaderFromCurl(curl) {
|
|
|
43
44
|
const joined = curl.replace(/\\\r?\n\s*/g, " ").replace(/\^\r?\n\s*/g, " ");
|
|
44
45
|
// -H 'cookie: …' (bash) or -H "cookie: …" (cmd). Chrome/Firefox use one
|
|
45
46
|
// or the other depending on which Copy-as-cURL variant the user picked.
|
|
46
|
-
const
|
|
47
|
-
|
|
47
|
+
const h = joined.match(/-H\s+(['"])\s*cookie\s*:\s*([\s\S]*?)\1/i);
|
|
48
|
+
if (h)
|
|
49
|
+
return h[2];
|
|
50
|
+
// macOS Chrome uses -b (or --cookie) instead of -H for cookies.
|
|
51
|
+
const b = joined.match(/(?:-b|--cookie)\s+(['"])([\s\S]*?)\1/);
|
|
52
|
+
return b ? b[2] : null;
|
|
48
53
|
}
|
|
49
54
|
function parseCookieHeader(header) {
|
|
50
55
|
const pairs = [];
|
package/dist/daemon.js
CHANGED
|
@@ -78,8 +78,11 @@ async function run(cmd) {
|
|
|
78
78
|
return { ok: true, data };
|
|
79
79
|
}
|
|
80
80
|
if (cmd.action === "screenshot") {
|
|
81
|
-
const
|
|
82
|
-
|
|
81
|
+
const [path, errors] = await Promise.all([
|
|
82
|
+
browser.screenshot({ fullPage: cmd.fullPage, caption: cmd.caption }),
|
|
83
|
+
browser.mcp("get_errors").catch(() => null),
|
|
84
|
+
]);
|
|
85
|
+
return { ok: true, data: { path, errors } };
|
|
83
86
|
}
|
|
84
87
|
if (cmd.action === "links") {
|
|
85
88
|
const data = await browser.links();
|
|
@@ -145,6 +148,14 @@ async function run(cmd) {
|
|
|
145
148
|
const data = await browser.viewportSize();
|
|
146
149
|
return { ok: true, data };
|
|
147
150
|
}
|
|
151
|
+
if (cmd.action === "instrumentation-set") {
|
|
152
|
+
await browser.instrumentationSet(cmd.instrumentationScript);
|
|
153
|
+
return { ok: true };
|
|
154
|
+
}
|
|
155
|
+
if (cmd.action === "instrumentation-clear") {
|
|
156
|
+
await browser.instrumentationClear();
|
|
157
|
+
return { ok: true };
|
|
158
|
+
}
|
|
148
159
|
if (cmd.action === "close") {
|
|
149
160
|
await browser.close();
|
|
150
161
|
return { ok: true };
|