cdp-mcp 0.2.1 → 0.4.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.
Files changed (96) hide show
  1. package/README.md +28 -303
  2. package/bin.js +23 -0
  3. package/contract.d.ts +1 -0
  4. package/contract.js +2 -0
  5. package/index.d.ts +1 -0
  6. package/index.js +2 -0
  7. package/package.json +31 -73
  8. package/dist/contract.d.ts +0 -11
  9. package/dist/contract.js +0 -11
  10. package/dist/contract.js.map +0 -1
  11. package/dist/index.d.ts +0 -18
  12. package/dist/index.js +0 -402
  13. package/dist/index.js.map +0 -1
  14. package/dist/locator.d.ts +0 -108
  15. package/dist/locator.js +0 -176
  16. package/dist/locator.js.map +0 -1
  17. package/dist/server.d.ts +0 -2
  18. package/dist/server.js +0 -41
  19. package/dist/server.js.map +0 -1
  20. package/dist/session/browser.d.ts +0 -29
  21. package/dist/session/browser.js +0 -409
  22. package/dist/session/browser.js.map +0 -1
  23. package/dist/session/buffers.d.ts +0 -48
  24. package/dist/session/buffers.js +0 -43
  25. package/dist/session/buffers.js.map +0 -1
  26. package/dist/session/pause.d.ts +0 -21
  27. package/dist/session/pause.js +0 -99
  28. package/dist/session/pause.js.map +0 -1
  29. package/dist/session/state.d.ts +0 -53
  30. package/dist/session/state.js +0 -93
  31. package/dist/session/state.js.map +0 -1
  32. package/dist/sourcemap/loader.d.ts +0 -4
  33. package/dist/sourcemap/loader.js +0 -138
  34. package/dist/sourcemap/loader.js.map +0 -1
  35. package/dist/sourcemap/normalize.d.ts +0 -2
  36. package/dist/sourcemap/normalize.js +0 -59
  37. package/dist/sourcemap/normalize.js.map +0 -1
  38. package/dist/sourcemap/store.d.ts +0 -57
  39. package/dist/sourcemap/store.js +0 -185
  40. package/dist/sourcemap/store.js.map +0 -1
  41. package/dist/tools/_locator_runtime.d.ts +0 -31
  42. package/dist/tools/_locator_runtime.js +0 -243
  43. package/dist/tools/_locator_runtime.js.map +0 -1
  44. package/dist/tools/_register.d.ts +0 -2
  45. package/dist/tools/_register.js +0 -30
  46. package/dist/tools/_register.js.map +0 -1
  47. package/dist/tools/breakpoints.d.ts +0 -4
  48. package/dist/tools/breakpoints.js +0 -164
  49. package/dist/tools/breakpoints.js.map +0 -1
  50. package/dist/tools/console.d.ts +0 -2
  51. package/dist/tools/console.js +0 -48
  52. package/dist/tools/console.js.map +0 -1
  53. package/dist/tools/dom.d.ts +0 -2
  54. package/dist/tools/dom.js +0 -309
  55. package/dist/tools/dom.js.map +0 -1
  56. package/dist/tools/execution.d.ts +0 -29
  57. package/dist/tools/execution.js +0 -89
  58. package/dist/tools/execution.js.map +0 -1
  59. package/dist/tools/forms.d.ts +0 -8
  60. package/dist/tools/forms.js +0 -256
  61. package/dist/tools/forms.js.map +0 -1
  62. package/dist/tools/inspect.d.ts +0 -2
  63. package/dist/tools/inspect.js +0 -178
  64. package/dist/tools/inspect.js.map +0 -1
  65. package/dist/tools/nav.d.ts +0 -2
  66. package/dist/tools/nav.js +0 -136
  67. package/dist/tools/nav.js.map +0 -1
  68. package/dist/tools/network.d.ts +0 -2
  69. package/dist/tools/network.js +0 -137
  70. package/dist/tools/network.js.map +0 -1
  71. package/dist/tools/session.d.ts +0 -2
  72. package/dist/tools/session.js +0 -76
  73. package/dist/tools/session.js.map +0 -1
  74. package/dist/tools/source.d.ts +0 -2
  75. package/dist/tools/source.js +0 -63
  76. package/dist/tools/source.js.map +0 -1
  77. package/dist/tools/storage.d.ts +0 -2
  78. package/dist/tools/storage.js +0 -296
  79. package/dist/tools/storage.js.map +0 -1
  80. package/dist/util/browser-resolve.d.ts +0 -19
  81. package/dist/util/browser-resolve.js +0 -263
  82. package/dist/util/browser-resolve.js.map +0 -1
  83. package/dist/util/errors.d.ts +0 -7
  84. package/dist/util/errors.js +0 -12
  85. package/dist/util/errors.js.map +0 -1
  86. package/dist/util/format.d.ts +0 -20
  87. package/dist/util/format.js +0 -65
  88. package/dist/util/format.js.map +0 -1
  89. package/dist/util/log.d.ts +0 -6
  90. package/dist/util/log.js +0 -34
  91. package/dist/util/log.js.map +0 -1
  92. package/docs/chromium-sandboxing.md +0 -197
  93. package/docs/known-chromium-gaps.md +0 -138
  94. package/docs/launchd-service.md +0 -217
  95. package/docs/local-l3-e2e-setup.md +0 -199
  96. package/docs/systemd-service.md +0 -233
@@ -1,20 +0,0 @@
1
- import type { Protocol } from "devtools-protocol";
2
- export declare function truncate(s: string, max?: number): string;
3
- export declare function previewRemoteObject(obj: Protocol.Runtime.RemoteObject): string;
4
- export declare function describeRemote(obj: Protocol.Runtime.RemoteObject): {
5
- type: string;
6
- preview: string;
7
- objectId?: string;
8
- };
9
- export declare function toolText(text: string): {
10
- content: {
11
- type: "text";
12
- text: string;
13
- }[];
14
- };
15
- export declare function toolJson(value: unknown): {
16
- content: {
17
- type: "text";
18
- text: string;
19
- }[];
20
- };
@@ -1,65 +0,0 @@
1
- const MAX_PREVIEW = 200;
2
- export function truncate(s, max = MAX_PREVIEW) {
3
- if (s.length <= max)
4
- return s;
5
- return s.slice(0, max) + `…(+${s.length - max} chars)`;
6
- }
7
- // Compact textual preview of a CDP RemoteObject without dereferencing further.
8
- export function previewRemoteObject(obj) {
9
- if (obj.unserializableValue)
10
- return obj.unserializableValue;
11
- if (obj.type === "undefined")
12
- return "undefined";
13
- if (obj.subtype === "null")
14
- return "null";
15
- if (obj.type === "string")
16
- return JSON.stringify(obj.value ?? "");
17
- if (obj.type === "number" || obj.type === "boolean" || obj.type === "bigint") {
18
- return String(obj.value);
19
- }
20
- if (obj.type === "function") {
21
- return obj.description ? truncate(obj.description) : "function";
22
- }
23
- if (obj.preview) {
24
- return truncate(previewFromPreview(obj.preview));
25
- }
26
- if (obj.description)
27
- return truncate(obj.description);
28
- return obj.className ?? obj.subtype ?? obj.type;
29
- }
30
- function previewFromPreview(p) {
31
- if (p.subtype === "array") {
32
- const items = (p.properties ?? [])
33
- .map((pp) => previewProperty(pp))
34
- .join(", ");
35
- return `[${items}${p.overflow ? ", …" : ""}]`;
36
- }
37
- const items = (p.properties ?? [])
38
- .map((pp) => `${pp.name}: ${previewProperty(pp)}`)
39
- .join(", ");
40
- const head = p.description && p.description !== "Object" ? `${p.description} ` : "";
41
- return `${head}{${items}${p.overflow ? ", …" : ""}}`;
42
- }
43
- function previewProperty(p) {
44
- if (p.type === "string")
45
- return JSON.stringify(p.value ?? "");
46
- if (p.value !== undefined)
47
- return p.value;
48
- return p.type;
49
- }
50
- export function describeRemote(obj) {
51
- return {
52
- type: obj.subtype ?? obj.type,
53
- preview: previewRemoteObject(obj),
54
- ...(obj.objectId ? { objectId: obj.objectId } : {}),
55
- };
56
- }
57
- // Wrap any value into the MCP tool content envelope.
58
- export function toolText(text) {
59
- return { content: [{ type: "text", text }] };
60
- }
61
- // Stringify a JSON result with a stable shape for the LLM to parse.
62
- export function toolJson(value) {
63
- return toolText(JSON.stringify(value, null, 2));
64
- }
65
- //# sourceMappingURL=format.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"format.js","sourceRoot":"","sources":["../../src/util/format.ts"],"names":[],"mappings":"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"}
@@ -1,6 +0,0 @@
1
- export declare const log: {
2
- debug: (msg: string, meta?: Record<string, unknown>) => void;
3
- info: (msg: string, meta?: Record<string, unknown>) => void;
4
- warn: (msg: string, meta?: Record<string, unknown>) => void;
5
- error: (msg: string, meta?: Record<string, unknown>) => void;
6
- };
package/dist/util/log.js DELETED
@@ -1,34 +0,0 @@
1
- // Logging for an MCP stdio server.
2
- //
3
- // stdout is reserved for JSON-RPC framing — anything we write there will
4
- // corrupt the protocol. All logging goes to stderr.
5
- const LEVELS = { debug: 10, info: 20, warn: 30, error: 40 };
6
- function envLevel() {
7
- const raw = process.env.CDP_MCP_LOG?.toLowerCase();
8
- if (raw === "debug" || raw === "info" || raw === "warn" || raw === "error")
9
- return raw;
10
- return "info";
11
- }
12
- const threshold = LEVELS[envLevel()];
13
- function emit(level, msg, meta) {
14
- if (LEVELS[level] < threshold)
15
- return;
16
- const ts = new Date().toISOString();
17
- const tail = meta ? " " + safeJson(meta) : "";
18
- process.stderr.write(`[${ts}] ${level.toUpperCase()} ${msg}${tail}\n`);
19
- }
20
- function safeJson(v) {
21
- try {
22
- return JSON.stringify(v);
23
- }
24
- catch {
25
- return String(v);
26
- }
27
- }
28
- export const log = {
29
- debug: (msg, meta) => emit("debug", msg, meta),
30
- info: (msg, meta) => emit("info", msg, meta),
31
- warn: (msg, meta) => emit("warn", msg, meta),
32
- error: (msg, meta) => emit("error", msg, meta),
33
- };
34
- //# sourceMappingURL=log.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"log.js","sourceRoot":"","sources":["../../src/util/log.ts"],"names":[],"mappings":"AAAA,mCAAmC;AACnC,EAAE;AACF,yEAAyE;AACzE,oDAAoD;AAIpD,MAAM,MAAM,GAA0B,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,IAAI,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC;AAEnF,SAAS,QAAQ;IACf,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,WAAW,EAAE,WAAW,EAAE,CAAC;IACnD,IAAI,GAAG,KAAK,OAAO,IAAI,GAAG,KAAK,MAAM,IAAI,GAAG,KAAK,MAAM,IAAI,GAAG,KAAK,OAAO;QAAE,OAAO,GAAG,CAAC;IACvF,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,SAAS,GAAG,MAAM,CAAC,QAAQ,EAAE,CAAC,CAAC;AAErC,SAAS,IAAI,CAAC,KAAY,EAAE,GAAW,EAAE,IAA8B;IACrE,IAAI,MAAM,CAAC,KAAK,CAAC,GAAG,SAAS;QAAE,OAAO;IACtC,MAAM,EAAE,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACpC,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,GAAG,GAAG,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;IAC9C,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,KAAK,KAAK,CAAC,WAAW,EAAE,IAAI,GAAG,GAAG,IAAI,IAAI,CAAC,CAAC;AACzE,CAAC;AAED,SAAS,QAAQ,CAAC,CAAU;IAC1B,IAAI,CAAC;QACH,OAAO,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;IAC3B,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC;IACnB,CAAC;AACH,CAAC;AAED,MAAM,CAAC,MAAM,GAAG,GAAG;IACjB,KAAK,EAAE,CAAC,GAAW,EAAE,IAA8B,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC;IAChF,IAAI,EAAE,CAAC,GAAW,EAAE,IAA8B,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC;IAC9E,IAAI,EAAE,CAAC,GAAW,EAAE,IAA8B,EAAE,EAAE,CAAC,IAAI,CAAC,MAAM,EAAE,GAAG,EAAE,IAAI,CAAC;IAC9E,KAAK,EAAE,CAAC,GAAW,EAAE,IAA8B,EAAE,EAAE,CAAC,IAAI,CAAC,OAAO,EAAE,GAAG,EAAE,IAAI,CAAC;CACjF,CAAC"}
@@ -1,197 +0,0 @@
1
- # Chromium sandboxing
2
-
3
- **Last updated: 2026-05-16**
4
-
5
- This project launches Chromium in two different contexts:
6
-
7
- - L3 e2e tests and L4 evals launch a real browser through `launch_chrome`.
8
- - Agents then control that browser through CDP, including `Runtime.evaluate`,
9
- `Debugger.*`, DOM interaction, console inspection, and network inspection.
10
-
11
- That makes sandboxing a host setup and threat-model decision, not just a
12
- Chrome flag. This document is the canonical reference for `--no-sandbox`,
13
- AppArmor, unprivileged user namespaces, snap confinement, and Bubblewrap.
14
-
15
- ## Current default
16
-
17
- `launch_chrome` defaults to `sandbox: false`, which adds `--no-sandbox`.
18
-
19
- The default exists because Ubuntu 23.10+ and 24.04 commonly restrict
20
- unprivileged user namespaces through AppArmor. Playwright-bundled Chromium
21
- does not ship with a SUID `chrome_sandbox` helper. On those hosts, launching
22
- Chromium without `--no-sandbox` can fail before the DevTools port opens:
23
-
24
- ```text
25
- zygote_host_impl_linux.cc: No usable sandbox!
26
- ```
27
-
28
- From `chrome-launcher`, that often surfaces as a startup port poll timeout or
29
- `ECONNREFUSED`.
30
-
31
- Other Linux distributions may allow Chromium's unprivileged user namespace
32
- sandbox path by default. Validate the actual host before assuming the Ubuntu
33
- automation default is necessary there.
34
-
35
- Use `sandbox: true` only when the host has a working Chromium sandbox path:
36
-
37
- - An AppArmor policy that permits the specific Chromium binary to create the
38
- unprivileged user namespace it needs.
39
- - Or a working SUID `chrome_sandbox` helper installed alongside that binary.
40
-
41
- ## Why the Chromium sandbox still matters
42
-
43
- The MCP caller is already highly privileged relative to the page. It can ask
44
- the server to evaluate JavaScript, drive the DOM, set breakpoints, inspect
45
- scopes, and read browser-observed network activity. For that caller, the
46
- Chromium renderer sandbox is not the primary trust boundary.
47
-
48
- The Chromium sandbox still matters for hostile page content and browser
49
- exploitation risk. With the sandbox enabled, Chromium isolates renderer, GPU,
50
- and utility child processes from the browser process using mechanisms such as
51
- namespaces, seccomp filters, brokered filesystem access, and per-process
52
- capability reduction. With `--no-sandbox`, a compromised renderer has a much
53
- larger blast radius inside the browser process tree.
54
-
55
- So the project default is a pragmatic automation default, not a claim that
56
- `--no-sandbox` is equally safe.
57
-
58
- ## How the mechanisms relate
59
-
60
- These mechanisms solve different problems:
61
-
62
- - Chromium sandbox: Chromium's internal process sandbox. It is the boundary
63
- between web content renderer processes and the rest of the browser.
64
- - AppArmor: host-enforced mandatory access control. It can confine Chromium,
65
- and on recent Ubuntu systems it can also restrict whether an unprivileged
66
- process may create user namespaces.
67
- - Snap confinement: the packaging sandbox used by snap-installed Chromium.
68
- It can hide or remap filesystem locations and has caused DevTools port and
69
- `userDataDir` friction in local runs.
70
- - Bubblewrap (`bwrap`): a small Linux sandboxing tool that starts a process in
71
- new namespaces with a controlled filesystem, process, and optional network
72
- view.
73
-
74
- Bubblewrap is useful defense-in-depth around a browser or eval job. For
75
- example, it can run the whole MCP server plus browser process tree with only
76
- the repository and selected temp/profile directories writable. That helps
77
- prevent accidental or malicious access to unrelated files such as SSH keys,
78
- cloud credentials, or the rest of the home directory.
79
-
80
- Bubblewrap is not a clean substitute for Chromium's own sandbox. If Chromium
81
- runs with `--no-sandbox` inside Bubblewrap, the entire browser process tree is
82
- inside an outer container-like boundary, but Chromium's internal renderer
83
- isolation is still disabled. A renderer compromise may be contained by the
84
- outer Bubblewrap filesystem or network policy, but it is not contained in the
85
- same way as Chromium's per-renderer sandbox.
86
-
87
- ## Recommended posture
88
-
89
- Local development:
90
-
91
- - Treat the current default, `sandbox: false`, as a host-capability fallback:
92
- it keeps work moving when Chromium sandbox setup is the blocker.
93
- - Prefer `sandbox: true` once the host has a known-good Chromium sandbox path.
94
-
95
- CI and L4 eval hosts:
96
-
97
- - Keep the default working and deterministic. If `--no-sandbox` is needed for
98
- Playwright-bundled Chromium on Ubuntu, document that in the host setup.
99
- - Consider running the whole job inside an outer sandbox, VM, container, or
100
- Bubblewrap profile with a throwaway browser profile and limited writable
101
- paths.
102
-
103
- Untrusted browsing:
104
-
105
- - Prefer Chromium's sandbox on.
106
- - Use a throwaway `userDataDir`.
107
- - Add host-level confinement such as AppArmor, a container, VM, or Bubblewrap.
108
- - Do not treat `bwrap + --no-sandbox` as equivalent to Chromium sandboxing.
109
-
110
- This outer containment also pairs with the agent-operator threat (prompt-injected
111
- page content steering the agent into actions or unscoped filesystem writes); see
112
- the agent-operator threat model and deployment hardening in
113
- [SECURITY.md](../SECURITY.md). A containerized outer-sandbox run mode that would
114
- make this contained posture the default is a direction under consideration.
115
-
116
- ## Validated hosts
117
-
118
- Hosts where `sandbox: true` has been verified working against this project, with the supporting posture:
119
-
120
- | Host | OS | Arch | `sandbox: true` | AppArmor profile | Notes |
121
- |---|---|---|---|---|---|
122
- | Ubuntu 24.04 arm64 (Parallels VM) | Ubuntu 24.04 | arm64 | ✓ | `/etc/apparmor.d/cdp-mcp-chromium` (named-unconfined, mirrors Ubuntu's stock `chrome` / `msedge` / `brave`) | `kernel.apparmor_restrict_unprivileged_userns = 0` was set as a side effect of enabling Bubblewrap, so the kernel-level userns restriction is already off system-wide. The AppArmor profile gives Playwright Chromium a stable named label (instead of `unconfined`) and grants `userns,` explicitly, so `sandbox: true` keeps working even if a future kernel/package update flips the global knob back to `1`. |
123
-
124
- When adding a new host to this table:
125
-
126
- 1. Run the smoke tests below and capture the values.
127
- 2. If `kernel.apparmor_restrict_unprivileged_userns` is `1` (Ubuntu's stock default) and `sandbox: true` is desired, install a profile under `/etc/apparmor.d/` mirroring Ubuntu's stock `chrome` / `msedge` / `brave` shape:
128
- ```apparmor
129
- abi <abi/4.0>,
130
- include <tunables/global>
131
-
132
- profile cdp-mcp-chromium /path/to/chromium flags=(unconfined) {
133
- userns,
134
- include if exists <local/cdp-mcp-chromium>
135
- }
136
- ```
137
- Load with `sudo apparmor_parser -r /etc/apparmor.d/cdp-mcp-chromium`. The profile auto-loads at boot from `/etc/apparmor.d/`.
138
- 3. Verify the running browser process is labelled correctly:
139
- ```sh
140
- cat /proc/<chromium-pid>/attr/current
141
- ```
142
- Expect the profile name (e.g. `cdp-mcp-chromium (unconfined)`), not `unconfined` alone.
143
- 4. Add a row to the table above.
144
-
145
- Other hosts to characterize as they come online: Fedora — Fedora uses SELinux rather than AppArmor and ships userns enabled by default, so `sandbox: true` is expected to work without host-side profile work. The `dnf install bubblewrap` path is also first-class on Fedora.
146
-
147
- ## Smoke tests
148
-
149
- Check whether unprivileged user namespaces are enabled:
150
-
151
- ```sh
152
- cat /proc/sys/kernel/unprivileged_userns_clone
153
- cat /proc/sys/user/max_user_namespaces
154
- ```
155
-
156
- Expected working values are usually `1` for
157
- `kernel.unprivileged_userns_clone` and a nonzero number for
158
- `user.max_user_namespaces`.
159
-
160
- On Ubuntu, AppArmor may still restrict unprivileged user namespaces:
161
-
162
- ```sh
163
- sysctl kernel.apparmor_restrict_unprivileged_userns
164
- ```
165
-
166
- Minimal Bubblewrap smoke tests:
167
-
168
- ```sh
169
- bwrap --unshare-user --uid 0 --gid 0 --ro-bind / / /usr/bin/true
170
- bwrap --unshare-user --uid 0 --gid 0 --unshare-net --ro-bind / / /usr/bin/true
171
- ```
172
-
173
- If the first command fails with `setting up uid map: Permission denied`, the
174
- host still blocks the user namespace setup Bubblewrap needs.
175
-
176
- If the second command fails with `loopback: Failed RTM_NEWADDR: Operation not
177
- permitted`, the network namespace setup is still blocked.
178
-
179
- Project-level browser check:
180
-
181
- ```sh
182
- npm run test:e2e
183
- ```
184
-
185
- For a direct MCP check, call `launch_chrome` with `sandbox: true` on the host
186
- you want to validate. If Chromium cannot create a usable sandbox, it will fail
187
- before exposing its DevTools target.
188
-
189
- ## Decision summary
190
-
191
- - `--no-sandbox` remains the automation default because it keeps Ubuntu
192
- Playwright-Chromium runs working.
193
- - `sandbox: true` is the preferred security posture when the host supports it.
194
- - AppArmor is the long-term host policy path for allowing Chromium's needed
195
- user namespace behavior narrowly.
196
- - Bubblewrap is an outer containment layer and is valuable defense-in-depth.
197
- - Bubblewrap does not replace Chromium's internal renderer sandbox.
@@ -1,138 +0,0 @@
1
- # Known Chromium gaps
2
-
3
- Specs in the L3 e2e suite that fail (or fail intermittently) on Chromium but
4
- pass on Chrome stable. Every entry below is a real coverage gap on Linux
5
- ARM64 + Chromium (the day-1 primary target) — not a CI-only concession.
6
-
7
- If the gap is mitigatable in production code, link the fix PR. If it's a CDP
8
- protocol-version difference, link the Chromium release that includes the fix.
9
-
10
- | Spec | Skip tag | CDP method missing/changed | Chromium version that fixes it | Tracking |
11
- |---|---|---|---|---|
12
- | _none yet — populate as L3 lands_ | | | | |
13
-
14
- ## Pre-flagged risks (not yet observed; documented for triage)
15
-
16
- These are known protocol-version-sensitive areas the test+eval plan flagged
17
- as risky during planning. Add a row above when one of them actually fires
18
- on the e2e suite.
19
-
20
- - **`Network.loadNetworkResource`** — Used by `src/sourcemap/loader.ts:113`
21
- to fetch source maps through the browser's network stack (so cookies/
22
- origin/auth flow naturally). Older Chromium revisions ship a more limited
23
- param set (no `options.includeCredentials`, no `options.disableCache`);
24
- the production code already has a Node-fetch fallback, but verify the
25
- fallback path is exercised under the older Chromium.
26
-
27
- - **`Page.captureScreenshot`** flag set — `captureBeyondViewport` and
28
- `quality` (when `format=jpeg`) gained options across versions. The
29
- screenshot e2e spec asserts byte-shape, not flag-respect, so this is
30
- most likely to surface as a "bytes don't match" assertion under older
31
- Chromium.
32
-
33
- ## Conventions
34
-
35
- - Add a `// @chromium-skip — <gap-id>` comment on the spec's `it()` line.
36
- - Set the spec to `it.skipIf(process.env.CDP_TEST_BROWSER === "chromium")` or
37
- use vitest's `.skip` with a runtime check.
38
- - Every skip MUST have a corresponding row in the table above. **Enforced** by
39
- `scripts/check-chromium-skips.mjs` — runs as `pretest:e2e` on every PR and
40
- also as `npm run lint:chromium-skips`. Greps `test/e2e/**/*.test.ts` for
41
- `@chromium-skip` tags and `it.skipIf`/`describe.skipIf` Chromium guards,
42
- parses the table above, exits 1 if any skip lacks a row OR any row points
43
- at a spec that no longer exists. Zero-skip state (the current state) is
44
- fine — the script is a no-op.
45
-
46
- _(no entries below this line yet means no L3 specs needed a Chromium skip)_
47
-
48
- ## Known host gaps (not Chromium-version issues)
49
-
50
- These are host/library combinations where the e2e suite cannot run end-to-
51
- end, separate from the per-Chromium-version skip mechanism above. Listed
52
- here so future contributors don't waste a debug cycle.
53
-
54
- - **Windows 11 + chrome-launcher 1.2.1.** `chrome-launcher.launch()`'s
55
- internal startup-port poll always fails with `ECONNREFUSED` on this Win11
56
- configuration, regardless of headless mode (`--headless=new`, classic
57
- `--headless`, or non-headless), browser (Chrome stable from Program
58
- Files, Playwright-bundled Chromium under `~/AppData/Local/ms-playwright/
59
- chromium-XXXX/chrome-win64/chrome.exe`), or how the port is selected
60
- (chrome-launcher-managed vs explicit). Spawning `chrome.exe` directly via
61
- `Start-Process` and probing `/json/version` over HTTP works fine — only
62
- chrome-launcher's launch path fails. The same code works on Linux (CI)
63
- and is widely used elsewhere, so this is a Windows-host quirk rather
64
- than a cdp-mcp issue. **Workaround**: run L3 changes under WSL2
65
- (Ubuntu) or push and let CI validate (but see WSL2 caveat below). Unit
66
- + L2 tests work fine on native Windows.
67
-
68
- *Originally hit on agents/l3-impl during PR #11 implementation.
69
- Cross-confirmed by Codex reviewer who diagnosed the on-CI failure
70
- separately — turned out to be a different root cause (Codex blocker on
71
- --remote-debugging-port=0 in chromeFlags overriding chrome-launcher's
72
- own port). After that fix, CI on Linux is the live validation; Win11
73
- local-host status remains as documented here.*
74
-
75
- - **macOS arm64 + system unbranded Chromium (brew cask).** The `chromium`
76
- Homebrew cask is **deprecated** ("does not pass the macOS Gatekeeper
77
- check; will be disabled 2026-09-01"). Install completes and the wrapper
78
- script lands at `/opt/homebrew/bin/chromium`, but on first launch
79
- Gatekeeper rejects the `.app` as "damaged" and the binary is unusable
80
- for unattended e2e/eval runs. Workaround for darwin-arm64: use Playwright
81
- Chrome-for-Testing (`npx playwright install chromium`) — `resolveBrowser`
82
- picks it up automatically from `~/Library/Caches/ms-playwright/chromium-
83
- <rev>/chrome-mac-arm64/Google Chrome for Testing.app/Contents/MacOS/
84
- Google Chrome for Testing` (CfT layout added to `pickPlaywrightExe` in
85
- the same PR that landed this entry). The resolver also explicitly skips
86
- the brew-cask wrapper on darwin (`isBrewCaskChromium`) so users who
87
- tried the deprecated cask first — and then turn to Playwright per this
88
- entry — fall through to the Playwright cache instead of getting the
89
- Gatekeeper-rejected wrapper back from Step 2. Functionally Chromium-
90
- channel at a fixed protocol revision, but Google-branded — call it out
91
- if your eval needs *unbranded* Chromium. Building Chromium from source
92
- on macOS is multi-hour + multi-GB ongoing maintenance; not a viable
93
- automation path.
94
-
95
- *Originally hit while validating set_breakpoint idempotency on a
96
- macOS arm64 host.*
97
-
98
- - **WSL2 (Ubuntu) + snap-installed chromium.** Default-template Ubuntu on
99
- WSL2 ships chromium as a snap (`/snap/bin/chromium`), which runs in a
100
- confined namespace. chrome-launcher launches the binary successfully
101
- (visible window appears under WSLg) but its startup-port poll
102
- `ECONNREFUSEs` on iter 1 and often iter 2 — the debug port either
103
- binds to a different port than chrome-launcher polled (race) or is
104
- invisible across the snap sandbox boundary. After 2-4 retries the
105
- agent's tool-use loop eventually picks an instance that responds, so
106
- the eval does run, but every trial pays an inflated cost in retry
107
- iterations and the trace is contaminated with WARN entries that look
108
- like real failures. Same chrome-launcher code path is clean on macOS,
109
- Linux native, and Linux CI.
110
-
111
- **Workaround options**: (a) **prefer macOS or Linux native** for
112
- interactive eval iteration — a macOS arm64 host confirmed clean;
113
- (b) install non-snap chromium in WSL2 via apt or
114
- symlink Playwright's bundled chromium to `/usr/local/bin/chromium-browser`
115
- before the snap path; (c) accept the noise and rely on CI for the
116
- authoritative signal. Don't rely on WSL2 for eval validation runs.
117
-
118
- *A re-run on the same commit (`f0ce92a`) on a native host showed 0
119
- chrome-launcher errors, isolating the cause to the WSL2 + snap-chromium
120
- combination rather than the harness.*
121
-
122
- - **Ubuntu 23.10+ (incl. 24.04) + Playwright-bundled Chromium.** Recent
123
- Ubuntu kernels restrict unprivileged user namespaces via AppArmor, and
124
- Playwright-bundled Chromium ships without a SUID `chrome_sandbox`
125
- helper. Without `--no-sandbox`, Chromium FATALs at startup
126
- (`zygote_host_impl_linux.cc: No usable sandbox!`) before opening its
127
- debug port — chrome-launcher's port-poll loop then times out with
128
- ECONNREFUSED, looking exactly like the WSL2/snap gap above but with a
129
- different root cause. **Mitigation:** `launchChrome` defaults
130
- `sandbox: false` so `--no-sandbox` is added automatically; eval
131
- pipelines on this host work out-of-the-box. For the full security model,
132
- including `sandbox: true`, AppArmor, snap confinement, and Bubblewrap, see
133
- [docs/chromium-sandboxing.md](./chromium-sandboxing.md).
134
-
135
- *First observed on an Ubuntu 24.04 arm64 host (Parallels VM) while
136
- validating the L4 eval suite. Quick eval went from FAIL/$0.34/445s
137
- (chrome-launcher retry storm) to PASS/$0.31/107s once `--no-sandbox`
138
- was the default.*