From e02d8719ba92849722b4610ed81aea32c8f1fb3a Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Sat, 17 Jan 2026 12:34:18 -0800 Subject: [PATCH 01/42] test: add first integration test for CLI->socket navigate command --- test/integration/cli-socket.test.ts | 73 +++++++++++++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 test/integration/cli-socket.test.ts diff --git a/test/integration/cli-socket.test.ts b/test/integration/cli-socket.test.ts new file mode 100644 index 0000000..7573722 --- /dev/null +++ b/test/integration/cli-socket.test.ts @@ -0,0 +1,73 @@ +import { spawn } from "node:child_process"; +import * as fs from "node:fs"; +import * as net from "node:net"; +import * as path from "node:path"; +import { afterEach, beforeEach, describe, expect, it } from "vitest"; + +const SOCKET_PATH = "/tmp/surf.sock"; +const CLI_PATH = path.join(__dirname, "../../native/cli.cjs"); + +describe("CLI to Socket communication", () => { + let server: net.Server | null = null; + let existingSocketBackedUp = false; + + beforeEach(() => { + // Back up existing socket if present (don't break running surf) + if (fs.existsSync(SOCKET_PATH)) { + fs.renameSync(SOCKET_PATH, `${SOCKET_PATH}.backup`); + existingSocketBackedUp = true; + } + }); + + afterEach(() => { + if (server) { + server.close(); + server = null; + } + // Clean up test socket + if (fs.existsSync(SOCKET_PATH)) { + fs.unlinkSync(SOCKET_PATH); + } + // Restore backed up socket + if (existingSocketBackedUp && fs.existsSync(`${SOCKET_PATH}.backup`)) { + fs.renameSync(`${SOCKET_PATH}.backup`, SOCKET_PATH); + existingSocketBackedUp = false; + } + }); + + it("sends navigate command as tool_request to socket", async () => { + // Create a mock socket server that captures the request + const receivedData = await new Promise((resolve, reject) => { + const timeout = setTimeout(() => reject(new Error("Test timeout")), 5000); + + server = net.createServer((socket) => { + let data = ""; + socket.on("data", (chunk) => { + data += chunk.toString(); + // Send a response so CLI doesn't hang + socket.write(`${JSON.stringify({ result: { success: true } })}\n`); + }); + socket.on("close", () => { + clearTimeout(timeout); + resolve(data); + }); + }); + + server.listen(SOCKET_PATH, () => { + // Run CLI command + const cli = spawn("node", [CLI_PATH, "go", "https://example.com"]); + + cli.on("error", (err) => { + clearTimeout(timeout); + reject(err); + }); + }); + }); + + const request = JSON.parse(receivedData.trim()); + expect(request.type).toBe("tool_request"); + expect(request.method).toBe("execute_tool"); + expect(request.params.tool).toBe("navigate"); + expect(request.params.args.url).toBe("https://example.com"); + }); +}); From 48ef76c1793a7ab3554c4edc9c3d280df9726ed9 Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Sat, 17 Jan 2026 12:34:53 -0800 Subject: [PATCH 02/42] test: add integration test for click command with element ref --- test/integration/cli-socket.test.ts | 31 +++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/test/integration/cli-socket.test.ts b/test/integration/cli-socket.test.ts index 7573722..9ccdb69 100644 --- a/test/integration/cli-socket.test.ts +++ b/test/integration/cli-socket.test.ts @@ -70,4 +70,35 @@ describe("CLI to Socket communication", () => { expect(request.params.tool).toBe("navigate"); expect(request.params.args.url).toBe("https://example.com"); }); + + it("sends click command with element reference", async () => { + const receivedData = await new Promise((resolve, reject) => { + const timeout = setTimeout(() => reject(new Error("Test timeout")), 5000); + + server = net.createServer((socket) => { + let data = ""; + socket.on("data", (chunk) => { + data += chunk.toString(); + socket.write(`${JSON.stringify({ result: { success: true } })}\n`); + }); + socket.on("close", () => { + clearTimeout(timeout); + resolve(data); + }); + }); + + server.listen(SOCKET_PATH, () => { + const cli = spawn("node", [CLI_PATH, "click", "e5"]); + cli.on("error", (err) => { + clearTimeout(timeout); + reject(err); + }); + }); + }); + + const request = JSON.parse(receivedData.trim()); + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("click"); + expect(request.params.args.ref).toBe("e5"); + }); }); From 33a6914b353ffe7f749d4a63d343e11e849069a2 Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Sat, 17 Jan 2026 12:35:15 -0800 Subject: [PATCH 03/42] test: add integration test for CLI error when socket unavailable --- test/integration/cli-socket.test.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/test/integration/cli-socket.test.ts b/test/integration/cli-socket.test.ts index 9ccdb69..300dffa 100644 --- a/test/integration/cli-socket.test.ts +++ b/test/integration/cli-socket.test.ts @@ -101,4 +101,23 @@ describe("CLI to Socket communication", () => { expect(request.params.tool).toBe("click"); expect(request.params.args.ref).toBe("e5"); }); + + it("exits with error when socket is not available", async () => { + // Don't start a server - socket file doesn't exist + const result = await new Promise<{ code: number | null; stderr: string }>((resolve) => { + const cli = spawn("node", [CLI_PATH, "go", "https://example.com"]); + let stderr = ""; + + cli.stderr.on("data", (chunk) => { + stderr += chunk.toString(); + }); + + cli.on("close", (code) => { + resolve({ code, stderr }); + }); + }); + + expect(result.code).toBe(1); + expect(result.stderr).toContain("Socket not found"); + }); }); From c3d8c99df4a1acba10855bf1ef301453e4b8a1e9 Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Sat, 17 Jan 2026 12:35:32 -0800 Subject: [PATCH 04/42] test: add integration test for type command --- test/integration/cli-socket.test.ts | 31 +++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/test/integration/cli-socket.test.ts b/test/integration/cli-socket.test.ts index 300dffa..d9949f0 100644 --- a/test/integration/cli-socket.test.ts +++ b/test/integration/cli-socket.test.ts @@ -120,4 +120,35 @@ describe("CLI to Socket communication", () => { expect(result.code).toBe(1); expect(result.stderr).toContain("Socket not found"); }); + + it("sends type command with text argument", async () => { + const receivedData = await new Promise((resolve, reject) => { + const timeout = setTimeout(() => reject(new Error("Test timeout")), 5000); + + server = net.createServer((socket) => { + let data = ""; + socket.on("data", (chunk) => { + data += chunk.toString(); + socket.write(`${JSON.stringify({ result: { success: true } })}\n`); + }); + socket.on("close", () => { + clearTimeout(timeout); + resolve(data); + }); + }); + + server.listen(SOCKET_PATH, () => { + const cli = spawn("node", [CLI_PATH, "type", "hello world"]); + cli.on("error", (err) => { + clearTimeout(timeout); + reject(err); + }); + }); + }); + + const request = JSON.parse(receivedData.trim()); + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("type"); + expect(request.params.args.text).toBe("hello world"); + }); }); From b3beb0cbbe98235fdde4924410205f2bc9a4376a Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Sat, 17 Jan 2026 12:35:48 -0800 Subject: [PATCH 05/42] test: add integration test for snap->screenshot alias --- test/integration/cli-socket.test.ts | 33 +++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/test/integration/cli-socket.test.ts b/test/integration/cli-socket.test.ts index d9949f0..aca4dcc 100644 --- a/test/integration/cli-socket.test.ts +++ b/test/integration/cli-socket.test.ts @@ -151,4 +151,37 @@ describe("CLI to Socket communication", () => { expect(request.params.tool).toBe("type"); expect(request.params.args.text).toBe("hello world"); }); + + it("resolves snap alias to screenshot command", async () => { + const receivedData = await new Promise((resolve, reject) => { + const timeout = setTimeout(() => reject(new Error("Test timeout")), 5000); + + server = net.createServer((socket) => { + let data = ""; + socket.on("data", (chunk) => { + data += chunk.toString(); + // Return a screenshot-like response + socket.write( + `${JSON.stringify({ result: { base64: "abc123", width: 800, height: 600 } })}\n`, + ); + }); + socket.on("close", () => { + clearTimeout(timeout); + resolve(data); + }); + }); + + server.listen(SOCKET_PATH, () => { + const cli = spawn("node", [CLI_PATH, "snap"]); + cli.on("error", (err) => { + clearTimeout(timeout); + reject(err); + }); + }); + }); + + const request = JSON.parse(receivedData.trim()); + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("screenshot"); + }); }); From 32b6c9dc05935da3946f6dc65104215950606474 Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Sat, 17 Jan 2026 12:36:10 -0800 Subject: [PATCH 06/42] test: add integration test for --tab-id global option --- test/integration/cli-socket.test.ts | 31 +++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/test/integration/cli-socket.test.ts b/test/integration/cli-socket.test.ts index aca4dcc..60634d9 100644 --- a/test/integration/cli-socket.test.ts +++ b/test/integration/cli-socket.test.ts @@ -184,4 +184,35 @@ describe("CLI to Socket communication", () => { expect(request.type).toBe("tool_request"); expect(request.params.tool).toBe("screenshot"); }); + + it("includes tabId in request when --tab-id is provided", async () => { + const receivedData = await new Promise((resolve, reject) => { + const timeout = setTimeout(() => reject(new Error("Test timeout")), 5000); + + server = net.createServer((socket) => { + let data = ""; + socket.on("data", (chunk) => { + data += chunk.toString(); + socket.write(`${JSON.stringify({ result: { success: true } })}\n`); + }); + socket.on("close", () => { + clearTimeout(timeout); + resolve(data); + }); + }); + + server.listen(SOCKET_PATH, () => { + const cli = spawn("node", [CLI_PATH, "go", "https://example.com", "--tab-id", "12345"]); + cli.on("error", (err) => { + clearTimeout(timeout); + reject(err); + }); + }); + }); + + const request = JSON.parse(receivedData.trim()); + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("navigate"); + expect(request.tabId).toBe(12345); + }); }); From 46565cbd9b758a8f2a40136499d740ac7150a64f Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Sat, 17 Jan 2026 12:37:10 -0800 Subject: [PATCH 07/42] refactor: extract runCliAndCapture helper, add --window-id test --- test/integration/cli-socket.test.ts | 165 ++++++++++------------------ 1 file changed, 57 insertions(+), 108 deletions(-) diff --git a/test/integration/cli-socket.test.ts b/test/integration/cli-socket.test.ts index 60634d9..95824d5 100644 --- a/test/integration/cli-socket.test.ts +++ b/test/integration/cli-socket.test.ts @@ -35,36 +35,43 @@ describe("CLI to Socket communication", () => { } }); - it("sends navigate command as tool_request to socket", async () => { - // Create a mock socket server that captures the request - const receivedData = await new Promise((resolve, reject) => { + // Helper to run CLI and capture the request sent to socket + const runCliAndCapture = ( + args: string[], + response: object = { result: { success: true } }, + ): Promise => { + return new Promise((resolve, reject) => { const timeout = setTimeout(() => reject(new Error("Test timeout")), 5000); server = net.createServer((socket) => { let data = ""; socket.on("data", (chunk) => { data += chunk.toString(); - // Send a response so CLI doesn't hang - socket.write(`${JSON.stringify({ result: { success: true } })}\n`); + socket.write(`${JSON.stringify(response)}\n`); }); socket.on("close", () => { clearTimeout(timeout); - resolve(data); + resolve(JSON.parse(data.trim())); }); }); server.listen(SOCKET_PATH, () => { - // Run CLI command - const cli = spawn("node", [CLI_PATH, "go", "https://example.com"]); - + const cli = spawn("node", [CLI_PATH, ...args]); cli.on("error", (err) => { clearTimeout(timeout); reject(err); }); }); }); + }; + + it("sends navigate command as tool_request to socket", async () => { + const request = (await runCliAndCapture(["go", "https://example.com"])) as { + type: string; + method: string; + params: { tool: string; args: { url: string } }; + }; - const request = JSON.parse(receivedData.trim()); expect(request.type).toBe("tool_request"); expect(request.method).toBe("execute_tool"); expect(request.params.tool).toBe("navigate"); @@ -72,31 +79,11 @@ describe("CLI to Socket communication", () => { }); it("sends click command with element reference", async () => { - const receivedData = await new Promise((resolve, reject) => { - const timeout = setTimeout(() => reject(new Error("Test timeout")), 5000); - - server = net.createServer((socket) => { - let data = ""; - socket.on("data", (chunk) => { - data += chunk.toString(); - socket.write(`${JSON.stringify({ result: { success: true } })}\n`); - }); - socket.on("close", () => { - clearTimeout(timeout); - resolve(data); - }); - }); - - server.listen(SOCKET_PATH, () => { - const cli = spawn("node", [CLI_PATH, "click", "e5"]); - cli.on("error", (err) => { - clearTimeout(timeout); - reject(err); - }); - }); - }); + const request = (await runCliAndCapture(["click", "e5"])) as { + type: string; + params: { tool: string; args: { ref: string } }; + }; - const request = JSON.parse(receivedData.trim()); expect(request.type).toBe("tool_request"); expect(request.params.tool).toBe("click"); expect(request.params.args.ref).toBe("e5"); @@ -122,97 +109,59 @@ describe("CLI to Socket communication", () => { }); it("sends type command with text argument", async () => { - const receivedData = await new Promise((resolve, reject) => { - const timeout = setTimeout(() => reject(new Error("Test timeout")), 5000); - - server = net.createServer((socket) => { - let data = ""; - socket.on("data", (chunk) => { - data += chunk.toString(); - socket.write(`${JSON.stringify({ result: { success: true } })}\n`); - }); - socket.on("close", () => { - clearTimeout(timeout); - resolve(data); - }); - }); - - server.listen(SOCKET_PATH, () => { - const cli = spawn("node", [CLI_PATH, "type", "hello world"]); - cli.on("error", (err) => { - clearTimeout(timeout); - reject(err); - }); - }); - }); + const request = (await runCliAndCapture(["type", "hello world"])) as { + type: string; + params: { tool: string; args: { text: string } }; + }; - const request = JSON.parse(receivedData.trim()); expect(request.type).toBe("tool_request"); expect(request.params.tool).toBe("type"); expect(request.params.args.text).toBe("hello world"); }); it("resolves snap alias to screenshot command", async () => { - const receivedData = await new Promise((resolve, reject) => { - const timeout = setTimeout(() => reject(new Error("Test timeout")), 5000); - - server = net.createServer((socket) => { - let data = ""; - socket.on("data", (chunk) => { - data += chunk.toString(); - // Return a screenshot-like response - socket.write( - `${JSON.stringify({ result: { base64: "abc123", width: 800, height: 600 } })}\n`, - ); - }); - socket.on("close", () => { - clearTimeout(timeout); - resolve(data); - }); - }); + const request = (await runCliAndCapture(["snap"], { + result: { base64: "abc123", width: 800, height: 600 }, + })) as { + type: string; + params: { tool: string }; + }; - server.listen(SOCKET_PATH, () => { - const cli = spawn("node", [CLI_PATH, "snap"]); - cli.on("error", (err) => { - clearTimeout(timeout); - reject(err); - }); - }); - }); - - const request = JSON.parse(receivedData.trim()); expect(request.type).toBe("tool_request"); expect(request.params.tool).toBe("screenshot"); }); it("includes tabId in request when --tab-id is provided", async () => { - const receivedData = await new Promise((resolve, reject) => { - const timeout = setTimeout(() => reject(new Error("Test timeout")), 5000); + const request = (await runCliAndCapture([ + "go", + "https://example.com", + "--tab-id", + "12345", + ])) as { + type: string; + params: { tool: string }; + tabId: number; + }; - server = net.createServer((socket) => { - let data = ""; - socket.on("data", (chunk) => { - data += chunk.toString(); - socket.write(`${JSON.stringify({ result: { success: true } })}\n`); - }); - socket.on("close", () => { - clearTimeout(timeout); - resolve(data); - }); - }); + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("navigate"); + expect(request.tabId).toBe(12345); + }); - server.listen(SOCKET_PATH, () => { - const cli = spawn("node", [CLI_PATH, "go", "https://example.com", "--tab-id", "12345"]); - cli.on("error", (err) => { - clearTimeout(timeout); - reject(err); - }); - }); - }); + it("includes windowId in request when --window-id is provided", async () => { + const request = (await runCliAndCapture([ + "go", + "https://example.com", + "--window-id", + "67890", + ])) as { + type: string; + params: { tool: string }; + windowId: number; + }; - const request = JSON.parse(receivedData.trim()); expect(request.type).toBe("tool_request"); expect(request.params.tool).toBe("navigate"); - expect(request.tabId).toBe(12345); + expect(request.windowId).toBe(67890); }); }); From 21e75ff3827d14b081887acb281931e7ac118dcc Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Sat, 17 Jan 2026 12:37:26 -0800 Subject: [PATCH 08/42] test: add integration test for read->page.read alias --- test/integration/cli-socket.test.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/integration/cli-socket.test.ts b/test/integration/cli-socket.test.ts index 95824d5..3bc33ea 100644 --- a/test/integration/cli-socket.test.ts +++ b/test/integration/cli-socket.test.ts @@ -164,4 +164,14 @@ describe("CLI to Socket communication", () => { expect(request.params.tool).toBe("navigate"); expect(request.windowId).toBe(67890); }); + + it("resolves read alias to page.read command", async () => { + const request = (await runCliAndCapture(["read"])) as { + type: string; + params: { tool: string }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("page.read"); + }); }); From 0533326ecb7ce201f28a5e2e87c8e5ce2828e59d Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Sat, 17 Jan 2026 12:37:40 -0800 Subject: [PATCH 09/42] test: add integration test for click with x,y coordinates --- test/integration/cli-socket.test.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/integration/cli-socket.test.ts b/test/integration/cli-socket.test.ts index 3bc33ea..6154ef3 100644 --- a/test/integration/cli-socket.test.ts +++ b/test/integration/cli-socket.test.ts @@ -174,4 +174,16 @@ describe("CLI to Socket communication", () => { expect(request.type).toBe("tool_request"); expect(request.params.tool).toBe("page.read"); }); + + it("sends click command with x,y coordinates", async () => { + const request = (await runCliAndCapture(["click", "100", "200"])) as { + type: string; + params: { tool: string; args: { x: number; y: number } }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("click"); + expect(request.params.args.x).toBe(100); + expect(request.params.args.y).toBe(200); + }); }); From 3c8e4f5bd2a4ddae7ac0114187d6ac61179d8ce4 Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Sat, 17 Jan 2026 12:38:05 -0800 Subject: [PATCH 10/42] test: add integration test for namespaced tab.list command --- test/integration/cli-socket.test.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/integration/cli-socket.test.ts b/test/integration/cli-socket.test.ts index 6154ef3..f17f8b6 100644 --- a/test/integration/cli-socket.test.ts +++ b/test/integration/cli-socket.test.ts @@ -186,4 +186,14 @@ describe("CLI to Socket communication", () => { expect(request.params.args.x).toBe(100); expect(request.params.args.y).toBe(200); }); + + it("sends namespaced tab.list command", async () => { + const request = (await runCliAndCapture(["tab.list"])) as { + type: string; + params: { tool: string }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("tab.list"); + }); }); From 5482c9e3980255faeb15ec8dc03c5e18dcac0a35 Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Sat, 17 Jan 2026 12:38:20 -0800 Subject: [PATCH 11/42] test: add integration test for click with --selector option --- test/integration/cli-socket.test.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/integration/cli-socket.test.ts b/test/integration/cli-socket.test.ts index f17f8b6..23233a5 100644 --- a/test/integration/cli-socket.test.ts +++ b/test/integration/cli-socket.test.ts @@ -196,4 +196,15 @@ describe("CLI to Socket communication", () => { expect(request.type).toBe("tool_request"); expect(request.params.tool).toBe("tab.list"); }); + + it("sends click command with --selector option", async () => { + const request = (await runCliAndCapture(["click", "--selector", ".submit-btn"])) as { + type: string; + params: { tool: string; args: { selector: string } }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("click"); + expect(request.params.args.selector).toBe(".submit-btn"); + }); }); From a5dd4be8656f679de669639bfcfeef4186fb746d Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Sat, 17 Jan 2026 13:17:39 -0800 Subject: [PATCH 12/42] test: add integration test for js command with code --- test/integration/cli-socket.test.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/integration/cli-socket.test.ts b/test/integration/cli-socket.test.ts index 23233a5..b413431 100644 --- a/test/integration/cli-socket.test.ts +++ b/test/integration/cli-socket.test.ts @@ -207,4 +207,15 @@ describe("CLI to Socket communication", () => { expect(request.params.tool).toBe("click"); expect(request.params.args.selector).toBe(".submit-btn"); }); + + it("sends js command with code argument", async () => { + const request = (await runCliAndCapture(["js", "return document.title"])) as { + type: string; + params: { tool: string; args: { code: string } }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("js"); + expect(request.params.args.code).toBe("return document.title"); + }); }); From ab0f3d62c02238800fe8757ea7e0f5220bd89a6b Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Sat, 17 Jan 2026 13:17:58 -0800 Subject: [PATCH 13/42] test: add integration test for window.new with url and dimensions --- test/integration/cli-socket.test.ts | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/test/integration/cli-socket.test.ts b/test/integration/cli-socket.test.ts index b413431..1d1df6a 100644 --- a/test/integration/cli-socket.test.ts +++ b/test/integration/cli-socket.test.ts @@ -218,4 +218,24 @@ describe("CLI to Socket communication", () => { expect(request.params.tool).toBe("js"); expect(request.params.args.code).toBe("return document.title"); }); + + it("sends window.new command with url and options", async () => { + const request = (await runCliAndCapture([ + "window.new", + "https://example.com", + "--width", + "1280", + "--height", + "720", + ])) as { + type: string; + params: { tool: string; args: { url: string; width: number; height: number } }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("window.new"); + expect(request.params.args.url).toBe("https://example.com"); + expect(request.params.args.width).toBe(1280); + expect(request.params.args.height).toBe(720); + }); }); From 7032d901650e65b316a08d9c4bc3fac5446a77fc Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Sat, 17 Jan 2026 13:18:12 -0800 Subject: [PATCH 14/42] test: add integration test for --incognito boolean flag --- test/integration/cli-socket.test.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/integration/cli-socket.test.ts b/test/integration/cli-socket.test.ts index 1d1df6a..5f3a8fb 100644 --- a/test/integration/cli-socket.test.ts +++ b/test/integration/cli-socket.test.ts @@ -238,4 +238,15 @@ describe("CLI to Socket communication", () => { expect(request.params.args.width).toBe(1280); expect(request.params.args.height).toBe(720); }); + + it("sends window.new with --incognito boolean flag", async () => { + const request = (await runCliAndCapture(["window.new", "--incognito"])) as { + type: string; + params: { tool: string; args: { incognito: boolean } }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("window.new"); + expect(request.params.args.incognito).toBe(true); + }); }); From 4a2ca45a28dfc9a9df1e8cfec266ab44c07f396e Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Sat, 17 Jan 2026 13:18:29 -0800 Subject: [PATCH 15/42] test: add integration test for scroll command --- test/integration/cli-socket.test.ts | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/test/integration/cli-socket.test.ts b/test/integration/cli-socket.test.ts index 5f3a8fb..5dec848 100644 --- a/test/integration/cli-socket.test.ts +++ b/test/integration/cli-socket.test.ts @@ -249,4 +249,22 @@ describe("CLI to Socket communication", () => { expect(request.params.tool).toBe("window.new"); expect(request.params.args.incognito).toBe(true); }); + + it("sends scroll command with direction and amount", async () => { + const request = (await runCliAndCapture([ + "scroll", + "--direction", + "down", + "--amount", + "3", + ])) as { + type: string; + params: { tool: string; args: { direction: string; amount: number } }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("scroll"); + expect(request.params.args.direction).toBe("down"); + expect(request.params.args.amount).toBe(3); + }); }); From 75a796ae4e82da5a6f33a6d694437aa2421375da Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Sat, 17 Jan 2026 13:18:53 -0800 Subject: [PATCH 16/42] test: add integration test for wait.element command --- test/integration/cli-socket.test.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/integration/cli-socket.test.ts b/test/integration/cli-socket.test.ts index 5dec848..db6f654 100644 --- a/test/integration/cli-socket.test.ts +++ b/test/integration/cli-socket.test.ts @@ -267,4 +267,16 @@ describe("CLI to Socket communication", () => { expect(request.params.args.direction).toBe("down"); expect(request.params.args.amount).toBe(3); }); + + it("sends wait.element command with selector and timeout", async () => { + const request = (await runCliAndCapture(["wait.element", "#result", "--timeout", "5000"])) as { + type: string; + params: { tool: string; args: { selector: string; timeout: number } }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("wait.element"); + expect(request.params.args.selector).toBe("#result"); + expect(request.params.args.timeout).toBe(5000); + }); }); From 8ac89412510aeae963c9e5756118ea65ee8053af Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Sat, 17 Jan 2026 13:19:10 -0800 Subject: [PATCH 17/42] test: add integration test for type with --submit flag --- test/integration/cli-socket.test.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test/integration/cli-socket.test.ts b/test/integration/cli-socket.test.ts index db6f654..1b9f307 100644 --- a/test/integration/cli-socket.test.ts +++ b/test/integration/cli-socket.test.ts @@ -279,4 +279,16 @@ describe("CLI to Socket communication", () => { expect(request.params.args.selector).toBe("#result"); expect(request.params.args.timeout).toBe(5000); }); + + it("sends type command with --submit flag", async () => { + const request = (await runCliAndCapture(["type", "search query", "--submit"])) as { + type: string; + params: { tool: string; args: { text: string; submit: boolean } }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("type"); + expect(request.params.args.text).toBe("search query"); + expect(request.params.args.submit).toBe(true); + }); }); From 5c552e1e36c6fcd71ae2b75fa8f771948c3c6fb4 Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Sat, 17 Jan 2026 13:19:27 -0800 Subject: [PATCH 18/42] test: add integration test for net->network alias --- test/integration/cli-socket.test.ts | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test/integration/cli-socket.test.ts b/test/integration/cli-socket.test.ts index 1b9f307..689e000 100644 --- a/test/integration/cli-socket.test.ts +++ b/test/integration/cli-socket.test.ts @@ -291,4 +291,14 @@ describe("CLI to Socket communication", () => { expect(request.params.args.text).toBe("search query"); expect(request.params.args.submit).toBe(true); }); + + it("resolves net alias to network command", async () => { + const request = (await runCliAndCapture(["net"])) as { + type: string; + params: { tool: string }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("network"); + }); }); From ed5c104ae8af32768a37350e99cd1f10e7b59fb2 Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Sat, 17 Jan 2026 13:19:41 -0800 Subject: [PATCH 19/42] test: add integration test for tab.new with url --- test/integration/cli-socket.test.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/integration/cli-socket.test.ts b/test/integration/cli-socket.test.ts index 689e000..ff5203e 100644 --- a/test/integration/cli-socket.test.ts +++ b/test/integration/cli-socket.test.ts @@ -301,4 +301,15 @@ describe("CLI to Socket communication", () => { expect(request.type).toBe("tool_request"); expect(request.params.tool).toBe("network"); }); + + it("sends tab.new command with url", async () => { + const request = (await runCliAndCapture(["tab.new", "https://github.com"])) as { + type: string; + params: { tool: string; args: { url: string } }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("tab.new"); + expect(request.params.args.url).toBe("https://github.com"); + }); }); From 8ecaeee9e9e3527e50af298309663098ec277ec2 Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Sat, 17 Jan 2026 13:19:58 -0800 Subject: [PATCH 20/42] test: add integration test for error response handling --- test/integration/cli-socket.test.ts | 32 +++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/test/integration/cli-socket.test.ts b/test/integration/cli-socket.test.ts index ff5203e..fd2c0df 100644 --- a/test/integration/cli-socket.test.ts +++ b/test/integration/cli-socket.test.ts @@ -312,4 +312,36 @@ describe("CLI to Socket communication", () => { expect(request.params.tool).toBe("tab.new"); expect(request.params.args.url).toBe("https://github.com"); }); + + it("outputs error message when server returns error", async () => { + const result = await new Promise<{ code: number | null; stderr: string }>((resolve) => { + const timeout = setTimeout(() => resolve({ code: 1, stderr: "timeout" }), 5000); + + server = net.createServer((socket) => { + socket.on("data", () => { + // Return an error response + socket.write( + `${JSON.stringify({ error: { content: [{ text: "Element not found" }] } })}\n`, + ); + }); + }); + + server.listen(SOCKET_PATH, () => { + const cli = spawn("node", [CLI_PATH, "click", "e99"]); + let stderr = ""; + + cli.stderr.on("data", (chunk) => { + stderr += chunk.toString(); + }); + + cli.on("close", (code) => { + clearTimeout(timeout); + resolve({ code, stderr }); + }); + }); + }); + + expect(result.code).toBe(1); + expect(result.stderr).toContain("Element not found"); + }); }); From e0fe6e5281fe5c025e2dd29d90ec01ae98640658 Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Sat, 17 Jan 2026 13:22:12 -0800 Subject: [PATCH 21/42] test: add integration test for key command --- test/integration/cli-socket.test.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/integration/cli-socket.test.ts b/test/integration/cli-socket.test.ts index fd2c0df..0774e10 100644 --- a/test/integration/cli-socket.test.ts +++ b/test/integration/cli-socket.test.ts @@ -344,4 +344,15 @@ describe("CLI to Socket communication", () => { expect(result.code).toBe(1); expect(result.stderr).toContain("Element not found"); }); + + it("sends key command with key name", async () => { + const request = (await runCliAndCapture(["key", "Enter"])) as { + type: string; + params: { tool: string; args: { key: string } }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("key"); + expect(request.params.args.key).toBe("Enter"); + }); }); From 77a8e407ff6692dd58fde6f1d1749fbdf6457ddd Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Sat, 17 Jan 2026 13:22:27 -0800 Subject: [PATCH 22/42] test: add integration tests for console, back, forward commands --- test/integration/cli-socket.test.ts | 30 +++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/test/integration/cli-socket.test.ts b/test/integration/cli-socket.test.ts index 0774e10..d9677ab 100644 --- a/test/integration/cli-socket.test.ts +++ b/test/integration/cli-socket.test.ts @@ -355,4 +355,34 @@ describe("CLI to Socket communication", () => { expect(request.params.tool).toBe("key"); expect(request.params.args.key).toBe("Enter"); }); + + it("sends console command", async () => { + const request = (await runCliAndCapture(["console"])) as { + type: string; + params: { tool: string }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("console"); + }); + + it("sends back command", async () => { + const request = (await runCliAndCapture(["back"])) as { + type: string; + params: { tool: string }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("back"); + }); + + it("sends forward command", async () => { + const request = (await runCliAndCapture(["forward"])) as { + type: string; + params: { tool: string }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("forward"); + }); }); From a9ebece7b002710d51d1815c6935cf9891cabf98 Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Sat, 17 Jan 2026 13:22:44 -0800 Subject: [PATCH 23/42] test: add integration tests for hover, drag, emulate.network --- test/integration/cli-socket.test.ts | 34 +++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/test/integration/cli-socket.test.ts b/test/integration/cli-socket.test.ts index d9677ab..f87abb2 100644 --- a/test/integration/cli-socket.test.ts +++ b/test/integration/cli-socket.test.ts @@ -385,4 +385,38 @@ describe("CLI to Socket communication", () => { expect(request.type).toBe("tool_request"); expect(request.params.tool).toBe("forward"); }); + + it("sends hover command with ref", async () => { + const request = (await runCliAndCapture(["hover", "--ref", "e3"])) as { + type: string; + params: { tool: string; args: { ref: string } }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("hover"); + expect(request.params.args.ref).toBe("e3"); + }); + + it("sends drag command with coordinates", async () => { + const request = (await runCliAndCapture(["drag", "--from", "100,100", "--to", "200,200"])) as { + type: string; + params: { tool: string; args: { from: string; to: string } }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("drag"); + expect(request.params.args.from).toBe("100,100"); + expect(request.params.args.to).toBe("200,200"); + }); + + it("sends emulate.network command with preset", async () => { + const request = (await runCliAndCapture(["emulate.network", "slow-3g"])) as { + type: string; + params: { tool: string; args: { preset: string } }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("emulate.network"); + expect(request.params.args.preset).toBe("slow-3g"); + }); }); From ccf576e0bdf7d91e09edc388581032e5fdf01fb6 Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Sat, 17 Jan 2026 13:23:00 -0800 Subject: [PATCH 24/42] test: add integration tests for frame.list, dialog.accept, cookie.list --- test/integration/cli-socket.test.ts | 31 +++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/test/integration/cli-socket.test.ts b/test/integration/cli-socket.test.ts index f87abb2..9938e4c 100644 --- a/test/integration/cli-socket.test.ts +++ b/test/integration/cli-socket.test.ts @@ -419,4 +419,35 @@ describe("CLI to Socket communication", () => { expect(request.params.tool).toBe("emulate.network"); expect(request.params.args.preset).toBe("slow-3g"); }); + + it("sends frame.list command", async () => { + const request = (await runCliAndCapture(["frame.list"])) as { + type: string; + params: { tool: string }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("frame.list"); + }); + + it("sends dialog.accept command with text", async () => { + const request = (await runCliAndCapture(["dialog.accept", "--text", "confirmed"])) as { + type: string; + params: { tool: string; args: { text: string } }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("dialog.accept"); + expect(request.params.args.text).toBe("confirmed"); + }); + + it("sends cookie.list command", async () => { + const request = (await runCliAndCapture(["cookie.list"])) as { + type: string; + params: { tool: string }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("cookie.list"); + }); }); From 233590be68e6ccc5159fa1e0b9f3164a89e27da9 Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Sat, 17 Jan 2026 13:25:40 -0800 Subject: [PATCH 25/42] test: add integration test for tab.switch --- test/integration/cli-socket.test.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/test/integration/cli-socket.test.ts b/test/integration/cli-socket.test.ts index 9938e4c..5c4a5bc 100644 --- a/test/integration/cli-socket.test.ts +++ b/test/integration/cli-socket.test.ts @@ -450,4 +450,15 @@ describe("CLI to Socket communication", () => { expect(request.type).toBe("tool_request"); expect(request.params.tool).toBe("cookie.list"); }); + + it("sends tab.switch command with id", async () => { + const request = (await runCliAndCapture(["tab.switch", "12345"])) as { + type: string; + params: { tool: string; args: { id: number } }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("tab.switch"); + expect(request.params.args.id).toBe(12345); + }); }); From 506b358bd30dabaf46715cc94cbb109648ae110a Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Sat, 17 Jan 2026 13:25:56 -0800 Subject: [PATCH 26/42] test: add integration tests for tab.close, tab.name, tab.reload --- test/integration/cli-socket.test.ts | 32 +++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/test/integration/cli-socket.test.ts b/test/integration/cli-socket.test.ts index 5c4a5bc..9e8fc20 100644 --- a/test/integration/cli-socket.test.ts +++ b/test/integration/cli-socket.test.ts @@ -461,4 +461,36 @@ describe("CLI to Socket communication", () => { expect(request.params.tool).toBe("tab.switch"); expect(request.params.args.id).toBe(12345); }); + + it("sends tab.close command with id", async () => { + const request = (await runCliAndCapture(["tab.close", "999"])) as { + type: string; + params: { tool: string; args: { id: number } }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("tab.close"); + expect(request.params.args.id).toBe(999); + }); + + it("sends tab.name command with name", async () => { + const request = (await runCliAndCapture(["tab.name", "main-tab"])) as { + type: string; + params: { tool: string; args: { name: string } }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("tab.name"); + expect(request.params.args.name).toBe("main-tab"); + }); + + it("sends tab.reload command", async () => { + const request = (await runCliAndCapture(["tab.reload"])) as { + type: string; + params: { tool: string }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("tab.reload"); + }); }); From 0ab5fd955244a237adaa74208b4a0de874ceeb71 Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Sat, 17 Jan 2026 13:26:11 -0800 Subject: [PATCH 27/42] test: add integration tests for window.list, window.focus, window.close --- test/integration/cli-socket.test.ts | 32 +++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/test/integration/cli-socket.test.ts b/test/integration/cli-socket.test.ts index 9e8fc20..8b6a504 100644 --- a/test/integration/cli-socket.test.ts +++ b/test/integration/cli-socket.test.ts @@ -493,4 +493,36 @@ describe("CLI to Socket communication", () => { expect(request.type).toBe("tool_request"); expect(request.params.tool).toBe("tab.reload"); }); + + it("sends window.list command", async () => { + const request = (await runCliAndCapture(["window.list"])) as { + type: string; + params: { tool: string }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("window.list"); + }); + + it("sends window.focus command with id", async () => { + const request = (await runCliAndCapture(["window.focus", "555"])) as { + type: string; + params: { tool: string; args: { id: number } }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("window.focus"); + expect(request.params.args.id).toBe(555); + }); + + it("sends window.close command with id", async () => { + const request = (await runCliAndCapture(["window.close", "777"])) as { + type: string; + params: { tool: string; args: { id: number } }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("window.close"); + expect(request.params.args.id).toBe(777); + }); }); From 48c5fcec336857ffd442f1e6ce446d0101d47f9f Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Sat, 17 Jan 2026 13:26:28 -0800 Subject: [PATCH 28/42] test: add integration tests for scroll.to, scroll.top, scroll.bottom --- test/integration/cli-socket.test.ts | 31 +++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/test/integration/cli-socket.test.ts b/test/integration/cli-socket.test.ts index 8b6a504..1435b11 100644 --- a/test/integration/cli-socket.test.ts +++ b/test/integration/cli-socket.test.ts @@ -525,4 +525,35 @@ describe("CLI to Socket communication", () => { expect(request.params.tool).toBe("window.close"); expect(request.params.args.id).toBe(777); }); + + it("sends scroll.to command with ref", async () => { + const request = (await runCliAndCapture(["scroll.to", "--ref", "e10"])) as { + type: string; + params: { tool: string; args: { ref: string } }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("scroll.to"); + expect(request.params.args.ref).toBe("e10"); + }); + + it("sends scroll.top command", async () => { + const request = (await runCliAndCapture(["scroll.top"])) as { + type: string; + params: { tool: string }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("scroll.top"); + }); + + it("sends scroll.bottom command", async () => { + const request = (await runCliAndCapture(["scroll.bottom"])) as { + type: string; + params: { tool: string }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("scroll.bottom"); + }); }); From f5c307b344262ad784dfc25413b1a4162324a01f Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Sat, 17 Jan 2026 13:26:44 -0800 Subject: [PATCH 29/42] test: add integration tests for wait.url, wait.network, wait.load --- test/integration/cli-socket.test.ts | 31 +++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/test/integration/cli-socket.test.ts b/test/integration/cli-socket.test.ts index 1435b11..401b62a 100644 --- a/test/integration/cli-socket.test.ts +++ b/test/integration/cli-socket.test.ts @@ -556,4 +556,35 @@ describe("CLI to Socket communication", () => { expect(request.type).toBe("tool_request"); expect(request.params.tool).toBe("scroll.bottom"); }); + + it("sends wait.url command with pattern", async () => { + const request = (await runCliAndCapture(["wait.url", "/dashboard"])) as { + type: string; + params: { tool: string; args: { pattern: string } }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("wait.url"); + expect(request.params.args.pattern).toBe("/dashboard"); + }); + + it("sends wait.network command", async () => { + const request = (await runCliAndCapture(["wait.network"])) as { + type: string; + params: { tool: string }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("wait.network"); + }); + + it("sends wait.load command", async () => { + const request = (await runCliAndCapture(["wait.load"])) as { + type: string; + params: { tool: string }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("wait.load"); + }); }); From 7e9fdcd893a32649747ad6a6f28941d98620901c Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Sat, 17 Jan 2026 13:27:01 -0800 Subject: [PATCH 30/42] test: add integration tests for locate.role, locate.text, page.text --- test/integration/cli-socket.test.ts | 33 +++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/test/integration/cli-socket.test.ts b/test/integration/cli-socket.test.ts index 401b62a..70cc016 100644 --- a/test/integration/cli-socket.test.ts +++ b/test/integration/cli-socket.test.ts @@ -587,4 +587,37 @@ describe("CLI to Socket communication", () => { expect(request.type).toBe("tool_request"); expect(request.params.tool).toBe("wait.load"); }); + + it("sends locate.role command with role and name", async () => { + const request = (await runCliAndCapture(["locate.role", "button", "--name", "Submit"])) as { + type: string; + params: { tool: string; args: { role: string; name: string } }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("locate.role"); + expect(request.params.args.role).toBe("button"); + expect(request.params.args.name).toBe("Submit"); + }); + + it("sends locate.text command with text", async () => { + const request = (await runCliAndCapture(["locate.text", "Sign In"])) as { + type: string; + params: { tool: string; args: { text: string } }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("locate.text"); + expect(request.params.args.text).toBe("Sign In"); + }); + + it("sends page.text command", async () => { + const request = (await runCliAndCapture(["page.text"])) as { + type: string; + params: { tool: string }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("page.text"); + }); }); From f0b7af3c61d62f0377f33680e79053c70a841eeb Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Sat, 17 Jan 2026 13:27:17 -0800 Subject: [PATCH 31/42] test: add integration tests for network.get, network.body, network.clear --- test/integration/cli-socket.test.ts | 32 +++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/test/integration/cli-socket.test.ts b/test/integration/cli-socket.test.ts index 70cc016..0c4fa4f 100644 --- a/test/integration/cli-socket.test.ts +++ b/test/integration/cli-socket.test.ts @@ -620,4 +620,36 @@ describe("CLI to Socket communication", () => { expect(request.type).toBe("tool_request"); expect(request.params.tool).toBe("page.text"); }); + + it("sends network.get command with id", async () => { + const request = (await runCliAndCapture(["network.get", "req-123"])) as { + type: string; + params: { tool: string; args: { id: string } }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("network.get"); + expect(request.params.args.id).toBe("req-123"); + }); + + it("sends network.body command with id", async () => { + const request = (await runCliAndCapture(["network.body", "req-456"])) as { + type: string; + params: { tool: string; args: { id: string } }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("network.body"); + expect(request.params.args.id).toBe("req-456"); + }); + + it("sends network.clear command", async () => { + const request = (await runCliAndCapture(["network.clear"])) as { + type: string; + params: { tool: string }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("network.clear"); + }); }); From 0e4ceb817d2a5faef029af97914afd426999c059 Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Sat, 17 Jan 2026 13:29:10 -0800 Subject: [PATCH 32/42] test: add integration tests for perf.metrics, health, zoom --- test/integration/cli-socket.test.ts | 32 +++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/test/integration/cli-socket.test.ts b/test/integration/cli-socket.test.ts index 0c4fa4f..0e8b222 100644 --- a/test/integration/cli-socket.test.ts +++ b/test/integration/cli-socket.test.ts @@ -652,4 +652,36 @@ describe("CLI to Socket communication", () => { expect(request.type).toBe("tool_request"); expect(request.params.tool).toBe("network.clear"); }); + + it("sends perf.metrics command", async () => { + const request = (await runCliAndCapture(["perf.metrics"])) as { + type: string; + params: { tool: string }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("perf.metrics"); + }); + + it("sends health command with url option", async () => { + const request = (await runCliAndCapture(["health", "--url", "https://example.com"])) as { + type: string; + params: { tool: string; args: { url: string } }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("health"); + expect(request.params.args.url).toBe("https://example.com"); + }); + + it("sends zoom command with --reset flag", async () => { + const request = (await runCliAndCapture(["zoom", "--reset"])) as { + type: string; + params: { tool: string; args: { reset: boolean } }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("zoom"); + expect(request.params.args.reset).toBe(true); + }); }); From fbc569983c63ca84a5125096cd4d0515d7aef062 Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Sat, 17 Jan 2026 13:29:28 -0800 Subject: [PATCH 33/42] test: add integration tests for form.fill, search, find alias --- test/integration/cli-socket.test.ts | 40 +++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/test/integration/cli-socket.test.ts b/test/integration/cli-socket.test.ts index 0e8b222..eebc0ef 100644 --- a/test/integration/cli-socket.test.ts +++ b/test/integration/cli-socket.test.ts @@ -684,4 +684,44 @@ describe("CLI to Socket communication", () => { expect(request.params.tool).toBe("zoom"); expect(request.params.args.reset).toBe(true); }); + + it("sends form.fill command with selector and value", async () => { + const request = (await runCliAndCapture([ + "form.fill", + "--selector", + "#email", + "--value", + "test@example.com", + ])) as { + type: string; + params: { tool: string; args: { selector: string; value: string } }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("form.fill"); + expect(request.params.args.selector).toBe("#email"); + expect(request.params.args.value).toBe("test@example.com"); + }); + + it("sends search command with term", async () => { + const request = (await runCliAndCapture(["search", "login button"])) as { + type: string; + params: { tool: string; args: { term: string } }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("search"); + expect(request.params.args.term).toBe("login button"); + }); + + it("resolves find alias to search command", async () => { + const request = (await runCliAndCapture(["find", "submit"])) as { + type: string; + params: { tool: string; args: { term: string } }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("search"); + expect(request.params.args.term).toBe("submit"); + }); }); From 2227cc4d36b8db19217614cb54a93ed7e6ca1a0a Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Sat, 17 Jan 2026 13:30:04 -0800 Subject: [PATCH 34/42] test: add integration tests for dialog.dismiss, dialog.info, page.state --- test/integration/cli-socket.test.ts | 30 +++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/test/integration/cli-socket.test.ts b/test/integration/cli-socket.test.ts index eebc0ef..322b581 100644 --- a/test/integration/cli-socket.test.ts +++ b/test/integration/cli-socket.test.ts @@ -724,4 +724,34 @@ describe("CLI to Socket communication", () => { expect(request.params.tool).toBe("search"); expect(request.params.args.term).toBe("submit"); }); + + it("sends dialog.dismiss command", async () => { + const request = (await runCliAndCapture(["dialog.dismiss"])) as { + type: string; + params: { tool: string }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("dialog.dismiss"); + }); + + it("sends dialog.info command", async () => { + const request = (await runCliAndCapture(["dialog.info"])) as { + type: string; + params: { tool: string }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("dialog.info"); + }); + + it("sends page.state command", async () => { + const request = (await runCliAndCapture(["page.state"])) as { + type: string; + params: { tool: string }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("page.state"); + }); }); From 2cd1c9704c7412d2e19c81d1ca93251b8f50e8a2 Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Sat, 17 Jan 2026 13:30:22 -0800 Subject: [PATCH 35/42] test: add integration tests for emulate.device, frame.switch, frame.main --- test/integration/cli-socket.test.ts | 32 +++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/test/integration/cli-socket.test.ts b/test/integration/cli-socket.test.ts index 322b581..47e0dfa 100644 --- a/test/integration/cli-socket.test.ts +++ b/test/integration/cli-socket.test.ts @@ -754,4 +754,36 @@ describe("CLI to Socket communication", () => { expect(request.type).toBe("tool_request"); expect(request.params.tool).toBe("page.state"); }); + + it("sends emulate.device command with device name", async () => { + const request = (await runCliAndCapture(["emulate.device", "iPhone 12"])) as { + type: string; + params: { tool: string; args: { device: string } }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("emulate.device"); + expect(request.params.args.device).toBe("iPhone 12"); + }); + + it("sends frame.switch command with frame id", async () => { + const request = (await runCliAndCapture(["frame.switch", "--id", "frame-1"])) as { + type: string; + params: { tool: string; args: { id: string } }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("frame.switch"); + expect(request.params.args.id).toBe("frame-1"); + }); + + it("sends frame.main command", async () => { + const request = (await runCliAndCapture(["frame.main"])) as { + type: string; + params: { tool: string }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("frame.main"); + }); }); From f0da7d28bdac1300093819e46ee1f43887a0791a Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Sat, 17 Jan 2026 13:30:39 -0800 Subject: [PATCH 36/42] test: add integration tests for history.search, history.list, bookmark.list --- test/integration/cli-socket.test.ts | 31 +++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/test/integration/cli-socket.test.ts b/test/integration/cli-socket.test.ts index 47e0dfa..9d0d8db 100644 --- a/test/integration/cli-socket.test.ts +++ b/test/integration/cli-socket.test.ts @@ -786,4 +786,35 @@ describe("CLI to Socket communication", () => { expect(request.type).toBe("tool_request"); expect(request.params.tool).toBe("frame.main"); }); + + it("sends history.search command with query", async () => { + const request = (await runCliAndCapture(["history.search", "github"])) as { + type: string; + params: { tool: string; args: { query: string } }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("history.search"); + expect(request.params.args.query).toBe("github"); + }); + + it("sends history.list command", async () => { + const request = (await runCliAndCapture(["history.list"])) as { + type: string; + params: { tool: string }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("history.list"); + }); + + it("sends bookmark.list command", async () => { + const request = (await runCliAndCapture(["bookmark.list"])) as { + type: string; + params: { tool: string }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("bookmark.list"); + }); }); From 88c3905287ac76f4fcd087bbd9a5edd3cc51c282 Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Sat, 17 Jan 2026 13:39:49 -0800 Subject: [PATCH 37/42] test: add integration tests for cookie.get, cookie.set, cookie.clear --- test/integration/cli-socket.test.ts | 39 +++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/test/integration/cli-socket.test.ts b/test/integration/cli-socket.test.ts index 9d0d8db..47d7393 100644 --- a/test/integration/cli-socket.test.ts +++ b/test/integration/cli-socket.test.ts @@ -817,4 +817,43 @@ describe("CLI to Socket communication", () => { expect(request.type).toBe("tool_request"); expect(request.params.tool).toBe("bookmark.list"); }); + + it("sends cookie.get command with name", async () => { + const request = (await runCliAndCapture(["cookie.get", "--name", "session"])) as { + type: string; + params: { tool: string; args: { name: string } }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("cookie.get"); + expect(request.params.args.name).toBe("session"); + }); + + it("sends cookie.set command with name and value", async () => { + const request = (await runCliAndCapture([ + "cookie.set", + "--name", + "token", + "--value", + "abc123", + ])) as { + type: string; + params: { tool: string; args: { name: string; value: string } }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("cookie.set"); + expect(request.params.args.name).toBe("token"); + expect(request.params.args.value).toBe("abc123"); + }); + + it("sends cookie.clear command", async () => { + const request = (await runCliAndCapture(["cookie.clear"])) as { + type: string; + params: { tool: string }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("cookie.clear"); + }); }); From 9926085034bfbeab9cc22693f880749a169947f8 Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Sat, 17 Jan 2026 13:40:08 -0800 Subject: [PATCH 38/42] test: add integration tests for emulate.cpu, emulate.viewport, emulate.touch --- test/integration/cli-socket.test.ts | 39 +++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/test/integration/cli-socket.test.ts b/test/integration/cli-socket.test.ts index 47d7393..348c202 100644 --- a/test/integration/cli-socket.test.ts +++ b/test/integration/cli-socket.test.ts @@ -856,4 +856,43 @@ describe("CLI to Socket communication", () => { expect(request.type).toBe("tool_request"); expect(request.params.tool).toBe("cookie.clear"); }); + + it("sends emulate.cpu command with rate", async () => { + const request = (await runCliAndCapture(["emulate.cpu", "4"])) as { + type: string; + params: { tool: string; args: { rate: number } }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("emulate.cpu"); + expect(request.params.args.rate).toBe(4); + }); + + it("sends emulate.viewport command with dimensions", async () => { + const request = (await runCliAndCapture([ + "emulate.viewport", + "--width", + "375", + "--height", + "812", + ])) as { + type: string; + params: { tool: string; args: { width: number; height: number } }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("emulate.viewport"); + expect(request.params.args.width).toBe(375); + expect(request.params.args.height).toBe(812); + }); + + it("sends emulate.touch command", async () => { + const request = (await runCliAndCapture(["emulate.touch"])) as { + type: string; + params: { tool: string }; + }; + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe("emulate.touch"); + }); }); From 029a189efc18cf09dd83935e20f20c1496a78ae5 Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Sat, 17 Jan 2026 13:42:40 -0800 Subject: [PATCH 39/42] refactor: convert integration tests to table-driven format with it.each --- test/integration/cli-socket.test.ts | 1212 +++++++++------------------ 1 file changed, 398 insertions(+), 814 deletions(-) diff --git a/test/integration/cli-socket.test.ts b/test/integration/cli-socket.test.ts index 348c202..c2c5ed6 100644 --- a/test/integration/cli-socket.test.ts +++ b/test/integration/cli-socket.test.ts @@ -12,7 +12,6 @@ describe("CLI to Socket communication", () => { let existingSocketBackedUp = false; beforeEach(() => { - // Back up existing socket if present (don't break running surf) if (fs.existsSync(SOCKET_PATH)) { fs.renameSync(SOCKET_PATH, `${SOCKET_PATH}.backup`); existingSocketBackedUp = true; @@ -24,22 +23,25 @@ describe("CLI to Socket communication", () => { server.close(); server = null; } - // Clean up test socket if (fs.existsSync(SOCKET_PATH)) { fs.unlinkSync(SOCKET_PATH); } - // Restore backed up socket if (existingSocketBackedUp && fs.existsSync(`${SOCKET_PATH}.backup`)) { fs.renameSync(`${SOCKET_PATH}.backup`, SOCKET_PATH); existingSocketBackedUp = false; } }); - // Helper to run CLI and capture the request sent to socket const runCliAndCapture = ( args: string[], response: object = { result: { success: true } }, - ): Promise => { + ): Promise<{ + type: string; + method?: string; + params: { tool: string; args: Record }; + tabId?: number; + windowId?: number; + }> => { return new Promise((resolve, reject) => { const timeout = setTimeout(() => reject(new Error("Test timeout")), 5000); @@ -65,269 +67,372 @@ describe("CLI to Socket communication", () => { }); }; - it("sends navigate command as tool_request to socket", async () => { - const request = (await runCliAndCapture(["go", "https://example.com"])) as { - type: string; - method: string; - params: { tool: string; args: { url: string } }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.method).toBe("execute_tool"); - expect(request.params.tool).toBe("navigate"); - expect(request.params.args.url).toBe("https://example.com"); - }); - - it("sends click command with element reference", async () => { - const request = (await runCliAndCapture(["click", "e5"])) as { - type: string; - params: { tool: string; args: { ref: string } }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("click"); - expect(request.params.args.ref).toBe("e5"); - }); - - it("exits with error when socket is not available", async () => { - // Don't start a server - socket file doesn't exist - const result = await new Promise<{ code: number | null; stderr: string }>((resolve) => { - const cli = spawn("node", [CLI_PATH, "go", "https://example.com"]); - let stderr = ""; - - cli.stderr.on("data", (chunk) => { - stderr += chunk.toString(); - }); - - cli.on("close", (code) => { - resolve({ code, stderr }); - }); - }); - - expect(result.code).toBe(1); - expect(result.stderr).toContain("Socket not found"); - }); - - it("sends type command with text argument", async () => { - const request = (await runCliAndCapture(["type", "hello world"])) as { - type: string; - params: { tool: string; args: { text: string } }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("type"); - expect(request.params.args.text).toBe("hello world"); - }); - - it("resolves snap alias to screenshot command", async () => { - const request = (await runCliAndCapture(["snap"], { - result: { base64: "abc123", width: 800, height: 600 }, - })) as { - type: string; - params: { tool: string }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("screenshot"); - }); - - it("includes tabId in request when --tab-id is provided", async () => { - const request = (await runCliAndCapture([ - "go", - "https://example.com", - "--tab-id", - "12345", - ])) as { - type: string; - params: { tool: string }; - tabId: number; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("navigate"); - expect(request.tabId).toBe(12345); - }); - - it("includes windowId in request when --window-id is provided", async () => { - const request = (await runCliAndCapture([ - "go", - "https://example.com", - "--window-id", - "67890", - ])) as { - type: string; - params: { tool: string }; - windowId: number; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("navigate"); - expect(request.windowId).toBe(67890); - }); - - it("resolves read alias to page.read command", async () => { - const request = (await runCliAndCapture(["read"])) as { - type: string; - params: { tool: string }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("page.read"); - }); - - it("sends click command with x,y coordinates", async () => { - const request = (await runCliAndCapture(["click", "100", "200"])) as { - type: string; - params: { tool: string; args: { x: number; y: number } }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("click"); - expect(request.params.args.x).toBe(100); - expect(request.params.args.y).toBe(200); - }); - - it("sends namespaced tab.list command", async () => { - const request = (await runCliAndCapture(["tab.list"])) as { - type: string; - params: { tool: string }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("tab.list"); - }); - - it("sends click command with --selector option", async () => { - const request = (await runCliAndCapture(["click", "--selector", ".submit-btn"])) as { - type: string; - params: { tool: string; args: { selector: string } }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("click"); - expect(request.params.args.selector).toBe(".submit-btn"); - }); - - it("sends js command with code argument", async () => { - const request = (await runCliAndCapture(["js", "return document.title"])) as { - type: string; - params: { tool: string; args: { code: string } }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("js"); - expect(request.params.args.code).toBe("return document.title"); - }); - - it("sends window.new command with url and options", async () => { - const request = (await runCliAndCapture([ - "window.new", - "https://example.com", - "--width", - "1280", - "--height", - "720", - ])) as { - type: string; - params: { tool: string; args: { url: string; width: number; height: number } }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("window.new"); - expect(request.params.args.url).toBe("https://example.com"); - expect(request.params.args.width).toBe(1280); - expect(request.params.args.height).toBe(720); - }); - - it("sends window.new with --incognito boolean flag", async () => { - const request = (await runCliAndCapture(["window.new", "--incognito"])) as { - type: string; - params: { tool: string; args: { incognito: boolean } }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("window.new"); - expect(request.params.args.incognito).toBe(true); - }); - - it("sends scroll command with direction and amount", async () => { - const request = (await runCliAndCapture([ - "scroll", - "--direction", - "down", - "--amount", - "3", - ])) as { - type: string; - params: { tool: string; args: { direction: string; amount: number } }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("scroll"); - expect(request.params.args.direction).toBe("down"); - expect(request.params.args.amount).toBe(3); - }); - - it("sends wait.element command with selector and timeout", async () => { - const request = (await runCliAndCapture(["wait.element", "#result", "--timeout", "5000"])) as { - type: string; - params: { tool: string; args: { selector: string; timeout: number } }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("wait.element"); - expect(request.params.args.selector).toBe("#result"); - expect(request.params.args.timeout).toBe(5000); - }); - - it("sends type command with --submit flag", async () => { - const request = (await runCliAndCapture(["type", "search query", "--submit"])) as { - type: string; - params: { tool: string; args: { text: string; submit: boolean } }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("type"); - expect(request.params.args.text).toBe("search query"); - expect(request.params.args.submit).toBe(true); - }); - - it("resolves net alias to network command", async () => { - const request = (await runCliAndCapture(["net"])) as { - type: string; - params: { tool: string }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("network"); - }); - - it("sends tab.new command with url", async () => { - const request = (await runCliAndCapture(["tab.new", "https://github.com"])) as { - type: string; - params: { tool: string; args: { url: string } }; - }; + // Table-driven tests for command parsing + const commandTests: Array<{ + name: string; + args: string[]; + expectedTool: string; + expectedArgs?: Record; + expectedGlobals?: { tabId?: number; windowId?: number }; + }> = [ + // Navigation + { + name: "go -> navigate", + args: ["go", "https://example.com"], + expectedTool: "navigate", + expectedArgs: { url: "https://example.com" }, + }, + { name: "back", args: ["back"], expectedTool: "back" }, + { name: "forward", args: ["forward"], expectedTool: "forward" }, + + // Aliases + { name: "snap -> screenshot", args: ["snap"], expectedTool: "screenshot" }, + { name: "read -> page.read", args: ["read"], expectedTool: "page.read" }, + { name: "net -> network", args: ["net"], expectedTool: "network" }, + { + name: "find -> search", + args: ["find", "submit"], + expectedTool: "search", + expectedArgs: { term: "submit" }, + }, + + // Click variants + { + name: "click with ref", + args: ["click", "e5"], + expectedTool: "click", + expectedArgs: { ref: "e5" }, + }, + { + name: "click with coordinates", + args: ["click", "100", "200"], + expectedTool: "click", + expectedArgs: { x: 100, y: 200 }, + }, + { + name: "click with selector", + args: ["click", "--selector", ".btn"], + expectedTool: "click", + expectedArgs: { selector: ".btn" }, + }, + + // Type + { + name: "type with text", + args: ["type", "hello world"], + expectedTool: "type", + expectedArgs: { text: "hello world" }, + }, + { + name: "type with --submit", + args: ["type", "query", "--submit"], + expectedTool: "type", + expectedArgs: { text: "query", submit: true }, + }, + + // Key + { name: "key", args: ["key", "Enter"], expectedTool: "key", expectedArgs: { key: "Enter" } }, + + // Mouse + { + name: "hover with ref", + args: ["hover", "--ref", "e3"], + expectedTool: "hover", + expectedArgs: { ref: "e3" }, + }, + { + name: "drag", + args: ["drag", "--from", "100,100", "--to", "200,200"], + expectedTool: "drag", + expectedArgs: { from: "100,100", to: "200,200" }, + }, + + // Scroll + { + name: "scroll with direction", + args: ["scroll", "--direction", "down", "--amount", "3"], + expectedTool: "scroll", + expectedArgs: { direction: "down", amount: 3 }, + }, + { name: "scroll.top", args: ["scroll.top"], expectedTool: "scroll.top" }, + { name: "scroll.bottom", args: ["scroll.bottom"], expectedTool: "scroll.bottom" }, + { + name: "scroll.to with ref", + args: ["scroll.to", "--ref", "e10"], + expectedTool: "scroll.to", + expectedArgs: { ref: "e10" }, + }, + { name: "scroll.info", args: ["scroll.info"], expectedTool: "scroll.info" }, + + // Page + { name: "page.text", args: ["page.text"], expectedTool: "page.text" }, + { name: "page.state", args: ["page.state"], expectedTool: "page.state" }, + + // Tab + { name: "tab.list", args: ["tab.list"], expectedTool: "tab.list" }, + { + name: "tab.new with url", + args: ["tab.new", "https://github.com"], + expectedTool: "tab.new", + expectedArgs: { url: "https://github.com" }, + }, + { + name: "tab.switch", + args: ["tab.switch", "12345"], + expectedTool: "tab.switch", + expectedArgs: { id: 12345 }, + }, + { + name: "tab.close", + args: ["tab.close", "999"], + expectedTool: "tab.close", + expectedArgs: { id: 999 }, + }, + { + name: "tab.name", + args: ["tab.name", "main-tab"], + expectedTool: "tab.name", + expectedArgs: { name: "main-tab" }, + }, + { name: "tab.reload", args: ["tab.reload"], expectedTool: "tab.reload" }, + + // Window + { + name: "window.new with url and size", + args: ["window.new", "https://example.com", "--width", "1280", "--height", "720"], + expectedTool: "window.new", + expectedArgs: { url: "https://example.com", width: 1280, height: 720 }, + }, + { + name: "window.new --incognito", + args: ["window.new", "--incognito"], + expectedTool: "window.new", + expectedArgs: { incognito: true }, + }, + { name: "window.list", args: ["window.list"], expectedTool: "window.list" }, + { + name: "window.focus", + args: ["window.focus", "555"], + expectedTool: "window.focus", + expectedArgs: { id: 555 }, + }, + { + name: "window.close", + args: ["window.close", "777"], + expectedTool: "window.close", + expectedArgs: { id: 777 }, + }, + { + name: "window.resize", + args: ["window.resize", "--id", "123", "--width", "1024", "--height", "768"], + expectedTool: "window.resize", + expectedArgs: { id: 123, width: 1024, height: 768 }, + }, + + // Wait + { + name: "wait.element", + args: ["wait.element", "#result", "--timeout", "5000"], + expectedTool: "wait.element", + expectedArgs: { selector: "#result", timeout: 5000 }, + }, + { + name: "wait.url", + args: ["wait.url", "/dashboard"], + expectedTool: "wait.url", + expectedArgs: { pattern: "/dashboard" }, + }, + { name: "wait.network", args: ["wait.network"], expectedTool: "wait.network" }, + { name: "wait.load", args: ["wait.load"], expectedTool: "wait.load" }, + { name: "wait.dom", args: ["wait.dom"], expectedTool: "wait.dom" }, + + // Locate + { + name: "locate.role", + args: ["locate.role", "button", "--name", "Submit"], + expectedTool: "locate.role", + expectedArgs: { role: "button", name: "Submit" }, + }, + { + name: "locate.text", + args: ["locate.text", "Sign In"], + expectedTool: "locate.text", + expectedArgs: { text: "Sign In" }, + }, + + // JavaScript + { + name: "js with code", + args: ["js", "return document.title"], + expectedTool: "js", + expectedArgs: { code: "return document.title" }, + }, + + // Network (network and console commands read from local files, tested separately) + { + name: "network.get", + args: ["network.get", "req-123"], + expectedTool: "network.get", + expectedArgs: { id: "req-123" }, + }, + { + name: "network.body", + args: ["network.body", "req-456"], + expectedTool: "network.body", + expectedArgs: { id: "req-456" }, + }, + { name: "network.clear", args: ["network.clear"], expectedTool: "network.clear" }, + + // Dialog + { + name: "dialog.accept with text", + args: ["dialog.accept", "--text", "confirmed"], + expectedTool: "dialog.accept", + expectedArgs: { text: "confirmed" }, + }, + { name: "dialog.dismiss", args: ["dialog.dismiss"], expectedTool: "dialog.dismiss" }, + { name: "dialog.info", args: ["dialog.info"], expectedTool: "dialog.info" }, + + // Cookie + { name: "cookie.list", args: ["cookie.list"], expectedTool: "cookie.list" }, + { + name: "cookie.get", + args: ["cookie.get", "--name", "session"], + expectedTool: "cookie.get", + expectedArgs: { name: "session" }, + }, + { + name: "cookie.set", + args: ["cookie.set", "--name", "token", "--value", "abc123"], + expectedTool: "cookie.set", + expectedArgs: { name: "token", value: "abc123" }, + }, + { name: "cookie.clear", args: ["cookie.clear"], expectedTool: "cookie.clear" }, + + // Frame + { name: "frame.list", args: ["frame.list"], expectedTool: "frame.list" }, + { + name: "frame.switch", + args: ["frame.switch", "--id", "frame-1"], + expectedTool: "frame.switch", + expectedArgs: { id: "frame-1" }, + }, + { name: "frame.main", args: ["frame.main"], expectedTool: "frame.main" }, + + // Emulation + { + name: "emulate.network", + args: ["emulate.network", "slow-3g"], + expectedTool: "emulate.network", + expectedArgs: { preset: "slow-3g" }, + }, + { + name: "emulate.device", + args: ["emulate.device", "iPhone 12"], + expectedTool: "emulate.device", + expectedArgs: { device: "iPhone 12" }, + }, + { + name: "emulate.cpu", + args: ["emulate.cpu", "4"], + expectedTool: "emulate.cpu", + expectedArgs: { rate: 4 }, + }, + { + name: "emulate.viewport", + args: ["emulate.viewport", "--width", "375", "--height", "812"], + expectedTool: "emulate.viewport", + expectedArgs: { width: 375, height: 812 }, + }, + { name: "emulate.touch", args: ["emulate.touch"], expectedTool: "emulate.touch" }, + + // History/Bookmark + { name: "history.list", args: ["history.list"], expectedTool: "history.list" }, + { + name: "history.search", + args: ["history.search", "github"], + expectedTool: "history.search", + expectedArgs: { query: "github" }, + }, + { name: "bookmark.list", args: ["bookmark.list"], expectedTool: "bookmark.list" }, + + // Form + { + name: "form.fill", + args: ["form.fill", "--selector", "#email", "--value", "test@example.com"], + expectedTool: "form.fill", + expectedArgs: { selector: "#email", value: "test@example.com" }, + }, + + // Search + { + name: "search", + args: ["search", "login button"], + expectedTool: "search", + expectedArgs: { term: "login button" }, + }, + + // Performance + { name: "perf.metrics", args: ["perf.metrics"], expectedTool: "perf.metrics" }, + + // Health + { + name: "health with url", + args: ["health", "--url", "https://example.com"], + expectedTool: "health", + expectedArgs: { url: "https://example.com" }, + }, + + // Zoom + { + name: "zoom --reset", + args: ["zoom", "--reset"], + expectedTool: "zoom", + expectedArgs: { reset: true }, + }, + + // Global options + { + name: "--tab-id option", + args: ["go", "https://example.com", "--tab-id", "12345"], + expectedTool: "navigate", + expectedArgs: { url: "https://example.com" }, + expectedGlobals: { tabId: 12345 }, + }, + { + name: "--window-id option", + args: ["go", "https://example.com", "--window-id", "67890"], + expectedTool: "navigate", + expectedArgs: { url: "https://example.com" }, + expectedGlobals: { windowId: 67890 }, + }, + ]; + + it.each(commandTests)("$name", async (test) => { + const request = await runCliAndCapture(test.args); + + expect(request.type).toBe("tool_request"); + expect(request.params.tool).toBe(test.expectedTool); + + if (test.expectedArgs) { + for (const [key, value] of Object.entries(test.expectedArgs)) { + expect(request.params.args[key]).toBe(value); + } + } - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("tab.new"); - expect(request.params.args.url).toBe("https://github.com"); + if (test.expectedGlobals?.tabId) { + expect(request.tabId).toBe(test.expectedGlobals.tabId); + } + if (test.expectedGlobals?.windowId) { + expect(request.windowId).toBe(test.expectedGlobals.windowId); + } }); - it("outputs error message when server returns error", async () => { - const result = await new Promise<{ code: number | null; stderr: string }>((resolve) => { - const timeout = setTimeout(() => resolve({ code: 1, stderr: "timeout" }), 5000); - - server = net.createServer((socket) => { - socket.on("data", () => { - // Return an error response - socket.write( - `${JSON.stringify({ error: { content: [{ text: "Element not found" }] } })}\n`, - ); - }); - }); - - server.listen(SOCKET_PATH, () => { - const cli = spawn("node", [CLI_PATH, "click", "e99"]); + // Special cases that need custom handling + describe("error handling", () => { + it("exits with error when socket is not available", async () => { + const result = await new Promise<{ code: number | null; stderr: string }>((resolve) => { + const cli = spawn("node", [CLI_PATH, "go", "https://example.com"]); let stderr = ""; cli.stderr.on("data", (chunk) => { @@ -335,564 +440,43 @@ describe("CLI to Socket communication", () => { }); cli.on("close", (code) => { - clearTimeout(timeout); resolve({ code, stderr }); }); }); - }); - - expect(result.code).toBe(1); - expect(result.stderr).toContain("Element not found"); - }); - - it("sends key command with key name", async () => { - const request = (await runCliAndCapture(["key", "Enter"])) as { - type: string; - params: { tool: string; args: { key: string } }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("key"); - expect(request.params.args.key).toBe("Enter"); - }); - - it("sends console command", async () => { - const request = (await runCliAndCapture(["console"])) as { - type: string; - params: { tool: string }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("console"); - }); - - it("sends back command", async () => { - const request = (await runCliAndCapture(["back"])) as { - type: string; - params: { tool: string }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("back"); - }); - - it("sends forward command", async () => { - const request = (await runCliAndCapture(["forward"])) as { - type: string; - params: { tool: string }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("forward"); - }); - - it("sends hover command with ref", async () => { - const request = (await runCliAndCapture(["hover", "--ref", "e3"])) as { - type: string; - params: { tool: string; args: { ref: string } }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("hover"); - expect(request.params.args.ref).toBe("e3"); - }); - - it("sends drag command with coordinates", async () => { - const request = (await runCliAndCapture(["drag", "--from", "100,100", "--to", "200,200"])) as { - type: string; - params: { tool: string; args: { from: string; to: string } }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("drag"); - expect(request.params.args.from).toBe("100,100"); - expect(request.params.args.to).toBe("200,200"); - }); - - it("sends emulate.network command with preset", async () => { - const request = (await runCliAndCapture(["emulate.network", "slow-3g"])) as { - type: string; - params: { tool: string; args: { preset: string } }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("emulate.network"); - expect(request.params.args.preset).toBe("slow-3g"); - }); - - it("sends frame.list command", async () => { - const request = (await runCliAndCapture(["frame.list"])) as { - type: string; - params: { tool: string }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("frame.list"); - }); - - it("sends dialog.accept command with text", async () => { - const request = (await runCliAndCapture(["dialog.accept", "--text", "confirmed"])) as { - type: string; - params: { tool: string; args: { text: string } }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("dialog.accept"); - expect(request.params.args.text).toBe("confirmed"); - }); - it("sends cookie.list command", async () => { - const request = (await runCliAndCapture(["cookie.list"])) as { - type: string; - params: { tool: string }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("cookie.list"); - }); - - it("sends tab.switch command with id", async () => { - const request = (await runCliAndCapture(["tab.switch", "12345"])) as { - type: string; - params: { tool: string; args: { id: number } }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("tab.switch"); - expect(request.params.args.id).toBe(12345); - }); - - it("sends tab.close command with id", async () => { - const request = (await runCliAndCapture(["tab.close", "999"])) as { - type: string; - params: { tool: string; args: { id: number } }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("tab.close"); - expect(request.params.args.id).toBe(999); - }); - - it("sends tab.name command with name", async () => { - const request = (await runCliAndCapture(["tab.name", "main-tab"])) as { - type: string; - params: { tool: string; args: { name: string } }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("tab.name"); - expect(request.params.args.name).toBe("main-tab"); - }); - - it("sends tab.reload command", async () => { - const request = (await runCliAndCapture(["tab.reload"])) as { - type: string; - params: { tool: string }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("tab.reload"); - }); - - it("sends window.list command", async () => { - const request = (await runCliAndCapture(["window.list"])) as { - type: string; - params: { tool: string }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("window.list"); - }); - - it("sends window.focus command with id", async () => { - const request = (await runCliAndCapture(["window.focus", "555"])) as { - type: string; - params: { tool: string; args: { id: number } }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("window.focus"); - expect(request.params.args.id).toBe(555); - }); - - it("sends window.close command with id", async () => { - const request = (await runCliAndCapture(["window.close", "777"])) as { - type: string; - params: { tool: string; args: { id: number } }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("window.close"); - expect(request.params.args.id).toBe(777); - }); - - it("sends scroll.to command with ref", async () => { - const request = (await runCliAndCapture(["scroll.to", "--ref", "e10"])) as { - type: string; - params: { tool: string; args: { ref: string } }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("scroll.to"); - expect(request.params.args.ref).toBe("e10"); - }); - - it("sends scroll.top command", async () => { - const request = (await runCliAndCapture(["scroll.top"])) as { - type: string; - params: { tool: string }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("scroll.top"); - }); - - it("sends scroll.bottom command", async () => { - const request = (await runCliAndCapture(["scroll.bottom"])) as { - type: string; - params: { tool: string }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("scroll.bottom"); - }); - - it("sends wait.url command with pattern", async () => { - const request = (await runCliAndCapture(["wait.url", "/dashboard"])) as { - type: string; - params: { tool: string; args: { pattern: string } }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("wait.url"); - expect(request.params.args.pattern).toBe("/dashboard"); - }); - - it("sends wait.network command", async () => { - const request = (await runCliAndCapture(["wait.network"])) as { - type: string; - params: { tool: string }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("wait.network"); - }); - - it("sends wait.load command", async () => { - const request = (await runCliAndCapture(["wait.load"])) as { - type: string; - params: { tool: string }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("wait.load"); - }); - - it("sends locate.role command with role and name", async () => { - const request = (await runCliAndCapture(["locate.role", "button", "--name", "Submit"])) as { - type: string; - params: { tool: string; args: { role: string; name: string } }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("locate.role"); - expect(request.params.args.role).toBe("button"); - expect(request.params.args.name).toBe("Submit"); - }); - - it("sends locate.text command with text", async () => { - const request = (await runCliAndCapture(["locate.text", "Sign In"])) as { - type: string; - params: { tool: string; args: { text: string } }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("locate.text"); - expect(request.params.args.text).toBe("Sign In"); - }); - - it("sends page.text command", async () => { - const request = (await runCliAndCapture(["page.text"])) as { - type: string; - params: { tool: string }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("page.text"); - }); - - it("sends network.get command with id", async () => { - const request = (await runCliAndCapture(["network.get", "req-123"])) as { - type: string; - params: { tool: string; args: { id: string } }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("network.get"); - expect(request.params.args.id).toBe("req-123"); - }); - - it("sends network.body command with id", async () => { - const request = (await runCliAndCapture(["network.body", "req-456"])) as { - type: string; - params: { tool: string; args: { id: string } }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("network.body"); - expect(request.params.args.id).toBe("req-456"); - }); - - it("sends network.clear command", async () => { - const request = (await runCliAndCapture(["network.clear"])) as { - type: string; - params: { tool: string }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("network.clear"); - }); - - it("sends perf.metrics command", async () => { - const request = (await runCliAndCapture(["perf.metrics"])) as { - type: string; - params: { tool: string }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("perf.metrics"); - }); - - it("sends health command with url option", async () => { - const request = (await runCliAndCapture(["health", "--url", "https://example.com"])) as { - type: string; - params: { tool: string; args: { url: string } }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("health"); - expect(request.params.args.url).toBe("https://example.com"); - }); - - it("sends zoom command with --reset flag", async () => { - const request = (await runCliAndCapture(["zoom", "--reset"])) as { - type: string; - params: { tool: string; args: { reset: boolean } }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("zoom"); - expect(request.params.args.reset).toBe(true); - }); - - it("sends form.fill command with selector and value", async () => { - const request = (await runCliAndCapture([ - "form.fill", - "--selector", - "#email", - "--value", - "test@example.com", - ])) as { - type: string; - params: { tool: string; args: { selector: string; value: string } }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("form.fill"); - expect(request.params.args.selector).toBe("#email"); - expect(request.params.args.value).toBe("test@example.com"); - }); - - it("sends search command with term", async () => { - const request = (await runCliAndCapture(["search", "login button"])) as { - type: string; - params: { tool: string; args: { term: string } }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("search"); - expect(request.params.args.term).toBe("login button"); - }); - - it("resolves find alias to search command", async () => { - const request = (await runCliAndCapture(["find", "submit"])) as { - type: string; - params: { tool: string; args: { term: string } }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("search"); - expect(request.params.args.term).toBe("submit"); - }); - - it("sends dialog.dismiss command", async () => { - const request = (await runCliAndCapture(["dialog.dismiss"])) as { - type: string; - params: { tool: string }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("dialog.dismiss"); - }); - - it("sends dialog.info command", async () => { - const request = (await runCliAndCapture(["dialog.info"])) as { - type: string; - params: { tool: string }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("dialog.info"); - }); - - it("sends page.state command", async () => { - const request = (await runCliAndCapture(["page.state"])) as { - type: string; - params: { tool: string }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("page.state"); - }); - - it("sends emulate.device command with device name", async () => { - const request = (await runCliAndCapture(["emulate.device", "iPhone 12"])) as { - type: string; - params: { tool: string; args: { device: string } }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("emulate.device"); - expect(request.params.args.device).toBe("iPhone 12"); - }); - - it("sends frame.switch command with frame id", async () => { - const request = (await runCliAndCapture(["frame.switch", "--id", "frame-1"])) as { - type: string; - params: { tool: string; args: { id: string } }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("frame.switch"); - expect(request.params.args.id).toBe("frame-1"); - }); - - it("sends frame.main command", async () => { - const request = (await runCliAndCapture(["frame.main"])) as { - type: string; - params: { tool: string }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("frame.main"); - }); - - it("sends history.search command with query", async () => { - const request = (await runCliAndCapture(["history.search", "github"])) as { - type: string; - params: { tool: string; args: { query: string } }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("history.search"); - expect(request.params.args.query).toBe("github"); - }); - - it("sends history.list command", async () => { - const request = (await runCliAndCapture(["history.list"])) as { - type: string; - params: { tool: string }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("history.list"); - }); - - it("sends bookmark.list command", async () => { - const request = (await runCliAndCapture(["bookmark.list"])) as { - type: string; - params: { tool: string }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("bookmark.list"); - }); - - it("sends cookie.get command with name", async () => { - const request = (await runCliAndCapture(["cookie.get", "--name", "session"])) as { - type: string; - params: { tool: string; args: { name: string } }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("cookie.get"); - expect(request.params.args.name).toBe("session"); - }); - - it("sends cookie.set command with name and value", async () => { - const request = (await runCliAndCapture([ - "cookie.set", - "--name", - "token", - "--value", - "abc123", - ])) as { - type: string; - params: { tool: string; args: { name: string; value: string } }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("cookie.set"); - expect(request.params.args.name).toBe("token"); - expect(request.params.args.value).toBe("abc123"); - }); - - it("sends cookie.clear command", async () => { - const request = (await runCliAndCapture(["cookie.clear"])) as { - type: string; - params: { tool: string }; - }; - - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("cookie.clear"); - }); + expect(result.code).toBe(1); + expect(result.stderr).toContain("Socket not found"); + }); - it("sends emulate.cpu command with rate", async () => { - const request = (await runCliAndCapture(["emulate.cpu", "4"])) as { - type: string; - params: { tool: string; args: { rate: number } }; - }; + it("outputs error message when server returns error", async () => { + const result = await new Promise<{ code: number | null; stderr: string }>((resolve) => { + const timeout = setTimeout(() => resolve({ code: 1, stderr: "timeout" }), 5000); - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("emulate.cpu"); - expect(request.params.args.rate).toBe(4); - }); + server = net.createServer((socket) => { + socket.on("data", () => { + socket.write( + `${JSON.stringify({ error: { content: [{ text: "Element not found" }] } })}\n`, + ); + }); + }); - it("sends emulate.viewport command with dimensions", async () => { - const request = (await runCliAndCapture([ - "emulate.viewport", - "--width", - "375", - "--height", - "812", - ])) as { - type: string; - params: { tool: string; args: { width: number; height: number } }; - }; + server.listen(SOCKET_PATH, () => { + const cli = spawn("node", [CLI_PATH, "click", "e99"]); + let stderr = ""; - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("emulate.viewport"); - expect(request.params.args.width).toBe(375); - expect(request.params.args.height).toBe(812); - }); + cli.stderr.on("data", (chunk) => { + stderr += chunk.toString(); + }); - it("sends emulate.touch command", async () => { - const request = (await runCliAndCapture(["emulate.touch"])) as { - type: string; - params: { tool: string }; - }; + cli.on("close", (code) => { + clearTimeout(timeout); + resolve({ code, stderr }); + }); + }); + }); - expect(request.type).toBe("tool_request"); - expect(request.params.tool).toBe("emulate.touch"); + expect(result.code).toBe(1); + expect(result.stderr).toContain("Element not found"); + }); }); }); From 47dba8a4f4c81675ea9945fdad0530a6d9a98bbf Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Sat, 17 Jan 2026 13:44:14 -0800 Subject: [PATCH 40/42] test: add more commands to table-driven tests --- test/integration/cli-socket.test.ts | 49 +++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/test/integration/cli-socket.test.ts b/test/integration/cli-socket.test.ts index c2c5ed6..5272cea 100644 --- a/test/integration/cli-socket.test.ts +++ b/test/integration/cli-socket.test.ts @@ -406,6 +406,55 @@ describe("CLI to Socket communication", () => { expectedArgs: { url: "https://example.com" }, expectedGlobals: { windowId: 67890 }, }, + + // Additional commands + { + name: "locate.label", + args: ["locate.label", "Email"], + expectedTool: "locate.label", + expectedArgs: { label: "Email" }, + }, + { name: "tab.named", args: ["tab.named"], expectedTool: "tab.named" }, + { + name: "tab.unname", + args: ["tab.unname", "my-tab"], + expectedTool: "tab.unname", + expectedArgs: { name: "my-tab" }, + }, + { name: "perf.start", args: ["perf.start"], expectedTool: "perf.start" }, + { name: "perf.stop", args: ["perf.stop"], expectedTool: "perf.stop" }, + { name: "network.stats", args: ["network.stats"], expectedTool: "network.stats" }, + { name: "network.origins", args: ["network.origins"], expectedTool: "network.origins" }, + { + name: "network.curl", + args: ["network.curl", "req-789"], + expectedTool: "network.curl", + expectedArgs: { id: "req-789" }, + }, + { + name: "bookmark.add", + args: ["bookmark.add", "--title", "My Page"], + expectedTool: "bookmark.add", + expectedArgs: { title: "My Page" }, + }, + { + name: "bookmark.remove", + args: ["bookmark.remove", "--id", "abc"], + expectedTool: "bookmark.remove", + expectedArgs: { id: "abc" }, + }, + { + name: "emulate.geo with --clear", + args: ["emulate.geo", "--clear"], + expectedTool: "emulate.geo", + expectedArgs: { clear: true }, + }, + { + name: "frame.js", + args: ["frame.js", "return 1+1", "--frame", "iframe-1"], + expectedTool: "frame.js", + expectedArgs: { code: "return 1+1", frame: "iframe-1" }, + }, ]; it.each(commandTests)("$name", async (test) => { From 777f06b84d3c7714f80a1082df972d55006068b2 Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Sat, 17 Jan 2026 16:15:14 -0800 Subject: [PATCH 41/42] test: add integration tests for tab groups, wait, upload, resize, smoke, console, network - Tab group commands: tab.group, tab.ungroup, tab.groups - Wait base command with duration - Upload with ref and files - Resize with dimensions - Smoke with urls array and routes/fail-fast - Console base command with limit and clear options - Network base command with various filters (origin, method, status, format, verbose) - Updated assertion to use toEqual() for array comparisons --- test/integration/cli-socket.test.ts | 117 +++++++++++++++++++++++++++- 1 file changed, 116 insertions(+), 1 deletion(-) diff --git a/test/integration/cli-socket.test.ts b/test/integration/cli-socket.test.ts index 5272cea..0a24959 100644 --- a/test/integration/cli-socket.test.ts +++ b/test/integration/cli-socket.test.ts @@ -455,6 +455,117 @@ describe("CLI to Socket communication", () => { expectedTool: "frame.js", expectedArgs: { code: "return 1+1", frame: "iframe-1" }, }, + + // Tab groups + { + name: "tab.group with name and color", + args: ["tab.group", "--name", "Work", "--color", "blue"], + expectedTool: "tab.group", + expectedArgs: { name: "Work", color: "blue" }, + }, + { + name: "tab.group with tabs", + args: ["tab.group", "--name", "Research", "--tabs", "1,2,3"], + expectedTool: "tab.group", + expectedArgs: { name: "Research", tabs: "1,2,3" }, + }, + { + name: "tab.ungroup", + args: ["tab.ungroup", "--tabs", "4,5"], + expectedTool: "tab.ungroup", + expectedArgs: { tabs: "4,5" }, + }, + { name: "tab.groups", args: ["tab.groups"], expectedTool: "tab.groups" }, + + // Wait (base command) + { + name: "wait with duration", + args: ["wait", "2"], + expectedTool: "wait", + expectedArgs: { duration: 2 }, + }, + + // Upload + { + name: "upload with ref and files", + args: ["upload", "--ref", "e5", "--files", "/path/to/file.pdf"], + expectedTool: "upload", + expectedArgs: { ref: "e5", files: "/path/to/file.pdf" }, + }, + + // Resize (standalone command) + { + name: "resize with dimensions", + args: ["resize", "--width", "1280", "--height", "720"], + expectedTool: "resize", + expectedArgs: { width: 1280, height: 720 }, + }, + + // Smoke + { + name: "smoke with urls", + args: ["smoke", "--urls", "https://example.com", "https://test.com"], + expectedTool: "smoke", + expectedArgs: { urls: ["https://example.com", "https://test.com"] }, + }, + { + name: "smoke with routes and fail-fast", + args: ["smoke", "--routes", "auth", "--fail-fast"], + expectedTool: "smoke", + expectedArgs: { routes: "auth", "fail-fast": true }, + }, + + // Console (base command) + { + name: "console", + args: ["console"], + expectedTool: "console", + }, + // Note: --level is stripped out by CLI for stream handling, so we test --limit instead + { + name: "console with limit", + args: ["console", "--limit", "100"], + expectedTool: "console", + expectedArgs: { limit: 100 }, + }, + { + name: "console with limit and clear", + args: ["console", "--limit", "50", "--clear"], + expectedTool: "console", + expectedArgs: { limit: 50, clear: true }, + }, + + // Network (base command) + { + name: "network with origin filter", + args: ["network", "--origin", "api.github.com"], + expectedTool: "network", + expectedArgs: { origin: "api.github.com" }, + }, + { + name: "network with method and status", + args: ["network", "--method", "POST", "--status", "200"], + expectedTool: "network", + expectedArgs: { method: "POST", status: 200 }, + }, + { + name: "network with format", + args: ["network", "--format", "curl"], + expectedTool: "network", + expectedArgs: { format: "curl" }, + }, + { + name: "network verbose", + args: ["network", "-v"], + expectedTool: "network", + expectedArgs: { v: true }, + }, + { + name: "network with multiple filters", + args: ["network", "--type", "json", "--last", "10", "--exclude-static"], + expectedTool: "network", + expectedArgs: { type: "json", last: 10, "exclude-static": true }, + }, ]; it.each(commandTests)("$name", async (test) => { @@ -465,7 +576,11 @@ describe("CLI to Socket communication", () => { if (test.expectedArgs) { for (const [key, value] of Object.entries(test.expectedArgs)) { - expect(request.params.args[key]).toBe(value); + if (Array.isArray(value)) { + expect(request.params.args[key]).toEqual(value); + } else { + expect(request.params.args[key]).toBe(value); + } } } From 6841f381dde159f0448b7a28c620277f8eb3b708 Mon Sep 17 00:00:00 2001 From: Carlos Villela Date: Sat, 17 Jan 2026 16:20:20 -0800 Subject: [PATCH 42/42] fix: resolve typecheck errors in integration tests - Add @types/node dependency - Add 'node' to tsconfig types - Add explicit types for socket, chunk, code parameters - Use fileURLToPath for ESM-compatible __dirname --- package-lock.json | 22 ++++++++++++++++++++-- package.json | 1 + test/integration/cli-socket.test.ts | 20 ++++++++++++-------- tsconfig.json | 2 +- 4 files changed, 34 insertions(+), 11 deletions(-) diff --git a/package-lock.json b/package-lock.json index 91bb6c6..084fb31 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "surf-cli", - "version": "2.0.0", + "version": "2.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "surf-cli", - "version": "2.0.0", + "version": "2.1.0", "license": "MIT", "dependencies": { "@google/generative-ai": "^0.24.1", @@ -24,6 +24,7 @@ "devDependencies": { "@biomejs/biome": "^2.3.11", "@types/chrome": "^0.0.287", + "@types/node": "^25.0.9", "@vitest/coverage-v8": "^4.0.16", "@vitest/ui": "^4.0.16", "typescript": "^5.7.2", @@ -1160,6 +1161,16 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/node": { + "version": "25.0.9", + "resolved": "https://registry.npmjs.org/@types/node/-/node-25.0.9.tgz", + "integrity": "sha512-/rpCXHlCWeqClNBwUhDcusJxXYDjZTyE8v5oTO7WbL8eij2nKhUeU89/6xgjU7N4/Vh3He0BtyhJdQbDyhiXAw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "undici-types": "~7.16.0" + } + }, "node_modules/@vitest/coverage-v8": { "version": "4.0.16", "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-4.0.16.tgz", @@ -4204,6 +4215,13 @@ "node": ">=14.17" } }, + "node_modules/undici-types": { + "version": "7.16.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", + "devOptional": true, + "license": "MIT" + }, "node_modules/unpipe": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", diff --git a/package.json b/package.json index 59187a0..2728457 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ "devDependencies": { "@biomejs/biome": "^2.3.11", "@types/chrome": "^0.0.287", + "@types/node": "^25.0.9", "@vitest/coverage-v8": "^4.0.16", "@vitest/ui": "^4.0.16", "typescript": "^5.7.2", diff --git a/test/integration/cli-socket.test.ts b/test/integration/cli-socket.test.ts index 0a24959..20771f3 100644 --- a/test/integration/cli-socket.test.ts +++ b/test/integration/cli-socket.test.ts @@ -2,8 +2,12 @@ import { spawn } from "node:child_process"; import * as fs from "node:fs"; import * as net from "node:net"; import * as path from "node:path"; +import { fileURLToPath } from "node:url"; import { afterEach, beforeEach, describe, expect, it } from "vitest"; +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + const SOCKET_PATH = "/tmp/surf.sock"; const CLI_PATH = path.join(__dirname, "../../native/cli.cjs"); @@ -45,9 +49,9 @@ describe("CLI to Socket communication", () => { return new Promise((resolve, reject) => { const timeout = setTimeout(() => reject(new Error("Test timeout")), 5000); - server = net.createServer((socket) => { + server = net.createServer((socket: net.Socket) => { let data = ""; - socket.on("data", (chunk) => { + socket.on("data", (chunk: Buffer) => { data += chunk.toString(); socket.write(`${JSON.stringify(response)}\n`); }); @@ -59,7 +63,7 @@ describe("CLI to Socket communication", () => { server.listen(SOCKET_PATH, () => { const cli = spawn("node", [CLI_PATH, ...args]); - cli.on("error", (err) => { + cli.on("error", (err: Error) => { clearTimeout(timeout); reject(err); }); @@ -599,11 +603,11 @@ describe("CLI to Socket communication", () => { const cli = spawn("node", [CLI_PATH, "go", "https://example.com"]); let stderr = ""; - cli.stderr.on("data", (chunk) => { + cli.stderr.on("data", (chunk: Buffer) => { stderr += chunk.toString(); }); - cli.on("close", (code) => { + cli.on("close", (code: number | null) => { resolve({ code, stderr }); }); }); @@ -616,7 +620,7 @@ describe("CLI to Socket communication", () => { const result = await new Promise<{ code: number | null; stderr: string }>((resolve) => { const timeout = setTimeout(() => resolve({ code: 1, stderr: "timeout" }), 5000); - server = net.createServer((socket) => { + server = net.createServer((socket: net.Socket) => { socket.on("data", () => { socket.write( `${JSON.stringify({ error: { content: [{ text: "Element not found" }] } })}\n`, @@ -628,11 +632,11 @@ describe("CLI to Socket communication", () => { const cli = spawn("node", [CLI_PATH, "click", "e99"]); let stderr = ""; - cli.stderr.on("data", (chunk) => { + cli.stderr.on("data", (chunk: Buffer) => { stderr += chunk.toString(); }); - cli.on("close", (code) => { + cli.on("close", (code: number | null) => { clearTimeout(timeout); resolve({ code, stderr }); }); diff --git a/tsconfig.json b/tsconfig.json index 8664721..bffeeda 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,7 +13,7 @@ "sourceMap": true, "outDir": "./dist", "lib": ["ES2022", "DOM", "DOM.Iterable"], - "types": ["chrome", "vitest/globals"] + "types": ["chrome", "vitest/globals", "node"] }, "include": ["src/**/*", "test/**/*"], "exclude": ["node_modules", "dist"]