Files
software-mansion__argent/packages/tool-server/test/chromium-debugger-dispatch.test.ts
filip131311 083947111b feat(chromium): network inspection via view-network-logs / view-network-request-details (#335)
## Summary

Adds network inspection for Chromium (CDP) devices by extending the
**existing** RN network tools rather than adding a new one — so there's
one network-viewing surface across platforms.

- **`view-network-logs`** and **`view-network-request-details`** now
also accept a `chromium-cdp-<port>` `device_id`. They dispatch on the
device id (the same pattern the ported `debugger-*` tools and
`gesture-tap` use): RN → the Metro-injected `fetch` interceptor;
Chromium → the browser's native CDP `Network` recording for the **active
tab**. Same flags, same output shape.
- Both widen their capability from RN-only to `DEBUGGER_TOOL_CAPABILITY`
(apple + android + chromium).

## How the Chromium side works

A lean **`NetworkManager`** (`chromium-server/network.ts`) keeps a
passive, always-on capped ring buffer fed by the page's CDP `Network`
events, re-attached on tab switch so it follows the active tab. No
in-app injection (the browser exposes `Network` natively).
`view-network-request-details` fetches the response body on demand via
`Network.getResponseBody`, and reuses the existing header-redaction.

**Read-only**, matching the `view-network-*` contract. Per review, the
earlier cut's standalone `chromium-network` tool and its Fetch-based
**routing / abort / mock / HAR** were **removed** — the read capability
on the existing tools is sufficient.

> Rebased onto `main` after #334 (tabs) merged — diff is network-only.

## Test plan

- [x] `NetworkManager`: recording (fields/duration/headers/failed,
oldest-first), `get()`, `reattach` (Network.enable), `dispose` (disable
+ detach).
- [x] Chromium branch of both view tools: capability now admits
chromium, `services()` routing, paginated render matching the RN format,
no-traffic message, header redaction, body via
`Network.getResponseBody`, not-found.
- [x] Updated the RN locked-tools registry test (these two are no longer
RN-locked; 15→13) + a positive assertion they now admit Chromium. RN
integration tests unchanged/passing.
- [x] Full suite **1136 pass**; `tsc --build` + `typecheck:tests` +
prettier clean; tool-description gate **9.25 ≥ 9.0**.
- [x] **Live (Chrome for Testing)**: `view-network-logs --device_id
chromium-cdp-9222` → `=== NETWORK LOGS (page 1/1, 1 total) === {id: …}
"GET example.com" 200 Document`; `view-network-request-details` → full
request/response headers + body via `getResponseBody`.
2026-06-15 15:31:51 +02:00

147 lines
6.6 KiB
TypeScript

import { describe, it, expect } from "vitest";
import {
DEBUGGER_TOOL_CAPABILITY,
RN_ONLY_TOOL_CAPABILITY,
debuggerServiceRef,
} from "../src/tools/debugger/debugger-service-ref";
import { CHROMIUM_JS_RUNTIME_DEBUGGER_NAMESPACE } from "../src/blueprints/chromium-js-runtime-debugger";
import { assertSupported, UnsupportedOperationError } from "../src/utils/capability";
import { resolveDevice } from "../src/utils/device-info";
// All tools that must reject Chromium at the HTTP capability gate. Pull the
// ToolDefinition directly so any future drift (someone re-adds an `chromium:`
// block on one of these) breaks this single test instead of slipping into a
// release. Kept exhaustive on purpose — a per-tool assertion is cheap and the
// list is the contract.
import { debuggerComponentTreeTool } from "../src/tools/debugger/debugger-component-tree";
import { debuggerReloadMetroTool } from "../src/tools/debugger/debugger-reload-metro";
import { debuggerInspectElementTool } from "../src/tools/debugger/debugger-inspect-element";
import { networkLogsTool } from "../src/tools/network/network-logs";
import { networkRequestTool } from "../src/tools/network/network-request";
import { reactProfilerAnalyzeTool } from "../src/tools/profiler/react/react-profiler-analyze";
import { reactProfilerComponentSourceTool } from "../src/tools/profiler/react/react-profiler-component-source";
import { reactProfilerCpuSummaryTool } from "../src/tools/profiler/react/react-profiler-cpu-summary";
import { reactProfilerFiberTreeTool } from "../src/tools/profiler/react/react-profiler-fiber-tree";
import { reactProfilerRendersTool } from "../src/tools/profiler/react/react-profiler-renders";
import { profilerCpuQueryTool } from "../src/tools/profiler/query/profiler-cpu-query";
import { profilerCommitQueryTool } from "../src/tools/profiler/query/profiler-commit-query";
import { profilerStackQueryTool } from "../src/tools/profiler/query/profiler-stack-query";
import { profilerLoadTool } from "../src/tools/profiler/query/profiler-load";
import { profilerCombinedReportTool } from "../src/tools/profiler/combined/profiler-combined-report";
const CHROMIUM_ID = "chromium-cdp-19222";
const IOS_ID = "AAAAAAAA-AAAA-AAAA-AAAA-AAAAAAAAAAAA";
const ANDROID_ID = "emulator-5554";
describe("debuggerServiceRef — platform dispatch", () => {
it("routes a Chromium device id to the ChromiumJsRuntimeDebugger blueprint", () => {
const ref = debuggerServiceRef({ port: 8081, device_id: CHROMIUM_ID });
expect(ref).toMatchObject({
urn: `${CHROMIUM_JS_RUNTIME_DEBUGGER_NAMESPACE}:${CHROMIUM_ID}`,
options: { device: resolveDevice(CHROMIUM_ID) },
});
});
it("routes an iOS UDID to the Metro-driven JsRuntimeDebugger blueprint", () => {
const ref = debuggerServiceRef({ port: 8081, device_id: IOS_ID });
expect(ref).toBe(`JsRuntimeDebugger:8081:${IOS_ID}`);
});
it("routes an Android serial to the Metro-driven JsRuntimeDebugger blueprint", () => {
const ref = debuggerServiceRef({ port: 8082, device_id: ANDROID_ID });
expect(ref).toBe(`JsRuntimeDebugger:8082:${ANDROID_ID}`);
});
it("tolerates a missing device_id — falls back to Metro URN so existing callers don't crash", () => {
// Mirrors the original template-literal behavior: `JsRuntimeDebugger:8081:undefined`
// is ugly but doesn't blow up at the dispatch site. Pre-chromium tests
// hit this path and relied on it.
const ref = debuggerServiceRef({ port: 8081 });
expect(typeof ref).toBe("string");
expect(ref as string).toMatch(/^JsRuntimeDebugger:8081:/);
});
});
describe("debugger tool capability gating — chromium", () => {
const chromiumDevice = resolveDevice(CHROMIUM_ID);
const iosDevice = resolveDevice(IOS_ID);
it("DEBUGGER_TOOL_CAPABILITY admits a Chromium device (ported tools)", () => {
expect(() =>
assertSupported("debugger-evaluate", DEBUGGER_TOOL_CAPABILITY, chromiumDevice)
).not.toThrow();
});
it("DEBUGGER_TOOL_CAPABILITY still admits iOS — port did not regress mobile support", () => {
expect(() =>
assertSupported("debugger-evaluate", DEBUGGER_TOOL_CAPABILITY, iosDevice)
).not.toThrow();
});
it("RN_ONLY_TOOL_CAPABILITY rejects a Chromium device (locked-out tools)", () => {
expect(() =>
assertSupported("debugger-component-tree", RN_ONLY_TOOL_CAPABILITY, chromiumDevice)
).toThrow(UnsupportedOperationError);
});
it("RN_ONLY_TOOL_CAPABILITY's rejection message names the tool and platform", () => {
try {
assertSupported("react-profiler-renders", RN_ONLY_TOOL_CAPABILITY, chromiumDevice);
throw new Error("expected throw");
} catch (err) {
const msg = (err as Error).message;
expect(msg).toContain("react-profiler-renders");
expect(msg).toContain("chromium");
expect(msg).toContain("app");
}
});
});
describe("RN-only tool registry — every locked tool actually rejects Chromium", () => {
const chromiumDevice = resolveDevice("chromium-cdp-19222");
// Source of truth for what must stay locked. If a tool is added/removed
// here, the maintainer is making an explicit Chromium-support decision.
const LOCKED_TOOLS = [
debuggerComponentTreeTool,
debuggerReloadMetroTool,
debuggerInspectElementTool,
reactProfilerAnalyzeTool,
reactProfilerComponentSourceTool,
reactProfilerCpuSummaryTool,
reactProfilerFiberTreeTool,
reactProfilerRendersTool,
profilerCpuQueryTool,
profilerCommitQueryTool,
profilerStackQueryTool,
profilerLoadTool,
profilerCombinedReportTool,
];
it.each(LOCKED_TOOLS.map((t) => [t.id, t] as const))(
"%s declares a capability and rejects Chromium",
(_id, tool) => {
expect(tool.capability).toBeDefined();
expect(() => assertSupported(tool.id, tool.capability!, chromiumDevice)).toThrow(
UnsupportedOperationError
);
}
);
it("matches the spec count — exactly 13 device-bound RN/iOS tools are locked", () => {
// Add to LOCKED_TOOLS above when locking a new tool; this guards against
// silent omissions. react-profiler-{start,stop,status} are factory-built
// and not exported as plain ToolDefinitions, so they're absent here even
// though they're locked — counted separately in the PR description.
// view-network-logs / view-network-request-details were UNLOCKED for
// Chromium (served via the Chromium server's Network recording) — see below.
expect(LOCKED_TOOLS).toHaveLength(13);
});
it("view-network-logs / view-network-request-details now ALSO support Chromium", () => {
for (const tool of [networkLogsTool, networkRequestTool]) {
expect(() => assertSupported(tool.id, tool.capability!, chromiumDevice)).not.toThrow();
}
});
});