diff --git a/.changeset/lean-browse-snapshot.md b/.changeset/lean-browse-snapshot.md new file mode 100644 index 000000000..b83adcfce --- /dev/null +++ b/.changeset/lean-browse-snapshot.md @@ -0,0 +1,7 @@ +--- +"browse": patch +--- + +`browse snapshot` is now lean by default: it prints the formatted accessibility tree only, omitting the `xpathMap`/`urlMap` ref maps (~217KB / ~60K tokens on a content-heavy page) that were previously included on every snapshot. + +Ref-based element commands (`click`, `fill`, `select`, etc.) are unaffected — the ref maps are still captured and cached server-side, so refs resolve exactly as before. To get the maps in the output, pass the new `--full` flag. The `--compact` flag is now a deprecated no-op alias of the default (it prints a stderr-only, TTY-gated deprecation notice). diff --git a/packages/cli/skills/browse/SKILL.md b/packages/cli/skills/browse/SKILL.md index b4d2c38af..639590a7f 100644 --- a/packages/cli/skills/browse/SKILL.md +++ b/packages/cli/skills/browse/SKILL.md @@ -132,8 +132,8 @@ browse wait selector "#result" Page state: ```bash -browse snapshot -browse snapshot --compact +browse snapshot # formatted tree only +browse snapshot --full # also include ref maps (xpathMap, urlMap) browse get url browse get title browse get text body diff --git a/packages/cli/src/commands/snapshot.ts b/packages/cli/src/commands/snapshot.ts index 7c1110a27..0060dc12a 100644 --- a/packages/cli/src/commands/snapshot.ts +++ b/packages/cli/src/commands/snapshot.ts @@ -8,19 +8,23 @@ import { export default class Snapshot extends BrowseCommand { static override description = - "Print the active page accessibility snapshot and cache refs for element commands."; + "Print the active page accessibility snapshot and cache refs for element commands. Pass --full to also include the ref maps (xpathMap, urlMap), or run `browse refs` to print them."; static override examples = [ "browse snapshot", - "browse snapshot --compact", + "browse snapshot --full", "browse snapshot --filter submit", "browse snapshot --max-depth 4", ]; static override flags = { ...driverCommandFlags, + full: Flags.boolean({ + description: "Also include the ref maps (xpathMap, urlMap).", + }), compact: Flags.boolean({ - description: "Print only the formatted tree, without ref maps.", + description: + "Deprecated and has no effect; use --full to include the ref maps.", }), filter: Flags.string({ description: @@ -35,10 +39,15 @@ export default class Snapshot extends BrowseCommand { async run(): Promise { const { flags } = await this.parse(Snapshot); + if (flags.compact && process.stderr.isTTY) { + this.warn( + "`--compact` is deprecated and has no effect; use `--full` to include the ref maps.", + ); + } await runDriverCommandFromFlags( "snapshot", { - compact: flags.compact, + full: flags.full, filter: flags.filter, maxDepth: flags["max-depth"], }, diff --git a/packages/cli/src/lib/driver/commands/snapshot.ts b/packages/cli/src/lib/driver/commands/snapshot.ts index f174833a5..e77bf5d69 100644 --- a/packages/cli/src/lib/driver/commands/snapshot.ts +++ b/packages/cli/src/lib/driver/commands/snapshot.ts @@ -5,9 +5,9 @@ import type { DriverCommandHandlers } from "./types.js"; export const snapshotHandlers: DriverCommandHandlers = { async snapshot(manager, params) { - const { compact, filter, maxDepth } = z + const { full, filter, maxDepth } = z .object({ - compact: z.boolean().optional(), + full: z.boolean().optional(), filter: z.string().optional(), maxDepth: z.number().int().nonnegative().optional(), }) @@ -20,18 +20,17 @@ export const snapshotHandlers: DriverCommandHandlers = { }); const tree = formatSnapshotTree(snapshot.formattedTree, { - compact, filter, maxDepth, }); - if (compact) { - return { tree }; + if (full) { + return { + tree, + urlMap: snapshot.urlMap, + xpathMap: snapshot.xpathMap, + }; } - return { - tree, - urlMap: snapshot.urlMap, - xpathMap: snapshot.xpathMap, - }; + return { tree }; }, }; diff --git a/packages/cli/tests/driver-commands.test.ts b/packages/cli/tests/driver-commands.test.ts index b028c6171..9d3afef09 100644 --- a/packages/cli/tests/driver-commands.test.ts +++ b/packages/cli/tests/driver-commands.test.ts @@ -7,6 +7,7 @@ import { describe, expect, it, vi } from "vitest"; import { resolveSelector } from "../src/lib/driver/commands/selectors.js"; import { formatSnapshotTree } from "../src/lib/driver/commands/snapshot-format.js"; +import { snapshotHandlers } from "../src/lib/driver/commands/snapshot.js"; import { runtimeHandlers } from "../src/lib/driver/commands/runtime.js"; import { tabHandlers } from "../src/lib/driver/commands/tabs.js"; import { DRIVER_COMMAND_NAMES } from "../src/lib/driver/commands/types.js"; @@ -335,6 +336,37 @@ describe("driver commands", () => { ); }); + it("omits ref maps by default and includes them with --full", async () => { + const snap = { + formattedTree: "[0-1] RootWebArea: Test\n [0-2] button: Go", + urlMap: { "0-2": "https://example.com/go" }, + xpathMap: { "0-1": "/", "0-2": "/button[1]" }, + }; + const setRefMaps = vi.fn(); + const manager = { + activePage: async () => ({ snapshot: async () => snap }), + setRefMaps, + } as unknown as Parameters< + NonNullable<(typeof snapshotHandlers)["snapshot"]> + >[0]; + + const lean = await snapshotHandlers.snapshot!(manager, {}); + expect(lean).not.toHaveProperty("xpathMap"); + expect(lean).not.toHaveProperty("urlMap"); + expect(setRefMaps).toHaveBeenCalledTimes(1); + expect(setRefMaps).toHaveBeenLastCalledWith({ + urlMap: snap.urlMap, + xpathMap: snap.xpathMap, + }); + + const full = await snapshotHandlers.snapshot!(manager, { full: true }); + expect(full).toMatchObject({ + urlMap: snap.urlMap, + xpathMap: snap.xpathMap, + }); + expect(setRefMaps).toHaveBeenCalledTimes(2); + }); + it("exposes descriptive help for the new driver command surface", async () => { const commands = [ ["open"], diff --git a/packages/evals/core/tools/browse_cli.ts b/packages/evals/core/tools/browse_cli.ts index 0e8ec5c9a..b877f15b6 100644 --- a/packages/evals/core/tools/browse_cli.ts +++ b/packages/evals/core/tools/browse_cli.ts @@ -619,11 +619,13 @@ class BrowseCliPageHandle implements CorePageHandle { } async represent(): Promise { + // BROWSE_SNAPSHOT_FULL=1 includes the ref maps in the snapshot. + const full = process.env.BROWSE_SNAPSHOT_FULL === "1"; const snapshot = await this.runCommandAfterSelecting<{ tree: string; xpathMap?: Record; urlMap?: Record; - }>(["snapshot"]); + }>(full ? ["snapshot", "--full"] : ["snapshot"]); const content = snapshot.tree; return { @@ -632,7 +634,10 @@ class BrowseCliPageHandle implements CorePageHandle { metadata: { bytes: Buffer.byteLength(content, "utf8"), tokenEstimate: Math.ceil(content.length / 4), - refCount: Object.keys(snapshot.xpathMap ?? {}).length, + // Count refs from xpathMap when present, else from the tree. + refCount: snapshot.xpathMap + ? Object.keys(snapshot.xpathMap).length + : (content.match(/\[\d+-\d+\]/g)?.length ?? 0), }, raw: snapshot, };