pi-lean-dimension 0.5.0 → 0.6.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 (104) hide show
  1. package/README.md +5 -3
  2. package/node_modules/pi-lean-host/AGENTS.md +13 -6
  3. package/node_modules/pi-lean-host/__tests__/api-learn-fetch-recipe.test.ts +282 -10
  4. package/node_modules/pi-lean-host/__tests__/api-learn-multi-file.test.ts +8 -16
  5. package/node_modules/pi-lean-host/__tests__/api-probe.test.ts +276 -279
  6. package/node_modules/pi-lean-host/__tests__/api-scaffold.test.ts +12 -33
  7. package/node_modules/pi-lean-host/__tests__/api-toggle.test.ts +14 -12
  8. package/node_modules/pi-lean-host/__tests__/bootstrap-command.test.ts +1 -7
  9. package/node_modules/pi-lean-host/__tests__/delete-command.test.ts +1 -12
  10. package/node_modules/pi-lean-host/__tests__/guide-catalog.test.ts +484 -0
  11. package/node_modules/pi-lean-host/__tests__/helpers.test.ts +0 -132
  12. package/node_modules/pi-lean-host/__tests__/oauth-command.test.ts +95 -0
  13. package/node_modules/pi-lean-host/__tests__/oauth-flow.test.ts +163 -0
  14. package/node_modules/pi-lean-host/__tests__/oauth-mint.test.ts +79 -6
  15. package/node_modules/pi-lean-host/__tests__/parse-api-guide.test.ts +4 -409
  16. package/node_modules/pi-lean-host/__tests__/response-spill.test.ts +0 -1
  17. package/node_modules/pi-lean-host/__tests__/secrets-command.test.ts +19 -35
  18. package/node_modules/pi-lean-host/__tests__/smoke.test.ts +1 -82
  19. package/node_modules/pi-lean-host/__tests__/ssrf-guard.test.ts +98 -0
  20. package/node_modules/pi-lean-host/__tests__/test-utils.ts +73 -0
  21. package/node_modules/pi-lean-host/__tests__/tools.test.ts +2 -518
  22. package/node_modules/pi-lean-host/__tests__/transport.test.ts +178 -31
  23. package/node_modules/pi-lean-host/__tests__/verify-command.test.ts +1 -8
  24. package/node_modules/pi-lean-host/__tests__/verify-stamp.test.ts +1 -4
  25. package/node_modules/pi-lean-host/api-guides/boe/local-helper.test.ts +9 -20
  26. package/node_modules/pi-lean-host/api-guides/dnb/error-envelope.test.ts +6 -18
  27. package/node_modules/pi-lean-host/api-guides/dnb/resumption-token.test.ts +6 -18
  28. package/node_modules/pi-lean-host/api-guides/frost-sensorthings/dotted-key.test.ts +6 -18
  29. package/node_modules/pi-lean-host/api-guides/github/static-key.test.ts +7 -25
  30. package/node_modules/pi-lean-host/api-guides/inaturalist/derived-id.test.ts +6 -18
  31. package/node_modules/pi-lean-host/api-guides/internet-archive/multi-recipe.test.ts +6 -26
  32. package/node_modules/pi-lean-host/api-guides/stripe/has-more.test.ts +6 -18
  33. package/node_modules/pi-lean-host/api-guides/telegram-bot/path-auth.test.ts +7 -25
  34. package/node_modules/pi-lean-host/api-guides/twitch/oauth2.test.ts +12 -47
  35. package/node_modules/pi-lean-host/api-guides/twitch-user/oauth-user.test.ts +11 -52
  36. package/node_modules/pi-lean-host/api-guides/usgs/transform.test.ts +10 -27
  37. package/node_modules/pi-lean-host/api-guides/wikidata-search/numeric-cursor.test.ts +6 -18
  38. package/node_modules/pi-lean-host/api-guides/wikimedia-action/token-bag.test.ts +9 -20
  39. package/node_modules/pi-lean-host/core/auth.ts +2 -1
  40. package/node_modules/pi-lean-host/core/helpers.ts +29 -35
  41. package/node_modules/pi-lean-host/core/oauth-command.ts +10 -8
  42. package/node_modules/pi-lean-host/core/oauth-flow.ts +20 -4
  43. package/node_modules/pi-lean-host/core/parse-api-guide.ts +3 -5
  44. package/node_modules/pi-lean-host/core/transport.ts +1 -1
  45. package/node_modules/pi-lean-host/package.json +1 -1
  46. package/node_modules/pi-lean-host/tools/api-guide.ts +18 -57
  47. package/node_modules/pi-lean-host/tools/api-learn.ts +20 -49
  48. package/node_modules/pi-lean-host/tools/api-probe.ts +17 -26
  49. package/node_modules/pi-lean-host/tools/api-scaffold.ts +20 -42
  50. package/node_modules/pi-lean-host/tools/utils.ts +69 -0
  51. package/node_modules/pi-lean-portal/AGENTS.md +4 -3
  52. package/node_modules/pi-lean-portal/README.md +9 -9
  53. package/node_modules/pi-lean-portal/__tests__/accessibility-tree.test.ts +0 -15
  54. package/node_modules/pi-lean-portal/__tests__/browser-install.test.ts +737 -0
  55. package/node_modules/pi-lean-portal/__tests__/browser-navigate.test.ts +33 -2
  56. package/node_modules/pi-lean-portal/__tests__/browser-status.test.ts +48 -25
  57. package/node_modules/pi-lean-portal/__tests__/browser-toggle-profile.test.ts +3 -9
  58. package/node_modules/pi-lean-portal/__tests__/browser-toggle.test.ts +3 -29
  59. package/node_modules/pi-lean-portal/__tests__/fetch-backend.test.ts +51 -0
  60. package/node_modules/pi-lean-portal/__tests__/helpers/__pycache__/mock-python-bridge.cpython-313-pytest-9.1.1.pyc +0 -0
  61. package/node_modules/pi-lean-portal/__tests__/helpers/__pycache__/mock-python-bridge.cpython-313.pyc +0 -0
  62. package/node_modules/pi-lean-portal/__tests__/helpers/mock-pi.ts +34 -0
  63. package/node_modules/pi-lean-portal/__tests__/helpers/mock-python-bridge.py +14 -24
  64. package/node_modules/pi-lean-portal/__tests__/plugin-registry.test.ts +0 -90
  65. package/node_modules/pi-lean-portal/__tests__/python-adapter.test.ts +32 -125
  66. package/node_modules/pi-lean-portal/__tests__/router-dispatch.test.ts +12 -170
  67. package/node_modules/pi-lean-portal/__tests__/router-session.test.ts +39 -151
  68. package/node_modules/pi-lean-portal/__tests__/web-guides.test.ts +1 -44
  69. package/node_modules/pi-lean-portal/backends/chromium/index.ts +1 -1
  70. package/node_modules/pi-lean-portal/backends/firefox/index.ts +1 -1
  71. package/node_modules/pi-lean-portal/backends/playwright-base/playwright-plugin.ts +1 -1
  72. package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/playwright_base.cpython-312.pyc +0 -0
  73. package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/playwright_base.cpython-313.pyc +0 -0
  74. package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/transport.cpython-312.pyc +0 -0
  75. package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/__pycache__/transport.cpython-313.pyc +0 -0
  76. package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/playwright_base.py +43 -57
  77. package/node_modules/pi-lean-portal/backends/python-base/pi_browser_bridge/transport.py +6 -21
  78. package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/test_accessibility.cpython-313-pytest-9.1.1.pyc +0 -0
  79. package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/test_playwright_base_quirks.cpython-313-pytest-9.1.1.pyc +0 -0
  80. package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/test_playwright_base_quirks.cpython-313.pyc +0 -0
  81. package/node_modules/pi-lean-portal/backends/python-base/tests/__pycache__/test_transport.cpython-313-pytest-9.1.1.pyc +0 -0
  82. package/node_modules/pi-lean-portal/backends/python-base/tests/test_playwright_base_quirks.py +157 -73
  83. package/node_modules/pi-lean-portal/backends/python-base/tests/test_transport.py +15 -41
  84. package/node_modules/pi-lean-portal/browser-install.ts +454 -0
  85. package/node_modules/pi-lean-portal/browser-status.ts +21 -0
  86. package/node_modules/pi-lean-portal/browser-toggle.ts +13 -11
  87. package/node_modules/pi-lean-portal/core/fetch-backend.ts +2 -2
  88. package/node_modules/pi-lean-portal/core/router.ts +7 -32
  89. package/node_modules/pi-lean-portal/core/shared/session-manager.ts +1 -3
  90. package/node_modules/pi-lean-portal/index.ts +1 -18
  91. package/node_modules/pi-lean-portal/package.json +2 -1
  92. package/node_modules/pi-lean-portal/tools/browser-navigate.ts +6 -2
  93. package/node_modules/pi-lean-portal/tools/utils.ts +5 -6
  94. package/node_modules/pi-lean-search/AGENTS.md +5 -3
  95. package/node_modules/pi-lean-search/__tests__/web-search.test.ts +180 -127
  96. package/node_modules/pi-lean-search/index.ts +0 -3
  97. package/node_modules/pi-lean-search/package.json +1 -1
  98. package/node_modules/pi-lean-search/web-search-tool.ts +22 -13
  99. package/node_modules/yaml/browser/dist/compose/resolve-flow-scalar.js +19 -18
  100. package/node_modules/yaml/browser/dist/nodes/Alias.js +25 -23
  101. package/node_modules/yaml/dist/compose/resolve-flow-scalar.js +19 -18
  102. package/node_modules/yaml/dist/nodes/Alias.js +25 -23
  103. package/node_modules/yaml/package.json +1 -1
  104. package/package.json +4 -4
@@ -10,7 +10,7 @@
10
10
  * Tests that DO require Python (run only when python3 is available):
11
11
  * - Full JSON-RPC cycle via mock-python-bridge.py
12
12
  * - All BrowserPlugin operations
13
- * - Error handling (bridge errors, protocol violations, timeouts)
13
+ * - Error handling (bridge application and session errors)
14
14
  * - Process lifecycle (spawn, ping handshake, shutdown, crash recovery)
15
15
  * - Stderr capture
16
16
  */
@@ -185,8 +185,7 @@ describe("constructor validation", () => {
185
185
 
186
186
  it("throws when bridgeScript is not provided", () => {
187
187
  expect(
188
- () =>
189
- new PythonPluginAdapter("test", {} as unknown as PythonBridgeConfig),
188
+ () => new PythonPluginAdapter("test", {} as unknown as PythonBridgeConfig),
190
189
  ).toThrow(/bridgeScript is required/);
191
190
  });
192
191
 
@@ -441,21 +440,23 @@ describeIntegration("integration with mock Python bridge", () => {
441
440
  expect(result.url).toBe("https://example.com/again");
442
441
  });
443
442
 
444
- it("returns error when bridge returns an application error", async () => {
443
+ it("rejects with PythonBridgeError on bridge error responses", async () => {
445
444
  await adapter.navigate("https://example.com", "t15", 30_000);
446
- // Use a method that the mock bridge handles as an error
447
- // We'll send a specially crafted call by accessing the private method
448
- // Actually — just test that error results work through the public API
449
- // The mock bridge handles browser.missingSession for this.
450
- // We can't easily trigger this through public ops, but we can test
451
- // that the adapter returns success:false for failures
452
- });
453
-
454
- it("returns error from navigate when bridge returns failure", async () => {
455
- // Adapter can return success:false if the bridge returns error data
456
- // For this, we'd need the bridge to return error for browser.navigate
457
- // which the mock doesn't do. Instead, test via browser.error method
458
- // which causes an application error.
445
+ const rpc = privateMethod<
446
+ (
447
+ method: string,
448
+ params: Record<string, unknown>,
449
+ timeoutMs?: number,
450
+ ) => Promise<unknown>
451
+ >(adapter, "_rpcCall");
452
+ // Application error (browser.error → -32000)
453
+ await expect(rpc.call(adapter, "browser.error", {})).rejects.toThrow(
454
+ PythonBridgeError,
455
+ );
456
+ // Session error (browser.missingSession → -32002)
457
+ await expect(rpc.call(adapter, "browser.missingSession", {})).rejects.toThrow(
458
+ /No active session/,
459
+ );
459
460
  });
460
461
  });
461
462
 
@@ -490,11 +491,7 @@ describe("session manager integration", () => {
490
491
  const adapter = createAdapter();
491
492
  await adapter.init();
492
493
  try {
493
- const result = await adapter.navigate(
494
- "https://example.com",
495
- "s1",
496
- 30_000,
497
- );
494
+ const result = await adapter.navigate("https://example.com", "s1", 30_000);
498
495
  expect(result.success).toBe(true);
499
496
  expect(result.url).toBe("https://example.com");
500
497
  } finally {
@@ -556,10 +553,7 @@ describeCrashRecovery("crash recovery", () => {
556
553
  await adapter.navigate("https://example.com", "crash-t4", 30_000);
557
554
 
558
555
  // Crash #1
559
- const p1 = privateMethod<{ kill: (s: string) => void }>(
560
- adapter,
561
- "_process",
562
- );
556
+ const p1 = privateMethod<{ kill: (s: string) => void }>(adapter, "_process");
563
557
  p1!.kill("SIGKILL");
564
558
  await new Promise((r) => setTimeout(r, 200));
565
559
 
@@ -572,10 +566,7 @@ describeCrashRecovery("crash recovery", () => {
572
566
  expect(r1.success).toBe(true);
573
567
 
574
568
  // Crash #2
575
- const p2 = privateMethod<{ kill: (s: string) => void }>(
576
- adapter,
577
- "_process",
578
- );
569
+ const p2 = privateMethod<{ kill: (s: string) => void }>(adapter, "_process");
579
570
  p2!.kill("SIGKILL");
580
571
  await new Promise((r) => setTimeout(r, 200));
581
572
 
@@ -654,73 +645,6 @@ describeStderr("stderr capture", () => {
654
645
  }
655
646
  }
656
647
  });
657
-
658
- it("auto-restarts bridge after external SIGKILL", async () => {
659
- const dir = mkdtempSync(join(tmpdir(), "pi-lean-portal-crash-"));
660
- const bridgePath = writeBridge(
661
- {
662
- "browser.navigate":
663
- "params = _req.get('params', {})\n" +
664
- "url = params.get('url', '')\n" +
665
- "sys.stdout.write(json.dumps({\n" +
666
- "'jsonrpc':'2.0',\n" +
667
- "'id':_rid,\n" +
668
- "'result':{\n" +
669
- " 'success':True,\n" +
670
- " 'url':url,\n" +
671
- " 'title':'P',\n" +
672
- " 'snapshot':'- [link] x',\n" +
673
- " 'elementCount':1\n" +
674
- "}\n" +
675
- "}) + '\\n')",
676
- },
677
- dir,
678
- );
679
-
680
- const adapter = new PythonPluginAdapter("crash-test", {
681
- bridgeScript: bridgePath,
682
- pythonPath: "python3",
683
- });
684
-
685
- try {
686
- await adapter.init();
687
-
688
- // First call starts the process
689
- const r1 = await adapter.navigate(
690
- "https://example.com/1",
691
- "crash-s1",
692
- 30_000,
693
- );
694
- expect(r1.success).toBe(true);
695
-
696
- // Get the child process and kill it
697
- const proc = privateMethod<{ kill: (s: string) => void }>(
698
- adapter,
699
- "_process",
700
- );
701
- expect(proc).not.toBeNull();
702
- proc!.kill("SIGKILL");
703
-
704
- // Wait for the process to actually die
705
- await new Promise((r) => setTimeout(r, 200));
706
-
707
- // Next call should auto-restart and succeed
708
- const r2 = await adapter.navigate(
709
- "https://example.com/2",
710
- "crash-s2",
711
- 30_000,
712
- );
713
- expect(r2.success).toBe(true);
714
- expect(r2.url).toBe("https://example.com/2");
715
- } finally {
716
- await adapter.cleanupAll().catch(() => {});
717
- try {
718
- rmSync(dir, { recursive: true, force: true });
719
- } catch {
720
- /* ignore */
721
- }
722
- }
723
- });
724
648
  });
725
649
 
726
650
  // ═════════════════════════════════════════════════════════════════════
@@ -802,19 +726,12 @@ describeBrowserInit("browser.init RPC", () => {
802
726
  try {
803
727
  await adapter.init({ launch: { headless: true } });
804
728
  // Trigger process start + handshake + navigate
805
- const r = await adapter.navigate(
806
- "https://example.com",
807
- "init-t1",
808
- 30_000,
809
- );
729
+ const r = await adapter.navigate("https://example.com", "init-t1", 30_000);
810
730
  expect(r.success).toBe(true);
811
731
 
812
732
  const stderr =
813
- (adapter as unknown as Record<string, string>)["_stderrAccumulated"] ??
814
- "";
815
- const orderLines = stderr
816
- .split("\n")
817
- .filter((l) => l.startsWith("ORDER:"));
733
+ (adapter as unknown as Record<string, string>)["_stderrAccumulated"] ?? "";
734
+ const orderLines = stderr.split("\n").filter((l) => l.startsWith("ORDER:"));
818
735
  expect(orderLines.length).toBeGreaterThan(0);
819
736
  // The second ORDER emission captures ping,init — proving init came
820
737
  // immediately after ping, before any other RPC.
@@ -879,8 +796,7 @@ describeBrowserInit("browser.init RPC", () => {
879
796
  .catch(() => {});
880
797
 
881
798
  const stderr =
882
- (adapter as unknown as Record<string, string>)["_stderrAccumulated"] ??
883
- "";
799
+ (adapter as unknown as Record<string, string>)["_stderrAccumulated"] ?? "";
884
800
  const cfgLine = stderr.split("\n").find((l) => l.startsWith("CFG:"));
885
801
  expect(cfgLine).toBeDefined();
886
802
  const parsed = JSON.parse(cfgLine!.slice("CFG:".length));
@@ -947,8 +863,7 @@ describeBrowserInit("browser.init RPC", () => {
947
863
  // (The adapter resets _stderrAccumulated on restart, so we must
948
864
  // snapshot now to prove the first handshake sent init.)
949
865
  const stderrBefore =
950
- (adapter as unknown as Record<string, string>)["_stderrAccumulated"] ??
951
- "";
866
+ (adapter as unknown as Record<string, string>)["_stderrAccumulated"] ?? "";
952
867
  const orderBefore = stderrBefore
953
868
  .split("\n")
954
869
  .filter((l) => l.startsWith("ORDER:"));
@@ -979,8 +894,7 @@ describeBrowserInit("browser.init RPC", () => {
979
894
  // show ping → browser.init as the first two methods, proving init
980
895
  // is re-sent on crash recovery, not just on the first start.
981
896
  const stderrAfter =
982
- (adapter as unknown as Record<string, string>)["_stderrAccumulated"] ??
983
- "";
897
+ (adapter as unknown as Record<string, string>)["_stderrAccumulated"] ?? "";
984
898
  const orderAfter = stderrAfter
985
899
  .split("\n")
986
900
  .filter((l) => l.startsWith("ORDER:"));
@@ -1037,8 +951,8 @@ describe("_buildPythonPath — PYTHONPATH injection", () => {
1037
951
  // Appended (not prepended): the user's entry comes first.
1038
952
  expect(value.indexOf(userEntry)).toBeLessThan(value.indexOf(expected));
1039
953
  } finally {
1040
- if (saved !== undefined) process.env.PYTHONPATH = saved;
1041
- else delete process.env.PYTHONPATH;
954
+ if (saved === undefined) delete process.env.PYTHONPATH;
955
+ else process.env.PYTHONPATH = saved;
1042
956
  }
1043
957
  });
1044
958
  });
@@ -1083,18 +997,11 @@ describePythonPathEnv("PYTHONPATH reaches the spawned bridge", () => {
1083
997
  });
1084
998
  try {
1085
999
  await adapter.init({});
1086
- const r = await adapter.navigate(
1087
- "https://example.com",
1088
- "pypath-t1",
1089
- 30_000,
1090
- );
1000
+ const r = await adapter.navigate("https://example.com", "pypath-t1", 30_000);
1091
1001
  expect(r.success).toBe(true);
1092
1002
  const stderr =
1093
- (adapter as unknown as Record<string, string>)["_stderrAccumulated"] ??
1094
- "";
1095
- const pypathLine = stderr
1096
- .split("\n")
1097
- .find((l) => l.startsWith("PYPATH:"));
1003
+ (adapter as unknown as Record<string, string>)["_stderrAccumulated"] ?? "";
1004
+ const pypathLine = stderr.split("\n").find((l) => l.startsWith("PYPATH:"));
1098
1005
  expect(pypathLine).toBeDefined();
1099
1006
  const pypath = (pypathLine ?? "").slice("PYPATH:".length);
1100
1007
  expect(pypath).toContain(join(DEFAULT_BACKENDS_ROOT, "python-base"));
@@ -173,12 +173,7 @@ describe("Router dispatch", () => {
173
173
  });
174
174
 
175
175
  it("auto-creates session from lastNav and returns snapshot", async () => {
176
- sessionManager.setLastNav(
177
- "default",
178
- "https://example.com",
179
- "Mock",
180
- "mock",
181
- );
176
+ sessionManager.setLastNav("default", "https://example.com", "Mock", "mock");
182
177
  const result = await router.snapshot("default");
183
178
  expect(result.success).toBe(true);
184
179
  // Should have navigated to recreate the session
@@ -239,12 +234,7 @@ describe("Router dispatch", () => {
239
234
  });
240
235
 
241
236
  it("returns snapshot on auto-created session (stale refs)", async () => {
242
- sessionManager.setLastNav(
243
- "default",
244
- "https://example.com",
245
- "Mock",
246
- "mock",
247
- );
237
+ sessionManager.setLastNav("default", "https://example.com", "Mock", "mock");
248
238
  const result = await router.click("default", "@e1");
249
239
  expect(result.success).toBe(true);
250
240
  // Should have navigated to recreate session
@@ -264,12 +254,7 @@ describe("Router dispatch", () => {
264
254
  snapshot: "",
265
255
  elementCount: 0,
266
256
  };
267
- sessionManager.setLastNav(
268
- "default",
269
- "https://example.com",
270
- "Mock",
271
- "mock",
272
- );
257
+ sessionManager.setLastNav("default", "https://example.com", "Mock", "mock");
273
258
  const result = await router.click("default", "@e1");
274
259
  expect(result.success).toBe(false);
275
260
  expect(result.error).toMatch(/no active session/i);
@@ -314,12 +299,7 @@ describe("Router dispatch", () => {
314
299
  });
315
300
 
316
301
  it("returns snapshot on auto-created session (stale refs)", async () => {
317
- sessionManager.setLastNav(
318
- "default",
319
- "https://example.com",
320
- "Mock",
321
- "mock",
322
- );
302
+ sessionManager.setLastNav("default", "https://example.com", "Mock", "mock");
323
303
  const result = await router.type("default", "@e2", "hello");
324
304
  expect(result.success).toBe(true);
325
305
  expect(mock.calls.get("type")).toBeUndefined();
@@ -481,6 +461,11 @@ describe("Router dispatch", () => {
481
461
  expect(session).toBeDefined();
482
462
  });
483
463
 
464
+ it("serves follow-up calls after navigate with a custom taskId", async () => {
465
+ await router.navigate("https://example.com", { taskId: "seq" });
466
+ expect((await router.snapshot("seq")).success).toBe(true);
467
+ });
468
+
484
469
  it("isolates sessions by different taskIds", async () => {
485
470
  await router.navigate("https://alpha.com", { taskId: "task-a" });
486
471
  await router.navigate("https://beta.com", { taskId: "task-b" });
@@ -541,91 +526,6 @@ describe("compactSnapshot()", () => {
541
526
  });
542
527
  });
543
528
 
544
- // ─── Session persistence across sequential calls ─────────────────
545
-
546
- describe("session persistence across sequential calls", () => {
547
- let mock: MockPlugin;
548
-
549
- beforeEach(() => {
550
- pluginRegistry.clear();
551
- mock = new MockPlugin("mock");
552
- pluginRegistry.register(mock, makeConfig({ name: "mock", enabled: true }));
553
- });
554
-
555
- afterEach(async () => {
556
- await sessionManager.removeAll();
557
- pluginRegistry.clear();
558
- });
559
-
560
- it("navigate then screenshotToTemp succeeds with same taskId", async () => {
561
- await router.navigate("https://example.com", { taskId: "seq-test-1" });
562
-
563
- const path = await router.screenshotToTemp("seq-test-1");
564
- expect(path).toBeDefined();
565
- expect(path).toMatch(/\/screenshot-seq-test-1\.jpg$/);
566
- });
567
-
568
- it("navigate then snapshot succeeds with same taskId", async () => {
569
- await router.navigate("https://example.com", { taskId: "seq-test-2" });
570
-
571
- const result = await router.snapshot("seq-test-2");
572
- expect(result.success).toBe(true);
573
- expect(result.snapshot).toBeTruthy();
574
- });
575
-
576
- it("navigate click type succeed sequentially", async () => {
577
- await router.navigate("https://example.com", { taskId: "seq-test-3" });
578
-
579
- const clickResult = await router.click("seq-test-3", "@e1");
580
- expect(clickResult.success).toBe(true);
581
-
582
- const typeResult = await router.type("seq-test-3", "@e2", "hello");
583
- expect(typeResult.success).toBe(true);
584
- });
585
-
586
- it("navigate then goBack succeeds", async () => {
587
- await router.navigate("https://example.com", { taskId: "seq-test-4" });
588
-
589
- const result = await router.goBack("seq-test-4");
590
- expect(result.success).toBe(true);
591
- });
592
-
593
- it("navigate then scroll succeeds", async () => {
594
- await router.navigate("https://example.com", { taskId: "seq-test-5" });
595
-
596
- const result = await router.scroll("seq-test-5", "down");
597
- expect(result.success).toBe(true);
598
- });
599
-
600
- it("navigate then evaluate succeeds", async () => {
601
- await router.navigate("https://example.com", { taskId: "seq-test-6" });
602
-
603
- const result = await router.evaluate("seq-test-6", "1 + 1");
604
- expect(result.success).toBe(true);
605
- });
606
-
607
- it("navigate then press succeeds", async () => {
608
- await router.navigate("https://example.com", { taskId: "seq-test-7" });
609
-
610
- const result = await router.press("seq-test-7", "Enter");
611
- expect(result.success).toBe(true);
612
- });
613
-
614
- it("navigate then getConsoleMessages succeeds", async () => {
615
- await router.navigate("https://example.com", { taskId: "seq-test-9" });
616
-
617
- const result = await router.getConsoleMessages("seq-test-9");
618
- expect(result.success).toBe(true);
619
- });
620
-
621
- it("navigate then clearConsole succeeds", async () => {
622
- await router.navigate("https://example.com", { taskId: "seq-test-10" });
623
-
624
- const result = await router.clearConsole("seq-test-10");
625
- expect(result.success).toBe(true);
626
- });
627
- });
628
-
629
529
  // ─── Bot detection UX ───────────────────────────────────────────
630
530
 
631
531
  describe("bot detection UX", () => {
@@ -711,9 +611,7 @@ describe("bot detection UX", () => {
711
611
  expect(result.snapshot).toMatch(/fingerprint:/);
712
612
 
713
613
  // The cache file must actually exist on disk
714
- const match = result.snapshot!.match(
715
- /Full snapshot cached at (\/[^\s]+)/,
716
- );
614
+ const match = result.snapshot!.match(/Full snapshot cached at (\/[^\s]+)/);
717
615
  expect(match).not.toBeNull();
718
616
  const cachePath = match![1]!;
719
617
  expect(existsSync(cachePath)).toBe(true);
@@ -863,12 +761,7 @@ describe("bot detection UX", () => {
863
761
 
864
762
  it("no profileName restored when lastNav has no profile", async () => {
865
763
  await sessionManager.removeAll();
866
- sessionManager.setLastNav(
867
- "default",
868
- "https://example.com",
869
- "Mock",
870
- "mock",
871
- );
764
+ sessionManager.setLastNav("default", "https://example.com", "Mock", "mock");
872
765
 
873
766
  const result = await router.snapshot("default");
874
767
  expect(result.success).toBe(true);
@@ -1050,12 +943,7 @@ describe("Router cookie dispatch", () => {
1050
943
 
1051
944
  describe("auto-recovery on cookie operations", () => {
1052
945
  it("getCookies auto-creates a session from lastNav if none exists", async () => {
1053
- sessionManager.setLastNav(
1054
- "default",
1055
- "https://example.com",
1056
- "Mock",
1057
- "mock",
1058
- );
946
+ sessionManager.setLastNav("default", "https://example.com", "Mock", "mock");
1059
947
 
1060
948
  const result = await router.getCookies("default");
1061
949
 
@@ -1064,51 +952,5 @@ describe("Router cookie dispatch", () => {
1064
952
  expect(mock.calls.get("navigate")).toHaveLength(1);
1065
953
  expect(mock.calls.get("getCookies")).toHaveLength(1);
1066
954
  });
1067
-
1068
- it("addCookies auto-creates a session from lastNav", async () => {
1069
- sessionManager.setLastNav(
1070
- "default",
1071
- "https://example.com",
1072
- "Mock",
1073
- "mock",
1074
- );
1075
-
1076
- const result = await router.addCookies("default", [
1077
- { name: "test", value: "val" },
1078
- ]);
1079
-
1080
- expect(result.success).toBe(true);
1081
- expect(mock.calls.get("navigate")).toHaveLength(1);
1082
- expect(mock.calls.get("addCookies")).toHaveLength(1);
1083
- });
1084
-
1085
- it("clearCookies auto-creates a session from lastNav", async () => {
1086
- sessionManager.setLastNav(
1087
- "default",
1088
- "https://example.com",
1089
- "Mock",
1090
- "mock",
1091
- );
1092
-
1093
- const result = await router.clearCookies("default");
1094
-
1095
- expect(result.success).toBe(true);
1096
- expect(mock.calls.get("navigate")).toHaveLength(1);
1097
- expect(mock.calls.get("clearCookies")).toHaveLength(1);
1098
- });
1099
-
1100
- it("returns error when auto-creation fails (lastNav plugin gone)", async () => {
1101
- sessionManager.setLastNav(
1102
- "default",
1103
- "https://example.com",
1104
- "Mock",
1105
- "extinct-plugin",
1106
- );
1107
-
1108
- const result = await router.getCookies("default");
1109
-
1110
- expect(result.success).toBe(false);
1111
- expect(result.error).toMatch(/no active session/i);
1112
- });
1113
955
  });
1114
956
  });