all repos — sz-chrome-remote-extension @ master

pi-chrome, patched to drive a signed-in browser on another host through a broker, with an MCP server

test-suite/unit/bridge-resilience.test.mjs (view raw)

 1
 2
 3
 4
 5
 6
 7
 8
 9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 110
 111
 112
 113
 114
 115
 116
 117
 118
 119
 120
 121
 122
 123
 124
 125
 126
 127
 128
 129
 130
 131
 132
 133
 134
 135
 136
 137
 138
// Bridge transport resilience in the shipped worker, plus same-process (subagent) loading in index.ts.
// Worker timers are scaled 1000x down so the real 45s /next deadline elapses in ~45ms.
import assert from "node:assert/strict";
import fs from "node:fs";
import vm from "node:vm";
import { stripTypeScriptTypes } from "node:module";
import { test } from "node:test";

const workerSource = fs.readFileSync(new URL("../../extensions/chrome-profile-bridge/browser-extension/service_worker.js", import.meta.url), "utf8");
const indexSource = fs.readFileSync(new URL("../../extensions/chrome-profile-bridge/index.ts", import.meta.url), "utf8");

function loadWorker(fetchImpl) {
  const listener = { addListener() {}, removeListener() {} };
  const chrome = {
    runtime: { id: "test", getManifest: () => ({ version: "0.0.1" }), getURL: (f) => `chrome-extension://test/${f}`, onInstalled: listener, onStartup: listener, reload() {} },
    alarms: { create() {}, onAlarm: listener }, action: { onClicked: listener }, webNavigation: { onCommitted: listener },
    debugger: { onDetach: listener }, scripting: {}, tabs: { onUpdated: listener },
  };
  const sandbox = {
    chrome, console, AbortController, URL,
    navigator: { userAgent: "unit-test" },
    setTimeout: (fn, ms, ...args) => setTimeout(fn, Math.ceil((ms || 0) / 1000), ...args),
    clearTimeout, setInterval: () => 0, clearInterval() {},
    fetch: fetchImpl,
  };
  sandbox.self = sandbox;
  vm.runInNewContext(workerSource, sandbox);
  return sandbox;
}

function response(status, body, headers = {}) {
  return { ok: status >= 200 && status < 300, status, headers: { get: (name) => headers[name.toLowerCase()] ?? null }, json: async () => body };
}

test("a half-open /next long poll is aborted at its deadline and polling resumes", async () => {
  const requests = [];
  let aborted = 0;
  const worker = loadWorker((url, options = {}) => {
    requests.push({ url, options });
    if (requests.length === 1) {
      // Zombie socket: never settles unless aborted.
      return new Promise((_resolve, reject) => {
        options.signal.addEventListener("abort", () => {
          aborted++;
          const error = new Error("The operation was aborted");
          error.name = "AbortError";
          reject(error);
        });
      });
    }
    return Promise.reject(new Error("bridge down"));
  });
  const first = worker.pollLoop();
  assert.ok(requests[0]?.options.signal, "the /next fetch carries an abort signal");
  // Resolves only after the scaled 45s deadline fires and the 2s backoff elapses.
  await first;
  assert.equal(aborted, 1, "the stalled /next fetch was aborted");
  await worker.pollLoop();
  assert.equal(requests.length, 2, "a later poll opens a fresh /next request instead of staying parked");
});

test("a command result is posted exactly once, retried on transport failure, and not retried on 4xx", async () => {
  const posts = [];
  let postStatus = [500, 200];
  const worker = loadWorker(async (url, options = {}) => {
    if (url.includes("/result")) {
      posts.push(JSON.parse(options.body));
      const status = postStatus.shift() ?? 200;
      if (status === 0) throw new Error("fetch failed");
      return response(status, {});
    }
    throw new Error("not polling in this test");
  });
  await worker.handleCommand({ id: "cmd-1", action: "tab.version", params: {} });
  assert.equal(posts.length, 2, "one retry after HTTP 500");
  assert.ok(posts.every((p) => p.id === "cmd-1" && p.ok === true), "the retry re-sends the success payload, not an error payload");

  posts.length = 0;
  postStatus = [0, 0, 0];
  await worker.handleCommand({ id: "cmd-2", action: "tab.version", params: {} });
  assert.equal(posts.length, 3, "network failures retry up to the attempt limit");
  assert.ok(posts.every((p) => p.ok === true), "a failed success-post never turns into a second, error result");

  posts.length = 0;
  postStatus = [404];
  await worker.handleCommand({ id: "cmd-3", action: "no.such.action", params: {} });
  assert.equal(posts.length, 1, "4xx (unknown command id) is final");
  assert.equal(posts[0].ok, false);
  assert.match(posts[0].error, /Unknown action/);
});

test("cdp.call widens only its own command deadline", () => {
  const worker = loadWorker(async () => { throw new Error("offline"); });
  assert.equal(worker.commandTimeoutMs("page.click", { timeoutMs: 90_000 }), 25_000);
  assert.equal(worker.commandTimeoutMs("cdp.call", {}), 25_000);
  assert.equal(worker.commandTimeoutMs("cdp.call", { timeoutMs: 60_000 }), 65_000);
  assert.equal(worker.commandTimeoutMs("cdp.call", { timeoutMs: 10_000_000 }), 125_000, "capped below the MV3 worker lifetime");
});

// ---- index.ts: a subagent session loads pi-chrome again in the same process. ----
function loadFactoryPrelude(globalState, root) {
  const from = indexSource.indexOf("\tconst alreadyLoaded = globalState[PI_CHROME_GLOBAL_KEY];");
  const to = indexSource.indexOf("\tconst bridge = new ChromeProfileBridge(", from);
  assert.ok(from > 0 && to > from, "factory prelude section");
  const body = stripTypeScriptTypes(`(() => {\n${indexSource.slice(from, to)}\nreturn true;\n})()`);
  const warnings = [];
  const token = Symbol(root);
  const sandbox = {
    globalState, currentRoot: root, instanceToken: token, PI_CHROME_GLOBAL_KEY: "loaded", PI_CHROME_VERSION: "9.9.9",
    console: { warn: (msg) => warnings.push(msg) },
  };
  const loaded = vm.runInNewContext(body, sandbox);
  return { loaded: loaded === true, token, warnings };
}

test("a same-root second load (subagent session) is not skipped, and does not steal the singleton flag", () => {
  const globalState = {};
  const parent = loadFactoryPrelude(globalState, "/pkg/pi-chrome");
  assert.equal(parent.loaded, true);
  assert.equal(globalState.loaded.token, parent.token);
  const subagent = loadFactoryPrelude(globalState, "/pkg/pi-chrome");
  assert.equal(subagent.loaded, true, "subagent gets its own pi-chrome instance and chrome_* tools");
  assert.equal(subagent.warnings.length, 0);
  assert.equal(globalState.loaded.token, parent.token, "only the first instance owns (and later clears) the flag");
});

test("a second install root is still treated as a duplicate", () => {
  const globalState = {};
  loadFactoryPrelude(globalState, "/npm/pi-chrome");
  const other = loadFactoryPrelude(globalState, "/checkout/pi-chrome");
  assert.equal(other.loaded, false);
  assert.match(other.warnings[0], /already loaded from \/npm\/pi-chrome/);
});

test("a stale same-root flag from an older release does not block loading", () => {
  const globalState = { loaded: { version: "0.15.19", root: "/pkg/pi-chrome" } };
  assert.equal(loadFactoryPrelude(globalState, "/pkg/pi-chrome").loaded, true);
});