pi-lean-dimension 0.2.4 → 0.3.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 (159) hide show
  1. package/README.md +1 -1
  2. package/node_modules/pi-lean-portal/AGENTS.md +15 -23
  3. package/node_modules/pi-lean-portal/README.md +31 -1
  4. package/node_modules/pi-lean-portal/__tests__/browser-toggle-profile.test.ts +12 -1
  5. package/node_modules/pi-lean-portal/__tests__/browser-toggle.test.ts +263 -1126
  6. package/node_modules/pi-lean-portal/__tests__/fetch-backend.test.ts +6 -32
  7. package/node_modules/pi-lean-portal/__tests__/helpers/mock-python-bridge.py +1 -1
  8. package/node_modules/pi-lean-portal/__tests__/helpers/probe-user-backend.ts +1 -2
  9. package/node_modules/pi-lean-portal/__tests__/helpers/setup-temp-dir.ts +26 -0
  10. package/node_modules/pi-lean-portal/__tests__/plugin-config-browser.test.ts +1 -1
  11. package/node_modules/pi-lean-portal/__tests__/plugin-contract.test.ts +1 -1
  12. package/node_modules/pi-lean-portal/__tests__/plugin-loading.test.ts +1 -1
  13. package/node_modules/pi-lean-portal/__tests__/probe-user-backend.test.ts +1 -1
  14. package/node_modules/pi-lean-portal/__tests__/python-adapter.test.ts +65 -69
  15. package/node_modules/pi-lean-portal/__tests__/router-dispatch.test.ts +1 -14
  16. package/node_modules/pi-lean-portal/__tests__/run-contributed-suites.test.ts +31 -16
  17. package/node_modules/pi-lean-portal/__tests__/snapshot-cache.test.ts +1 -1
  18. package/node_modules/pi-lean-portal/__tests__/web-guides.test.ts +146 -19
  19. package/node_modules/pi-lean-portal/backends/playwright-base/playwright-plugin.ts +1 -1
  20. package/node_modules/pi-lean-portal/backends/python-adapter.ts +3 -3
  21. package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__init__.py +2 -4
  22. package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/__init__.cpython-312.pyc +0 -0
  23. package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/__init__.cpython-313.pyc +0 -0
  24. package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/playwright_base.cpython-312.pyc +0 -0
  25. package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/playwright_base.cpython-313.pyc +0 -0
  26. package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/playwright_base.py +572 -81
  27. package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/test_playwright_base_quirks.cpython-313-pytest-9.1.1.pyc +0 -0
  28. package/node_modules/pi-lean-portal/backends/python-base/tests/test_playwright_base_quirks.py +13 -43
  29. package/node_modules/pi-lean-portal/browser-profile.ts +2 -3
  30. package/node_modules/pi-lean-portal/browser-toggle.ts +168 -385
  31. package/node_modules/pi-lean-portal/contributed/CHOOSING.md +2 -2
  32. package/node_modules/pi-lean-portal/contributed/README.md +65 -6
  33. package/node_modules/pi-lean-portal/contributed/camoufox-py/__pycache__/bridge.cpython-312.pyc +0 -0
  34. package/node_modules/pi-lean-portal/contributed/camoufox-py/bridge.py +108 -14
  35. package/node_modules/pi-lean-portal/contributed/camoufox-py/pin.json +4 -0
  36. package/node_modules/pi-lean-portal/core/fetch-backend.ts +10 -30
  37. package/node_modules/pi-lean-portal/core/guides.ts +14 -18
  38. package/node_modules/pi-lean-portal/core/router.ts +2 -20
  39. package/node_modules/pi-lean-portal/core/shared/bot-detection.ts +19 -30
  40. package/node_modules/pi-lean-portal/core/shared/browser-data.ts +4 -4
  41. package/node_modules/pi-lean-portal/core/shared/nav-settle.ts +2 -2
  42. package/node_modules/pi-lean-portal/core/shared/paths.ts +16 -2
  43. package/node_modules/pi-lean-portal/core/shared/settings-reader.ts +2 -7
  44. package/node_modules/pi-lean-portal/core/shared/snapshot-cache.ts +1 -1
  45. package/node_modules/pi-lean-portal/index.ts +8 -1
  46. package/node_modules/pi-lean-portal/package.json +2 -1
  47. package/node_modules/pi-lean-portal/tools/web-learn.ts +1 -4
  48. package/node_modules/pi-lean-search/__tests__/web-search.test.ts +87 -0
  49. package/node_modules/pi-lean-search/index.ts +134 -64
  50. package/node_modules/pi-lean-search/package.json +2 -1
  51. package/node_modules/playwright/README.md +3 -3
  52. package/node_modules/playwright/lib/agents/copilot-setup-steps.yml +2 -2
  53. package/node_modules/playwright/lib/cli/reportActions.js +2 -2
  54. package/node_modules/playwright/lib/common/index.js +77 -52
  55. package/node_modules/playwright/lib/common/index.js.txt +10 -11
  56. package/node_modules/playwright/lib/errorContext.js +2 -3
  57. package/node_modules/playwright/lib/index.js +30 -8
  58. package/node_modules/playwright/lib/matchers/expect.js +4811 -4379
  59. package/node_modules/playwright/lib/matchers/expect.js.LICENSE +87 -140
  60. package/node_modules/playwright/lib/matchers/expect.js.txt +30 -35
  61. package/node_modules/playwright/lib/program.js +19 -0
  62. package/node_modules/playwright/lib/runner/index.js +309 -153
  63. package/node_modules/playwright/lib/runner/index.js.txt +13 -12
  64. package/node_modules/playwright/lib/transform/babelBundle.js +12899 -11654
  65. package/node_modules/playwright/lib/transform/babelBundle.js.LICENSE +379 -354
  66. package/node_modules/playwright/lib/transform/babelBundle.js.txt +39 -39
  67. package/node_modules/playwright/lib/transform/esmLoader.js +3129 -1116
  68. package/node_modules/playwright/lib/transform/esmLoader.js.LICENSE +81 -61
  69. package/node_modules/playwright/lib/transform/esmLoader.js.txt +21 -18
  70. package/node_modules/playwright/lib/util.js +2 -29
  71. package/node_modules/playwright/lib/worker/workerProcessEntry.js +14 -32
  72. package/node_modules/playwright/lib/worker/workerProcessEntry.js.txt +7 -8
  73. package/node_modules/playwright/package.json +3 -6
  74. package/node_modules/playwright/types/test.d.ts +391 -4
  75. package/node_modules/playwright/types/testReporter.d.ts +79 -4
  76. package/node_modules/playwright-core/browsers.json +12 -18
  77. package/node_modules/playwright-core/lib/bootstrap.js +1 -1
  78. package/node_modules/playwright-core/lib/coreBundle.js +11344 -9900
  79. package/node_modules/playwright-core/lib/server/electron/loader.js +5 -2
  80. package/node_modules/playwright-core/lib/serverRegistry.js +5 -1
  81. package/node_modules/playwright-core/lib/tools/cli-client/help.json +21 -6
  82. package/node_modules/playwright-core/lib/tools/cli-client/program.js +1 -1
  83. package/node_modules/playwright-core/lib/tools/cli-client/session.js +4 -0
  84. package/node_modules/playwright-core/lib/tools/{cli-client/skill → skills/playwright-cli}/SKILL.md +21 -5
  85. package/node_modules/playwright-core/lib/tools/{cli-client/skill/references/spec-driven-testing.md → skills/playwright-cli/references/test-generation.md} +135 -7
  86. package/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/SKILL.md +143 -0
  87. package/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/references/gallery-spec.md +144 -0
  88. package/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/references/migration.md +85 -0
  89. package/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/references/react.md +67 -0
  90. package/node_modules/playwright-core/lib/tools/skills/playwright-component-testing/references/vue.md +75 -0
  91. package/node_modules/playwright-core/lib/tools/utils/extension.js +31 -8
  92. package/node_modules/playwright-core/lib/utilsBundle.js +37698 -33670
  93. package/node_modules/playwright-core/lib/utilsBundle.js.LICENSE +391 -268
  94. package/node_modules/playwright-core/lib/vite/dashboard/assets/index-CyWAfh-p.js +11 -0
  95. package/node_modules/playwright-core/lib/vite/dashboard/assets/index-DhC616m4.css +1 -0
  96. package/node_modules/playwright-core/lib/vite/dashboard/index.html +2 -2
  97. package/node_modules/playwright-core/lib/vite/htmlReport/report.css +2 -1
  98. package/node_modules/playwright-core/lib/vite/htmlReport/report.js +15 -55
  99. package/node_modules/playwright-core/lib/vite/recorder/assets/codeMirrorModule--QdMvsKi.css +1 -0
  100. package/node_modules/playwright-core/lib/vite/recorder/assets/codeMirrorModule-CwYYHbcZ.js +32 -0
  101. package/node_modules/playwright-core/lib/vite/recorder/assets/index-DYjdXIbE.js +129 -0
  102. package/node_modules/playwright-core/lib/vite/recorder/assets/index-l_lX622x.css +1 -0
  103. package/node_modules/playwright-core/lib/vite/recorder/index.html +2 -2
  104. package/node_modules/playwright-core/lib/vite/traceViewer/assets/codeMirrorModule-By56iMx7.js +32 -0
  105. package/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-B34OrIms.js +181 -0
  106. package/node_modules/playwright-core/lib/vite/traceViewer/assets/urlMatch-L3liM589.js +1 -0
  107. package/node_modules/playwright-core/lib/vite/traceViewer/assets/xtermModule-COQkjINf.js +7 -0
  108. package/node_modules/playwright-core/lib/vite/traceViewer/codeMirrorModule.-QdMvsKi.css +1 -0
  109. package/node_modules/playwright-core/lib/vite/traceViewer/defaultSettingsView.BLFoOugd.css +1 -0
  110. package/node_modules/playwright-core/lib/vite/traceViewer/index.B_TqY17P.css +1 -0
  111. package/node_modules/playwright-core/lib/vite/traceViewer/index.Dl36UVQT.js +1 -0
  112. package/node_modules/playwright-core/lib/vite/traceViewer/index.html +5 -5
  113. package/node_modules/playwright-core/lib/vite/traceViewer/snapshot.B_Jk1wbt.js +1 -0
  114. package/node_modules/playwright-core/lib/vite/traceViewer/snapshot.html +2 -2
  115. package/node_modules/playwright-core/lib/vite/traceViewer/sw.bundle.js +3 -4
  116. package/node_modules/playwright-core/lib/vite/traceViewer/uiMode.C7UW1sC9.css +1 -0
  117. package/node_modules/playwright-core/lib/vite/traceViewer/uiMode.D962mr9b.js +5 -0
  118. package/node_modules/playwright-core/lib/vite/traceViewer/uiMode.html +5 -5
  119. package/node_modules/playwright-core/lib/vite/traceViewer/xtermModule.kHJ-D0s7.css +1 -0
  120. package/node_modules/playwright-core/lib/webp_codec.LICENSE +173 -0
  121. package/node_modules/playwright-core/lib/webp_codec.wasm +0 -0
  122. package/node_modules/playwright-core/lib/xdg-open +338 -137
  123. package/node_modules/playwright-core/package.json +2 -2
  124. package/node_modules/playwright-core/types/protocol.d.ts +249 -36
  125. package/node_modules/playwright-core/types/structs.d.ts +3 -1
  126. package/node_modules/playwright-core/types/types.d.ts +2009 -266
  127. package/package.json +3 -3
  128. package/node_modules/pi-lean-portal/__tests__/url-safety.test.ts +0 -188
  129. package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/bridge.py +0 -525
  130. package/node_modules/pi-lean-portal/core/shared/url-safety.ts +0 -164
  131. package/node_modules/playwright-core/lib/server/deviceDescriptorsSource.json +0 -2739
  132. package/node_modules/playwright-core/lib/tools/cli-client/skill/references/test-generation.md +0 -134
  133. package/node_modules/playwright-core/lib/vite/dashboard/assets/index-BY2S1tHT.css +0 -1
  134. package/node_modules/playwright-core/lib/vite/dashboard/assets/index-C_5TMfeg.js +0 -52
  135. package/node_modules/playwright-core/lib/vite/recorder/assets/codeMirrorModule-DYBRYzYX.css +0 -1
  136. package/node_modules/playwright-core/lib/vite/recorder/assets/codeMirrorModule-DeBYQozu.js +0 -32
  137. package/node_modules/playwright-core/lib/vite/recorder/assets/index-4ZiSSCmn.css +0 -1
  138. package/node_modules/playwright-core/lib/vite/recorder/assets/index-Bq-mQf8S.js +0 -193
  139. package/node_modules/playwright-core/lib/vite/traceViewer/assets/codeMirrorModule-LEHpjmcn.js +0 -32
  140. package/node_modules/playwright-core/lib/vite/traceViewer/assets/defaultSettingsView-BNmKHKpQ.js +0 -264
  141. package/node_modules/playwright-core/lib/vite/traceViewer/assets/urlMatch-BYQrIQwR.js +0 -1
  142. package/node_modules/playwright-core/lib/vite/traceViewer/assets/xtermModule-CsJ4vdCR.js +0 -9
  143. package/node_modules/playwright-core/lib/vite/traceViewer/codeMirrorModule.DYBRYzYX.css +0 -1
  144. package/node_modules/playwright-core/lib/vite/traceViewer/defaultSettingsView.CjdS-WJx.css +0 -1
  145. package/node_modules/playwright-core/lib/vite/traceViewer/index.CzXZzn5A.css +0 -1
  146. package/node_modules/playwright-core/lib/vite/traceViewer/index.DMMX1gXU.js +0 -2
  147. package/node_modules/playwright-core/lib/vite/traceViewer/snapshot.v8KI4P3m.js +0 -2
  148. package/node_modules/playwright-core/lib/vite/traceViewer/uiMode.BZQ54Kgt.css +0 -1
  149. package/node_modules/playwright-core/lib/vite/traceViewer/uiMode.Ut8wwJNp.js +0 -6
  150. package/node_modules/playwright-core/lib/vite/traceViewer/xtermModule.DYP7pi_n.css +0 -32
  151. /package/node_modules/playwright-core/lib/tools/{cli-client/skill → skills/playwright-cli}/references/element-attributes.md +0 -0
  152. /package/node_modules/playwright-core/lib/tools/{cli-client/skill → skills/playwright-cli}/references/playwright-tests.md +0 -0
  153. /package/node_modules/playwright-core/lib/tools/{cli-client/skill → skills/playwright-cli}/references/request-mocking.md +0 -0
  154. /package/node_modules/playwright-core/lib/tools/{cli-client/skill → skills/playwright-cli}/references/running-code.md +0 -0
  155. /package/node_modules/playwright-core/lib/tools/{cli-client/skill → skills/playwright-cli}/references/session-management.md +0 -0
  156. /package/node_modules/playwright-core/lib/tools/{cli-client/skill → skills/playwright-cli}/references/storage-state.md +0 -0
  157. /package/node_modules/playwright-core/lib/tools/{cli-client/skill → skills/playwright-cli}/references/tracing.md +0 -0
  158. /package/node_modules/playwright-core/lib/tools/{cli-client/skill → skills/playwright-cli}/references/video-recording.md +0 -0
  159. /package/node_modules/playwright-core/lib/tools/{trace → skills/playwright-trace}/SKILL.md +0 -0
@@ -1,8 +1,7 @@
1
1
  /**
2
2
  * Tests for fetch-backend.ts — webFetch() and supporting utilities.
3
3
  *
4
- * Uses vi.spyOn(global, 'fetch') to mock HTTP responses, avoiding
5
- * URL safety restrictions (127.0.0.1 is blocked) and providing
4
+ * Uses vi.spyOn(global, 'fetch') to mock HTTP responses, providing
6
5
  * deterministic control over response timing.
7
6
  */
8
7
 
@@ -187,40 +186,14 @@ describe("webFetch — core fetch", () => {
187
186
  });
188
187
  });
189
188
 
190
- // ─── URL Safety ───────────────────────────────────────────────
189
+ // ─── URL Validation ───────────────────────────────────────────
191
190
 
192
- describe("webFetch — URL safety", () => {
191
+ describe("webFetch — URL validation", () => {
193
192
  it("rejects invalid URLs before fetching", async () => {
194
193
  const result = await webFetch({ url: "not a url" });
195
194
  expect(result.success).toBe(false);
196
195
  expect(result.error).toBe("Invalid URL");
197
196
  });
198
-
199
- it("rejects SSRF (localhost) before fetching", async () => {
200
- const result = await webFetch({ url: "http://localhost/admin" });
201
- expect(result.success).toBe(false);
202
- expect(result.error).toMatch(/URL blocked/i);
203
- });
204
-
205
- it("rejects SSRF (private IP) before fetching", async () => {
206
- const result = await webFetch({ url: "http://127.0.0.1/secret" });
207
- expect(result.success).toBe(false);
208
- expect(result.error).toMatch(/URL blocked/i);
209
- });
210
-
211
- it("rejects dangerous schemes before fetching", async () => {
212
- const result = await webFetch({ url: "file:///etc/passwd" });
213
- expect(result.success).toBe(false);
214
- expect(result.error).toMatch(/URL blocked/i);
215
- });
216
-
217
- it("rejects URLs containing secrets before fetching", async () => {
218
- const result = await webFetch({
219
- url: "http://example.com/api?api_key=secret123",
220
- });
221
- expect(result.success).toBe(false);
222
- expect(result.error).toMatch(/URL blocked/i);
223
- });
224
197
  });
225
198
 
226
199
  // ─── JavaScript Detection ─────────────────────────────────────
@@ -314,11 +287,12 @@ describe("webFetch — content capping", () => {
314
287
  });
315
288
 
316
289
  it("preserves both temp files when two large fetches share a taskId", async () => {
290
+ const tid = "preserve-shared";
317
291
  mockFetch({ body: `<p>${longText(6000)}</p>` });
318
- const a = await webFetch({ url: "http://example.com/a", taskId: "shared" });
292
+ const a = await webFetch({ url: "http://example.com/a", taskId: tid });
319
293
 
320
294
  mockFetch({ body: `<p>${"B".repeat(6000)}</p>` });
321
- const b = await webFetch({ url: "http://example.com/b", taskId: "shared" });
295
+ const b = await webFetch({ url: "http://example.com/b", taskId: tid });
322
296
 
323
297
  expect(a.filePath).toBeTruthy();
324
298
  expect(b.filePath).toBeTruthy();
@@ -37,7 +37,7 @@ def main() -> None:
37
37
  write_response(req_id, result="pong")
38
38
 
39
39
  elif method == "browser.init":
40
- # Phase 0: accept plugin config forwarded from the adapter after ping.
40
+ # Accept plugin config forwarded from the adapter after ping.
41
41
  write_response(req_id, result={"ok": True})
42
42
 
43
43
  elif method == "shutdown":
@@ -43,8 +43,7 @@ import { isAbsolute, join } from "node:path";
43
43
  * and non-empty; otherwise defaults to
44
44
  * `~/.pi/agent/pi-lean-portal/user-backends/`.
45
45
  *
46
- * Vendored locally — see the module doc for the rationale. Sprint 2's
47
- * generic runner reuses this same helper for consistency.
46
+ * Vendored locally — reused by the contributed test runner for consistency.
48
47
  */
49
48
  export function userBackendsDir(): string {
50
49
  const env = process.env.PI_USER_BACKENDS_DIR;
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Test setup: isolate each worker's temp directory using process.pid.
3
+ *
4
+ * vitest's default `forks` pool runs each test file in a separate Node
5
+ * process with a unique PID. Keying PI_BROWSER_TEMP_DIR on the PID
6
+ * gives each worker a disjoint filesystem namespace, preventing
7
+ * cross-worker `removeAllSnapshotFiles()` races that cause flaky
8
+ * ENOENT / null-result failures in snapshot-cache.test.ts.
9
+ *
10
+ * This file must be listed in vitest's `setupFiles` so it runs *before*
11
+ * any test file import, including the module-level `const BROWSER_TEMP_DIR`
12
+ * evaluation in `core/shared/paths.ts`.
13
+ */
14
+ import { tmpdir } from "node:os";
15
+ import { rmSync } from "node:fs";
16
+
17
+ const dir = `${tmpdir()}/pi-lean-portal-test-${process.pid}`;
18
+ process.env.PI_BROWSER_TEMP_DIR = dir;
19
+
20
+ process.on("exit", () => {
21
+ try {
22
+ rmSync(dir, { recursive: true, force: true });
23
+ } catch {
24
+ /* best-effort cleanup */
25
+ }
26
+ });
@@ -190,7 +190,7 @@ describe("loadFullConfig().browser", () => {
190
190
  });
191
191
  });
192
192
 
193
- // ─── Default plugin fallback (Phase 0b) ─────────────────────────
193
+ // ─── Default plugin fallback ───────────────────────────────────────
194
194
 
195
195
  describe("loadFullConfig().plugins default fallback", () => {
196
196
  it("returns the four shipped backends when browser.plugins is absent", () => {
@@ -3,7 +3,7 @@
3
3
  * test harness itself works correctly against a known-good fixture.
4
4
  *
5
5
  * These tests exercise the structural (non-browser) contract only.
6
- * Real-browser behavioral tests run in chromium-py.test.ts (Phase B6).
6
+ * Real-browser behavioral tests run in the per-backend contract test files.
7
7
  */
8
8
 
9
9
  import { runContractTests } from "./helpers/plugin-contract.js";
@@ -259,7 +259,7 @@ describe("DEFAULT_BACKENDS_ROOT", () => {
259
259
  });
260
260
  });
261
261
 
262
- // ─── Multi-root discovery (Phase 0b) ─────────────────────────────
262
+ // ─── Multi-root discovery ────────────────────────────────────────────
263
263
 
264
264
  describe("detectPluginType — multi-root", () => {
265
265
  let packageRoot: string;
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Unit tests for `probeUserBackend` (Sprint 1, AC 1.2).
2
+ * Unit tests for `probeUserBackend`.
3
3
  *
4
4
  * Structural — no real browser, no MiniWoB content. Uses a temp
5
5
  * directory as `PI_USER_BACKENDS_DIR` so nothing is written to the
@@ -814,12 +814,12 @@ describeStderr("stderr capture", () => {
814
814
  // ═════════════════════════════════════════════════════════════════════
815
815
 
816
816
  // ══════════════════════════════════════════════════════════════════════
817
- // browser.init RPC (Phase 0)
817
+ // browser.init RPC
818
818
  // ══════════════════════════════════════════════════════════════════════
819
819
 
820
820
  const describeBrowserInit = PYTHON_AVAILABLE ? describe : describe.skip;
821
821
 
822
- describeBrowserInit("browser.init RPC (Phase 0)", () => {
822
+ describeBrowserInit("browser.init RPC", () => {
823
823
  /**
824
824
  * Build a bridge that records every method received (in order) and
825
825
  * optionally answers `browser.init` with success or an error.
@@ -1087,7 +1087,7 @@ describeBrowserInit("browser.init RPC (Phase 0)", () => {
1087
1087
  });
1088
1088
 
1089
1089
  // ═════════════════════════════════════════════════════════════════════
1090
- // PYTHONPATH injection (Phase 0b)
1090
+ // PYTHONPATH injection
1091
1091
  // ═════════════════════════════════════════════════════════════════════
1092
1092
 
1093
1093
  /**
@@ -1095,7 +1095,7 @@ describeBrowserInit("browser.init RPC (Phase 0)", () => {
1095
1095
  * module-level `DEFAULT_BACKENDS_ROOT` only, never `this`), so it can be
1096
1096
  * invoked without binding. These unit tests don't need a subprocess.
1097
1097
  */
1098
- describe("_buildPythonPath — PYTHONPATH injection (Phase 0b)", () => {
1098
+ describe("_buildPythonPath — PYTHONPATH injection", () => {
1099
1099
  it("includes the package python-base directory when PYTHONPATH is unset", () => {
1100
1100
  const adapter = createAdapter();
1101
1101
  const saved = process.env.PYTHONPATH;
@@ -1131,73 +1131,69 @@ describe("_buildPythonPath — PYTHONPATH injection (Phase 0b)", () => {
1131
1131
 
1132
1132
  const describePythonPathEnv = PYTHON_AVAILABLE ? describe : describe.skip;
1133
1133
 
1134
- describePythonPathEnv(
1135
- "PYTHONPATH reaches the spawned bridge (Phase 0b)",
1136
- () => {
1137
- it("the bridge process env includes the package python-base path", async () => {
1138
- const dir = mkdtempSync(join(tmpdir(), "pi-lean-portal-pypath-"));
1139
- const bridgePath = join(dir, "bridge.py");
1140
- const lines: string[] = [
1141
- "import json, sys, os",
1142
- "sys.stderr.write('PYPATH:' + (os.environ.get('PYTHONPATH') or '') + '\\n')",
1143
- "sys.stderr.flush()",
1144
- "for _line in sys.stdin:",
1145
- " _line = _line.strip()",
1146
- " if not _line: continue",
1147
- " _req = json.loads(_line)",
1148
- " _m = _req.get('method','')",
1149
- " _rid = _req.get('id')",
1150
- " if _m == 'ping':",
1151
- " sys.stdout.write(json.dumps({'jsonrpc':'2.0','id':_rid,'result':'pong'}) + '\\n')",
1152
- " sys.stdout.flush()",
1153
- " elif _m == 'browser.init':",
1154
- " sys.stdout.write(json.dumps({'jsonrpc':'2.0','id':_rid,'result':{'ok':True}}) + '\\n')",
1155
- " sys.stdout.flush()",
1156
- " elif _m == 'shutdown':",
1157
- " sys.stdout.write(json.dumps({'jsonrpc':'2.0','id':_rid,'result':'bye'}) + '\\n')",
1158
- " sys.stdout.flush()",
1159
- " break",
1160
- " elif _m == 'browser.navigate':",
1161
- " sys.stdout.write(json.dumps({'jsonrpc':'2.0','id':_rid,'result':{'success':True,'url':_req.get('params',{}).get('url',''),'title':'P','snapshot':'','elementCount':0}}) + '\\n')",
1162
- " sys.stdout.flush()",
1163
- " else:",
1164
- " sys.stdout.write(json.dumps({'jsonrpc':'2.0','id':_rid,'error':{'code':-32601,'message':'unknown'}}) + '\\n')",
1165
- " sys.stdout.flush()",
1166
- ];
1167
- writeFileSync(bridgePath, lines.join("\n"), "utf-8");
1168
- const adapter = new PythonPluginAdapter("pypath-test", {
1169
- bridgeScript: bridgePath,
1170
- pythonPath: "python3",
1171
- });
1134
+ describePythonPathEnv("PYTHONPATH reaches the spawned bridge", () => {
1135
+ it("the bridge process env includes the package python-base path", async () => {
1136
+ const dir = mkdtempSync(join(tmpdir(), "pi-lean-portal-pypath-"));
1137
+ const bridgePath = join(dir, "bridge.py");
1138
+ const lines: string[] = [
1139
+ "import json, sys, os",
1140
+ "sys.stderr.write('PYPATH:' + (os.environ.get('PYTHONPATH') or '') + '\\n')",
1141
+ "sys.stderr.flush()",
1142
+ "for _line in sys.stdin:",
1143
+ " _line = _line.strip()",
1144
+ " if not _line: continue",
1145
+ " _req = json.loads(_line)",
1146
+ " _m = _req.get('method','')",
1147
+ " _rid = _req.get('id')",
1148
+ " if _m == 'ping':",
1149
+ " sys.stdout.write(json.dumps({'jsonrpc':'2.0','id':_rid,'result':'pong'}) + '\\n')",
1150
+ " sys.stdout.flush()",
1151
+ " elif _m == 'browser.init':",
1152
+ " sys.stdout.write(json.dumps({'jsonrpc':'2.0','id':_rid,'result':{'ok':True}}) + '\\n')",
1153
+ " sys.stdout.flush()",
1154
+ " elif _m == 'shutdown':",
1155
+ " sys.stdout.write(json.dumps({'jsonrpc':'2.0','id':_rid,'result':'bye'}) + '\\n')",
1156
+ " sys.stdout.flush()",
1157
+ " break",
1158
+ " elif _m == 'browser.navigate':",
1159
+ " sys.stdout.write(json.dumps({'jsonrpc':'2.0','id':_rid,'result':{'success':True,'url':_req.get('params',{}).get('url',''),'title':'P','snapshot':'','elementCount':0}}) + '\\n')",
1160
+ " sys.stdout.flush()",
1161
+ " else:",
1162
+ " sys.stdout.write(json.dumps({'jsonrpc':'2.0','id':_rid,'error':{'code':-32601,'message':'unknown'}}) + '\\n')",
1163
+ " sys.stdout.flush()",
1164
+ ];
1165
+ writeFileSync(bridgePath, lines.join("\n"), "utf-8");
1166
+ const adapter = new PythonPluginAdapter("pypath-test", {
1167
+ bridgeScript: bridgePath,
1168
+ pythonPath: "python3",
1169
+ });
1170
+ try {
1171
+ await adapter.init({});
1172
+ const r = await adapter.navigate(
1173
+ "https://example.com",
1174
+ "pypath-t1",
1175
+ 30_000,
1176
+ );
1177
+ expect(r.success).toBe(true);
1178
+ const stderr =
1179
+ (adapter as unknown as Record<string, string>)["_stderrAccumulated"] ??
1180
+ "";
1181
+ const pypathLine = stderr
1182
+ .split("\n")
1183
+ .find((l) => l.startsWith("PYPATH:"));
1184
+ expect(pypathLine).toBeDefined();
1185
+ const pypath = (pypathLine ?? "").slice("PYPATH:".length);
1186
+ expect(pypath).toContain(join(DEFAULT_BACKENDS_ROOT, "python-base"));
1187
+ } finally {
1188
+ await adapter.cleanupAll().catch(() => {});
1172
1189
  try {
1173
- await adapter.init({});
1174
- const r = await adapter.navigate(
1175
- "https://example.com",
1176
- "pypath-t1",
1177
- 30_000,
1178
- );
1179
- expect(r.success).toBe(true);
1180
- const stderr =
1181
- (adapter as unknown as Record<string, string>)[
1182
- "_stderrAccumulated"
1183
- ] ?? "";
1184
- const pypathLine = stderr
1185
- .split("\n")
1186
- .find((l) => l.startsWith("PYPATH:"));
1187
- expect(pypathLine).toBeDefined();
1188
- const pypath = (pypathLine ?? "").slice("PYPATH:".length);
1189
- expect(pypath).toContain(join(DEFAULT_BACKENDS_ROOT, "python-base"));
1190
- } finally {
1191
- await adapter.cleanupAll().catch(() => {});
1192
- try {
1193
- rmSync(dir, { recursive: true, force: true });
1194
- } catch {
1195
- /* ignore */
1196
- }
1190
+ rmSync(dir, { recursive: true, force: true });
1191
+ } catch {
1192
+ /* ignore */
1197
1193
  }
1198
- });
1199
- },
1200
- );
1194
+ }
1195
+ });
1196
+ });
1201
1197
 
1202
1198
  const describePersistence = PYTHON_AVAILABLE ? describe : describe.skip;
1203
1199
 
@@ -64,19 +64,6 @@ describe("Router dispatch", () => {
64
64
  expect(mock.calls.get("navigate")).toBeUndefined();
65
65
  });
66
66
 
67
- it("blocks SSRF URLs without calling the plugin", async () => {
68
- const result = await router.navigate("http://localhost:8080/");
69
- expect(result.success).toBe(false);
70
- expect(result.error).toMatch(/blocked/i);
71
- expect(mock.calls.get("navigate")).toBeUndefined();
72
- });
73
-
74
- it("blocks private IP ranges", async () => {
75
- const result = await router.navigate("http://192.168.1.1/");
76
- expect(result.success).toBe(false);
77
- expect(result.error).toMatch(/blocked/i);
78
- });
79
-
80
67
  it("returns error for unknown strategy", async () => {
81
68
  const result = await router.navigate("https://example.com", {
82
69
  strategy: "nonexistent",
@@ -836,7 +823,7 @@ describe("bot detection UX", () => {
836
823
  });
837
824
  });
838
825
 
839
- // ─── Profile-aware auto-recovery (Phase 7) ─────────────────
826
+ // ─── Profile-aware auto-recovery ────────────────────────────────
840
827
  describe("profile-aware auto-recovery", () => {
841
828
  it("restores profileName from lastNav on auto-creation", async () => {
842
829
  await sessionManager.removeAll();
@@ -11,8 +11,7 @@
11
11
  * invisible-py's lifecycle/deadlock tests) keep a hand-authored
12
12
  * file under ``__tests__/contributed/<name>-py/``.
13
13
  *
14
- * Gated by `CONTRIB_RUN === "1"` (the same env var Sprint 2 established
15
- * for the contributed MiniWoB parity helper). No-op in bare CI (no
14
+ * Gated by `CONTRIB_RUN === "1"` (the same env var the contributed MiniWoB parity helper established). No-op in bare CI (no
16
15
  * user-backends installed → discovered list empty → registers nothing)
17
16
  * and also no-op when `CONTRIB_RUN` is unset, so `npm run test:ci`
18
17
  * stays green with the file present even on a machine that has
@@ -20,8 +19,8 @@
20
19
  *
21
20
  * No stealth engine is **named** in this file. Discovery is purely
22
21
  * runtime — whatever the user has installed under `user-backends/` is
23
- * what runs. The shared suites own parametrized `it` titles (Sprint 1
24
- * mitigation) so a helper regression is attributable per-backend.
22
+ * what runs. The shared suites own parametrized `it` titles so a helper
23
+ * regression is attributable per-backend.
25
24
  *
26
25
  * @module
27
26
  */
@@ -188,6 +187,21 @@ ${Array.from({ length: 80 }, (_, i) => `<p>Paragraph ${i + 1}: content to make t
188
187
  // absent or has no matching entry, cfg stays {} (synthetic fallback).
189
188
  const entry = pluginCfg?.plugins.find((p) => p.name === name);
190
189
  const cfg = entry?.config ?? {};
190
+ // Camoufox ``152.0.4-beta.27+`` humanized-click motion makes
191
+ // ``locator.click(timeout=5s)`` flake/timeout and eats MiniWoB
192
+ // task budgets (~1.5s/click vs 10s task timers). The contributed
193
+ // suite exercises the backend *contract*, not human-emulation
194
+ // stealth, so force ``launch.humanize=false`` for every discovered
195
+ // backend. Real users keep the humanize=True default for
196
+ // evasion-sensitive browsing.
197
+ const launchOverride = {
198
+ ...((cfg.launch as Record<string, unknown> | undefined) ?? {}),
199
+ humanize: false,
200
+ };
201
+ const testCfg: Record<string, unknown> = {
202
+ ...cfg,
203
+ launch: launchOverride,
204
+ };
191
205
  if (!entry) {
192
206
  console.warn(
193
207
  `[contributed-runner] ${name}: no settings entry — synthetic config`,
@@ -202,12 +216,12 @@ ${Array.from({ length: 80 }, (_, i) => `<p>Paragraph ${i + 1}: content to make t
202
216
  new PythonPluginAdapter(`${name}-${idSuffix}`, {
203
217
  bridgeScript: probe.bridgePath,
204
218
  pythonPath:
205
- typeof cfg.pythonPath === "string"
206
- ? cfg.pythonPath
219
+ typeof testCfg.pythonPath === "string"
220
+ ? testCfg.pythonPath
207
221
  : probe.venvPython,
208
- capabilities: mergeCapabilities(cfg.capabilities),
209
- ...(typeof cfg.transportTimeoutMs === "number"
210
- ? { transportTimeoutMs: cfg.transportTimeoutMs }
222
+ capabilities: mergeCapabilities(testCfg.capabilities),
223
+ ...(typeof testCfg.transportTimeoutMs === "number"
224
+ ? { transportTimeoutMs: testCfg.transportTimeoutMs }
211
225
  : {}),
212
226
  });
213
227
 
@@ -232,7 +246,7 @@ ${Array.from({ length: 80 }, (_, i) => `<p>Paragraph ${i + 1}: content to make t
232
246
  getServerUrl: () => serverUrl,
233
247
  createPlugin: async () => {
234
248
  const p = buildAdapter("persist");
235
- await p.init(cfg);
249
+ await p.init(testCfg);
236
250
  return p;
237
251
  },
238
252
  });
@@ -247,12 +261,13 @@ ${Array.from({ length: 80 }, (_, i) => `<p>Paragraph ${i + 1}: content to make t
247
261
  registerContributedParitySuite({
248
262
  name,
249
263
  probe,
250
- ...(cfg.capabilities !== undefined
264
+ ...(testCfg.capabilities !== undefined
251
265
  ? {
252
- capabilities: cfg.capabilities as Partial<PluginCapabilities>,
266
+ capabilities:
267
+ testCfg.capabilities as Partial<PluginCapabilities>,
253
268
  }
254
269
  : {}),
255
- config: cfg,
270
+ config: testCfg,
256
271
  getBaseUrl: async () => miniwobBaseUrl,
257
272
  });
258
273
 
@@ -269,7 +284,7 @@ ${Array.from({ length: 80 }, (_, i) => `<p>Paragraph ${i + 1}: content to make t
269
284
  describe("quirks", () => {
270
285
  beforeAll(async () => {
271
286
  const p = buildAdapter("quirks");
272
- await p.init(cfg);
287
+ await p.init(testCfg);
273
288
  quirks_ = await p.describeQuirks();
274
289
  await p.cleanupAll().catch(() => {});
275
290
  });
@@ -277,7 +292,7 @@ ${Array.from({ length: 80 }, (_, i) => `<p>Paragraph ${i + 1}: content to make t
277
292
  it("eval_prefix: evaluate('1 + 1') === 2", async () => {
278
293
  if (!quirks_.eval_prefix) return;
279
294
  const p = buildAdapter("quirks-eval");
280
- await p.init(cfg);
295
+ await p.init(testCfg);
281
296
  const nav = await p.navigate(
282
297
  `${serverUrl}/simple`,
283
298
  "quirks-eval",
@@ -293,7 +308,7 @@ ${Array.from({ length: 80 }, (_, i) => `<p>Paragraph ${i + 1}: content to make t
293
308
  it("scroll_via_wheel: scroll moves the page", async () => {
294
309
  if (!quirks_.scroll_via_wheel) return;
295
310
  const p = buildAdapter("quirks-scroll");
296
- await p.init(cfg);
311
+ await p.init(testCfg);
297
312
  const nav = await p.navigate(
298
313
  `${serverUrl}/scroll`,
299
314
  "quirks-scroll",
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Snapshot Disk Cache tests — Phase 1 of the browser-intelligence plan.
2
+ * Snapshot Disk Cache tests — disk-backed snapshot caching.
3
3
  *
4
4
  * All tests are browser-free — no Chromium needed.
5
5
  * Cleanup of temp files happens in afterEach.