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/index.html (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
 139
 140
 141
 142
 143
 144
 145
 146
 147
 148
 149
 150
 151
 152
 153
 154
 155
 156
 157
 158
 159
 160
 161
 162
 163
 164
 165
 166
 167
 168
 169
 170
 171
 172
 173
 174
 175
 176
 177
 178
 179
 180
 181
 182
 183
 184
 185
 186
 187
 188
 189
 190
 191
 192
 193
<!doctype html>
<meta charset="utf-8">
<title>pi-chrome browser-control benchmark</title>
<link rel="stylesheet" href="_style.css">
<body>
<main style="max-width:1280px">
<h1>pi-chrome browser-control benchmark</h1>
<p>This benchmark measures how well Chrome-control tools let agents do real browser work: DOM discovery, trusted input, keyboard/focus, scroll, drag/drop, files, frames, clipboard, and observability.</p>

<section class="panel">
  <div class="controls">
    <button id="refresh">Refresh verdicts</button>
    <button id="clear">Clear local verdicts</button>
    <button id="copy">Copy JSON report</button>
    <label>Filter <input id="filter" placeholder="category, id, expected..." /></label>
    <label>Mode
      <select id="mode">
        <option value="trusted">trusted</option>
        <option value="synthetic">synthetic</option>
        <option value="manual">manual</option>
      </select>
    </label>
  </div>
  <p class="hint">Open a row, drive it with <code>chrome_*</code> tools, then return here. Verdicts are read from <code>localStorage</code>. Expected outcomes come from <code>manifest.json</code>.</p>
  <textarea id="copyFallback" class="copy-fallback" readonly aria-label="JSON report fallback"></textarea>
</section>

<div id="summary" class="summary"></div>
<table id="tbl">
  <thead>
    <tr>
      <th>id</th><th>gate</th><th>category</th><th>verdict</th><th>expected</th><th>baseline</th><th>risk</th><th>goal / notes</th><th>open</th>
    </tr>
  </thead>
  <tbody></tbody>
</table>

<h2>Long-horizon hermetic tasks</h2>
<p class="hint">These WebArena/BrowserGym-inspired tasks use deterministic in-page graders and fresh <code>$RUN_ID</code> state. They test multi-step browser work beyond event fidelity.</p>
<table id="taskTbl">
  <thead><tr><th>id</th><th>difficulty</th><th>category</th><th>intent</th><th>requires</th><th>open</th></tr></thead>
  <tbody></tbody>
</table>

<h2>Agent run loop</h2>
<pre class="code">python3 -m http.server 8765
# For each manifest entry:
# 1. chrome_navigate http://127.0.0.1:8765/&lt;file&gt;
# 2. chrome_snapshot before acting; prefer uid selectors.
# 3. Execute recipe with selected mode, adapting descriptive frame/shadow selectors to tool uids.
# 4. chrome_evaluate JSON.stringify({v:window.__verdict,r:window.__reason,e:window.__events?.slice(-20)})
# 5. Compare verdict to manifest.expected[mode]; CONDITIONAL means inspect prerequisites/notes.</pre>
</main>
<script>
let manifest = [];
let taskManifest = [];
const tbody = document.querySelector("#tbl tbody");
const taskTbody = document.querySelector("#taskTbl tbody");
const modeEl = document.getElementById("mode");
const filterEl = document.getElementById("filter");

function verdictFor(id) {
  const raw = localStorage.getItem("pi-chrome-suite:" + id);
  if (!raw) return null;
  try { return JSON.parse(raw); } catch { return null; }
}
function classFor(v) {
  if (!v) return "pending";
  return String(v).toLowerCase();
}
function expectedClass(actual, expected) {
  if (!actual) return "pending";
  if (expected === "CONDITIONAL") return actual === "PASS" ? "ok" : "conditional";
  return actual === expected ? "ok" : "mismatch";
}
function report() {
  const mode = modeEl.value;
  return manifest.map(m => {
    const v = verdictFor(m.id);
    return {
      id: m.id,
      category: m.category,
      gate: m.gate ?? "core",
      file: m.file,
      verdict: v?.verdict ?? "PENDING",
      reason: v?.reason ?? [],
      expected: m.expected?.[mode] ?? "—",
      timestamp: v?.ts ?? null,
      goal: m.goal,
      prerequisites: m.prerequisites ?? [],
      notes: m.notes ?? []
    };
  });
}
function paint() {
  const q = filterEl.value.trim().toLowerCase();
  const mode = modeEl.value;
  tbody.innerHTML = "";
  const rows = report();
  const counts = rows.reduce((acc, r) => (acc[r.verdict] = (acc[r.verdict] || 0) + 1, acc), {});
  const mismatches = rows.filter(r => r.verdict !== "PENDING" && r.expected !== "CONDITIONAL" && r.expected !== "—" && r.verdict !== r.expected).length;
  const coreMismatches = rows.filter(r => (r.gate ?? "core") === "core" && r.verdict !== "PENDING" && r.expected !== "CONDITIONAL" && r.expected !== "—" && r.verdict !== r.expected).length;
  const gates = rows.reduce((acc, r) => (acc[r.gate ?? "core"] = (acc[r.gate ?? "core"] || 0) + 1, acc), {});
  document.getElementById("summary").innerHTML = `
    <span class="pill pass">PASS ${counts.PASS || 0}</span>
    <span class="pill fail">FAIL ${counts.FAIL || 0}</span>
    <span class="pill skip">SKIP ${counts.SKIP || 0}</span>
    <span class="pill warn">WARN ${counts.WARN || 0}</span>
    <span class="pill pending">PENDING ${counts.PENDING || 0}</span>
    <span class="pill ${coreMismatches ? "fail" : "pass"}">core unexpected ${coreMismatches}</span>
    <span class="pill ${mismatches ? "fail" : "pass"}">all unexpected ${mismatches}</span>
    <span class="pill expected">core ${gates.core || 0}</span>
    <span class="pill expected">conditional ${gates.conditional || 0}</span>
    <span class="pill expected">quality ${gates.quality || 0}</span>
  `;
  for (const m of manifest) {
    const v = verdictFor(m.id);
    const verdict = v?.verdict ?? "PENDING";
    const expected = m.expected?.[mode] ?? "—";
    const hay = JSON.stringify({ id:m.id, gate:m.gate, category:m.category, verdict, expected, goal:m.goal, notes:m.notes }).toLowerCase();
    if (q && !hay.includes(q)) continue;
    const tr = document.createElement("tr");
    tr.className = expectedClass(verdict, expected);
    const reason = (v?.reason || []).join(" / ");
    const notes = [...(m.prerequisites || []).map(x => "pre: " + x), ...(m.notes || [])].join("; ");
    tr.innerHTML = `
      <td><code>${m.id}</code></td>
      <td><span class="pill expected">${m.gate || "core"}</span></td>
      <td>${m.category || "—"}</td>
      <td><span class="pill ${classFor(verdict)}">${verdict}</span><div class="reason">${escapeHtml(reason)}</div></td>
      <td><span class="pill expected">${expected}</span></td>
      <td><span class="pill ${m.manualBaseline === "verified" ? "pass" : "pending"}">${m.manualBaseline || "unverified"}</span></td>
      <td>${m.flakeRisk ? `<span class="risk">${m.flakeRisk}</span>` : ""}</td>
      <td><div>${escapeHtml(m.goal || "")}</div><div class="notes">${escapeHtml(notes)}</div><details><summary>recipe</summary><pre>${escapeHtml(JSON.stringify(m.recipe || [], null, 2))}</pre></details></td>
      <td><a target="_blank" href="${m.file}">open</a></td>
    `;
    tbody.appendChild(tr);
  }
}
function escapeHtml(s) {
  return String(s).replace(/[&<>"']/g, c => ({"&":"&amp;","<":"&lt;",">":"&gt;","\"":"&quot;","'":"&#39;"}[c]));
}
function paintTasks() {
  taskTbody.innerHTML = "";
  const run = "manual-" + new Date().toISOString().slice(0,10);
  for (const t of taskManifest) {
    const url = t.startUrl.replaceAll("$RUN_ID", run).replaceAll("$SEED", String(t.seed ?? 0));
    const tr = document.createElement("tr");
    tr.innerHTML = `
      <td><code>${t.taskId || t.id}</code><div class="notes">seed=${t.seed ?? 0} maxSteps=${t.maxSteps ?? "—"}</div></td>
      <td><span class="pill expected">${t.difficulty}</span></td>
      <td>${t.category}</td>
      <td>${escapeHtml(t.goal || t.intent)}</td>
      <td><div class="notes">${escapeHtml((t.requires || []).join(", "))}</div><div class="notes">actions: ${escapeHtml((t.actionSubsets || []).join(", "))}</div></td>
      <td><a target="_blank" href="${url}">open</a></td>
    `;
    taskTbody.appendChild(tr);
  }
}
async function init() {
  manifest = await fetch("manifest.json", { cache: "no-store" }).then(r => r.json());
  taskManifest = await fetch("task-manifest.json", { cache: "no-store" }).then(r => r.json()).catch(() => []);
  paint();
  paintTasks();
}
document.getElementById("refresh").addEventListener("click", paint);
document.getElementById("clear").addEventListener("click", () => {
  if (!confirm("Clear pi-chrome-suite verdicts for this origin?")) return;
  for (const m of manifest) localStorage.removeItem("pi-chrome-suite:" + m.id);
  paint();
});
document.getElementById("copy").addEventListener("click", async () => {
  const text = JSON.stringify({ mode: modeEl.value, generatedAt: new Date().toISOString(), rows: report() }, null, 2);
  const box = document.getElementById("copyFallback");
  box.value = text;
  box.style.display = "block";
  box.focus();
  box.select();
  let copied = false;
  try { await navigator.clipboard.writeText(text); copied = true; } catch {}
  if (!copied) {
    try { copied = document.execCommand("copy"); } catch {}
  }
  box.dataset.copied = copied ? "true" : "false";
  console.log(text);
});
modeEl.addEventListener("change", paint);
filterEl.addEventListener("input", paint);
window.addEventListener("storage", paint);
setInterval(paint, 1000);
init();
</script>
</body>