Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@ Cutting that release is tracked in
- An MCP stdio integration suite covering initialization, tool discovery,
browser-command calls, malformed and oversized input, and rejection of local
CLI commands.
- CLI parser coverage for the previously untested session, navigation,
diagnostics, flow, network emulation, reporting, and local command paths.
- Progressive context pruning: `inspect --context summary|outline|text|actions|full`
with `--task` ranking, `--within @rN` scoping, and `--limit` / `--budget` /
`--depth` bounds. Every focused response reports `contextStats` and `omitted`.
Expand Down
67 changes: 66 additions & 1 deletion apps/headless/Tests/HeadlessProtocolTests/ProtocolTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,11 @@ struct ProtocolTests {
}

static func rejectsUnexpectedRequestFields() throws {
let data = Data(#"{"id":"request-1","version":"0.1","command":"ping","parameters":{},"execute":"anything"}"#.utf8)
let valid = Data(#"{"id":"request-1","version":"0.4","command":"ping","parameters":{}}"#.utf8)
let decoded = try ProtocolCodec.decodeLine(CommandRequest.self, from: valid)
try expect(decoded.command == .ping, "the strict-field control request should decode")

let data = Data(#"{"id":"request-1","version":"0.4","command":"ping","parameters":{},"execute":"anything"}"#.utf8)
try expectThrows("unexpected top-level request fields should be rejected") {
_ = try ProtocolCodec.decodeLine(CommandRequest.self, from: data)
}
Expand Down Expand Up @@ -502,6 +506,66 @@ struct ProtocolTests {
}
}

static func cliCommandMatrix() throws {
let remoteCommands: [([String], CommandName)] = [
(["status"], .ping),
(["stop"], .shutdown),
(["session", "create", "qa"], .sessionCreate),
(["session", "list"], .sessionList),
(["session", "close", "qa"], .sessionClose),
(["back"], .back),
(["reload"], .reload),
(["tour", "--pace", "750"], .tour),
(["capture-info"], .captureInfo),
(["artifacts", "list"], .artifactList),
(["qa", "report"], .qaReport),
(["qa", "clear"], .qaClear),
(["performance", "get"], .performanceGet),
(["animations", "list"], .animationList),
(["report", "create", "--output", "report.json"], .reportCreate),
(["flow", "start"], .flowStart),
(["flow", "stop", "--output", "flow.json"], .flowStop),
(["network", "emulate", "--offline", "--latency", "100"], .networkEmulate),
(["network", "mock", "clear"], .networkMockClear),
]
for (arguments, command) in remoteCommands {
let invocation = try CLIParser().parse(arguments)
try expect(
invocation.request?.command == command,
"\(arguments.joined(separator: " ")) should parse as \(command.rawValue)"
)
}

let localCommands: [([String], LocalCommand)] = [
(["start"], .start),
(["help"], .help),
(["--help"], .help),
]
for (arguments, command) in localCommands {
let invocation = try CLIParser().parse(arguments)
try expect(
invocation.local == command && invocation.request == nil,
"\(arguments.joined(separator: " ")) should stay local"
)
}

let sessionCreate = try CLIParser().parse(["session", "create", "qa"])
try expect(sessionCreate.request?.parameters["name"] == .string("qa"), "session create name should parse")
let sessionClose = try CLIParser().parse(["session", "close", "qa"])
try expect(sessionClose.request?.session == "qa", "session close target should parse")
let tour = try CLIParser().parse(["tour", "--pace", "750"])
try expect(tour.request?.parameters["pace"] == .number(750), "tour pace should parse")
let emulation = try CLIParser().parse([
"network", "emulate", "--offline", "--latency", "100",
"--download-kbps", "2000", "--upload-kbps", "500",
])
try expect(emulation.request?.parameters["offline"] == .bool(true), "offline emulation should parse")
try expect(emulation.request?.parameters["latencyMs"] == .number(100), "emulation latency should parse")
try expectThrows("unknown trailing arguments should not be ignored") {
_ = try CLIParser().parse(["performance", "get", "extra"])
}
}

static func chromiumRuntimeSelection() throws {
let runtimeInvocation = try CLIParser().parse(["runtime"])
try expect(runtimeInvocation.local == .runtime, "runtime diagnostics command should parse")
Expand Down Expand Up @@ -924,6 +988,7 @@ struct ProtocolTests {
("client timeout parity", clientTimeoutsMatchCommandBounds),
("CLI P1 artifacts", cliP1Artifacts),
("CLI P2 commands and boundaries", cliP2CommandsAndBoundaries),
("CLI command matrix", cliCommandMatrix),
("Chromium runtime selection", chromiumRuntimeSelection),
("artifact store round-trip", artifactStoreRoundTrip),
("screenshot series helpers", screenshotSeriesHelpers),
Expand Down
5 changes: 5 additions & 0 deletions docs/roadmap/improvements-backlog.md
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,11 @@ flow start-stop/network emulate/mock clear/session ops/status/stop/start/help).
Also tighten `rejectsUnexpectedRequestFields` (`ProtocolTests.swift:109-114`)
which currently passes for the wrong reason.

**Progress:** MCP stdio coverage now exercises C3 end to end. The audited CLI
command matrix is covered, and the unexpected-field test first proves its
current-version control request decodes before adding the forbidden field.
Recording, flow, artifact, runtime, transport, and host-specific gaps remain.

**D3. Web CI:** ~~`next build` + eslint on PR (site can break invisibly today).~~
**Done** — the `web` job in `ci.yml` runs `pnpm --filter @headless/web lint`
and `build`.
Expand Down
Loading