agent-browser 0.3.1 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/agent-browser-darwin-arm64 +0 -0
- package/bin/agent-browser-darwin-x64 +0 -0
- package/bin/agent-browser-linux-arm64 +0 -0
- package/bin/agent-browser-linux-x64 +0 -0
- package/package.json +6 -1
- package/.prettierrc +0 -7
- package/AGENTS.md +0 -26
- package/benchmark/benchmark.ts +0 -521
- package/benchmark/run.ts +0 -322
- package/cli/Cargo.lock +0 -114
- package/cli/Cargo.toml +0 -17
- package/cli/src/main.rs +0 -332
- package/docker/Dockerfile.build +0 -31
- package/docker/docker-compose.yml +0 -68
- package/src/actions.ts +0 -1670
- package/src/browser.test.ts +0 -157
- package/src/browser.ts +0 -686
- package/src/cli-light.ts +0 -457
- package/src/client.ts +0 -150
- package/src/daemon.ts +0 -187
- package/src/index.ts +0 -1185
- package/src/protocol.test.ts +0 -216
- package/src/protocol.ts +0 -852
- package/src/snapshot.ts +0 -380
- package/src/types.ts +0 -913
- package/tsconfig.json +0 -28
- package/vitest.config.ts +0 -9
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,9 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-browser",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.2",
|
|
4
4
|
"description": "Headless browser automation CLI for AI agents",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
|
+
"files": [
|
|
8
|
+
"dist",
|
|
9
|
+
"bin",
|
|
10
|
+
"scripts"
|
|
11
|
+
],
|
|
7
12
|
"bin": {
|
|
8
13
|
"agent-browser": "./bin/agent-browser"
|
|
9
14
|
},
|
package/.prettierrc
DELETED
package/AGENTS.md
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
# AGENTS.md
|
|
2
|
-
|
|
3
|
-
Instructions for AI coding agents working with this codebase.
|
|
4
|
-
|
|
5
|
-
<!-- opensrc:start -->
|
|
6
|
-
|
|
7
|
-
## Source Code Reference
|
|
8
|
-
|
|
9
|
-
Source code for dependencies is available in `opensrc/` for deeper understanding of implementation details.
|
|
10
|
-
|
|
11
|
-
See `opensrc/sources.json` for the list of available packages and their versions.
|
|
12
|
-
|
|
13
|
-
Use this source code when you need to understand how a package works internally, not just its types/interface.
|
|
14
|
-
|
|
15
|
-
### Fetching Additional Source Code
|
|
16
|
-
|
|
17
|
-
To fetch source code for a package or repository you need to understand, run:
|
|
18
|
-
|
|
19
|
-
```bash
|
|
20
|
-
npx opensrc <package> # npm package (e.g., npx opensrc zod)
|
|
21
|
-
npx opensrc pypi:<package> # Python package (e.g., npx opensrc pypi:requests)
|
|
22
|
-
npx opensrc crates:<package> # Rust crate (e.g., npx opensrc crates:serde)
|
|
23
|
-
npx opensrc <owner>/<repo> # GitHub repo (e.g., npx opensrc vercel/ai)
|
|
24
|
-
```
|
|
25
|
-
|
|
26
|
-
<!-- opensrc:end -->
|
package/benchmark/benchmark.ts
DELETED
|
@@ -1,521 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env npx tsx
|
|
2
|
-
/**
|
|
3
|
-
* Benchmark: agent-browser vs playwright-mcp
|
|
4
|
-
*
|
|
5
|
-
* Measures:
|
|
6
|
-
* - Speed: cold start, navigation, click, snapshot operations
|
|
7
|
-
* - Context usage: output/response size in bytes and estimated tokens
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { spawn, execSync, ChildProcess } from 'child_process';
|
|
11
|
-
import * as readline from 'readline';
|
|
12
|
-
|
|
13
|
-
// ============================================================================
|
|
14
|
-
// Configuration
|
|
15
|
-
// ============================================================================
|
|
16
|
-
|
|
17
|
-
const TEST_URL = 'https://example.com';
|
|
18
|
-
const ITERATIONS = 3;
|
|
19
|
-
|
|
20
|
-
interface BenchmarkResult {
|
|
21
|
-
operation: string;
|
|
22
|
-
tool: string;
|
|
23
|
-
timeMs: number;
|
|
24
|
-
outputBytes: number;
|
|
25
|
-
estimatedTokens: number;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
const results: BenchmarkResult[] = [];
|
|
29
|
-
|
|
30
|
-
// Estimate tokens (~4 chars per token for English text)
|
|
31
|
-
function estimateTokens(text: string): number {
|
|
32
|
-
return Math.ceil(text.length / 4);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function formatBytes(bytes: number): string {
|
|
36
|
-
if (bytes < 1024) return `${bytes} B`;
|
|
37
|
-
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
|
|
38
|
-
return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
// ============================================================================
|
|
42
|
-
// Agent-Browser Benchmarks
|
|
43
|
-
// ============================================================================
|
|
44
|
-
|
|
45
|
-
async function runAgentBrowser(args: string[]): Promise<{ timeMs: number; output: string }> {
|
|
46
|
-
const start = performance.now();
|
|
47
|
-
|
|
48
|
-
return new Promise((resolve, reject) => {
|
|
49
|
-
const proc = spawn('node', ['./dist/index.js', '--session', 'benchmark', '--json', ...args], {
|
|
50
|
-
cwd: process.cwd(),
|
|
51
|
-
env: { ...process.env },
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
let output = '';
|
|
55
|
-
let stderr = '';
|
|
56
|
-
|
|
57
|
-
proc.stdout.on('data', (data) => { output += data.toString(); });
|
|
58
|
-
proc.stderr.on('data', (data) => { stderr += data.toString(); });
|
|
59
|
-
|
|
60
|
-
proc.on('close', (code) => {
|
|
61
|
-
const timeMs = performance.now() - start;
|
|
62
|
-
if (code !== 0 && !output.includes('"success"')) {
|
|
63
|
-
reject(new Error(`agent-browser failed: ${stderr || output}`));
|
|
64
|
-
} else {
|
|
65
|
-
resolve({ timeMs, output: output.trim() });
|
|
66
|
-
}
|
|
67
|
-
});
|
|
68
|
-
|
|
69
|
-
proc.on('error', reject);
|
|
70
|
-
});
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
async function benchmarkAgentBrowser(): Promise<void> {
|
|
74
|
-
console.log('\n📦 Benchmarking agent-browser...\n');
|
|
75
|
-
|
|
76
|
-
// Clean up any existing session
|
|
77
|
-
try {
|
|
78
|
-
await runAgentBrowser(['close']);
|
|
79
|
-
} catch {
|
|
80
|
-
// Ignore - session might not exist
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Wait a bit for cleanup
|
|
84
|
-
await new Promise(r => setTimeout(r, 500));
|
|
85
|
-
|
|
86
|
-
// Cold start (includes daemon startup + browser launch + navigation)
|
|
87
|
-
console.log(' ⏱️ Cold start (navigate)...');
|
|
88
|
-
const coldStart = await runAgentBrowser(['open', TEST_URL]);
|
|
89
|
-
results.push({
|
|
90
|
-
operation: 'cold_start_navigate',
|
|
91
|
-
tool: 'agent-browser',
|
|
92
|
-
timeMs: coldStart.timeMs,
|
|
93
|
-
outputBytes: coldStart.output.length,
|
|
94
|
-
estimatedTokens: estimateTokens(coldStart.output),
|
|
95
|
-
});
|
|
96
|
-
console.log(` ${coldStart.timeMs.toFixed(0)}ms, ${formatBytes(coldStart.output.length)}`);
|
|
97
|
-
|
|
98
|
-
// Warm operations
|
|
99
|
-
for (let i = 0; i < ITERATIONS; i++) {
|
|
100
|
-
// Navigate (warm)
|
|
101
|
-
console.log(` ⏱️ Navigate (warm, iter ${i + 1})...`);
|
|
102
|
-
const nav = await runAgentBrowser(['open', TEST_URL]);
|
|
103
|
-
results.push({
|
|
104
|
-
operation: 'navigate_warm',
|
|
105
|
-
tool: 'agent-browser',
|
|
106
|
-
timeMs: nav.timeMs,
|
|
107
|
-
outputBytes: nav.output.length,
|
|
108
|
-
estimatedTokens: estimateTokens(nav.output),
|
|
109
|
-
});
|
|
110
|
-
console.log(` ${nav.timeMs.toFixed(0)}ms, ${formatBytes(nav.output.length)}`);
|
|
111
|
-
|
|
112
|
-
// Snapshot
|
|
113
|
-
console.log(` ⏱️ Snapshot (iter ${i + 1})...`);
|
|
114
|
-
const snapshot = await runAgentBrowser(['snapshot']);
|
|
115
|
-
results.push({
|
|
116
|
-
operation: 'snapshot',
|
|
117
|
-
tool: 'agent-browser',
|
|
118
|
-
timeMs: snapshot.timeMs,
|
|
119
|
-
outputBytes: snapshot.output.length,
|
|
120
|
-
estimatedTokens: estimateTokens(snapshot.output),
|
|
121
|
-
});
|
|
122
|
-
console.log(` ${snapshot.timeMs.toFixed(0)}ms, ${formatBytes(snapshot.output.length)}`);
|
|
123
|
-
|
|
124
|
-
// Get title
|
|
125
|
-
console.log(` ⏱️ Get title (iter ${i + 1})...`);
|
|
126
|
-
const title = await runAgentBrowser(['get', 'title']);
|
|
127
|
-
results.push({
|
|
128
|
-
operation: 'get_title',
|
|
129
|
-
tool: 'agent-browser',
|
|
130
|
-
timeMs: title.timeMs,
|
|
131
|
-
outputBytes: title.output.length,
|
|
132
|
-
estimatedTokens: estimateTokens(title.output),
|
|
133
|
-
});
|
|
134
|
-
console.log(` ${title.timeMs.toFixed(0)}ms, ${formatBytes(title.output.length)}`);
|
|
135
|
-
|
|
136
|
-
// Get URL
|
|
137
|
-
console.log(` ⏱️ Get URL (iter ${i + 1})...`);
|
|
138
|
-
const url = await runAgentBrowser(['get', 'url']);
|
|
139
|
-
results.push({
|
|
140
|
-
operation: 'get_url',
|
|
141
|
-
tool: 'agent-browser',
|
|
142
|
-
timeMs: url.timeMs,
|
|
143
|
-
outputBytes: url.output.length,
|
|
144
|
-
estimatedTokens: estimateTokens(url.output),
|
|
145
|
-
});
|
|
146
|
-
console.log(` ${url.timeMs.toFixed(0)}ms, ${formatBytes(url.output.length)}`);
|
|
147
|
-
|
|
148
|
-
// Click (on a link that exists on example.com)
|
|
149
|
-
console.log(` ⏱️ Click link (iter ${i + 1})...`);
|
|
150
|
-
const click = await runAgentBrowser(['click', 'a']);
|
|
151
|
-
results.push({
|
|
152
|
-
operation: 'click',
|
|
153
|
-
tool: 'agent-browser',
|
|
154
|
-
timeMs: click.timeMs,
|
|
155
|
-
outputBytes: click.output.length,
|
|
156
|
-
estimatedTokens: estimateTokens(click.output),
|
|
157
|
-
});
|
|
158
|
-
console.log(` ${click.timeMs.toFixed(0)}ms, ${formatBytes(click.output.length)}`);
|
|
159
|
-
|
|
160
|
-
// Navigate back for next iteration
|
|
161
|
-
await runAgentBrowser(['open', TEST_URL]);
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// Screenshot
|
|
165
|
-
console.log(' ⏱️ Screenshot...');
|
|
166
|
-
const screenshot = await runAgentBrowser(['screenshot']);
|
|
167
|
-
results.push({
|
|
168
|
-
operation: 'screenshot',
|
|
169
|
-
tool: 'agent-browser',
|
|
170
|
-
timeMs: screenshot.timeMs,
|
|
171
|
-
outputBytes: screenshot.output.length,
|
|
172
|
-
estimatedTokens: estimateTokens(screenshot.output),
|
|
173
|
-
});
|
|
174
|
-
console.log(` ${screenshot.timeMs.toFixed(0)}ms, ${formatBytes(screenshot.output.length)}`);
|
|
175
|
-
|
|
176
|
-
// Close
|
|
177
|
-
console.log(' ⏱️ Close...');
|
|
178
|
-
const close = await runAgentBrowser(['close']);
|
|
179
|
-
results.push({
|
|
180
|
-
operation: 'close',
|
|
181
|
-
tool: 'agent-browser',
|
|
182
|
-
timeMs: close.timeMs,
|
|
183
|
-
outputBytes: close.output.length,
|
|
184
|
-
estimatedTokens: estimateTokens(close.output),
|
|
185
|
-
});
|
|
186
|
-
console.log(` ${close.timeMs.toFixed(0)}ms`);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// ============================================================================
|
|
190
|
-
// Playwright-MCP Benchmarks
|
|
191
|
-
// ============================================================================
|
|
192
|
-
|
|
193
|
-
class MCPClient {
|
|
194
|
-
private proc: ChildProcess;
|
|
195
|
-
private rl: readline.Interface;
|
|
196
|
-
private responseBuffer: Map<number, { resolve: (v: any) => void; reject: (e: Error) => void }> = new Map();
|
|
197
|
-
private requestId = 0;
|
|
198
|
-
private ready = false;
|
|
199
|
-
|
|
200
|
-
constructor() {
|
|
201
|
-
this.proc = spawn('node', ['./opensrc/repos/github.com/microsoft/playwright-mcp/cli.js', '--headless'], {
|
|
202
|
-
cwd: process.cwd(),
|
|
203
|
-
stdio: ['pipe', 'pipe', 'pipe'],
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
this.rl = readline.createInterface({ input: this.proc.stdout! });
|
|
207
|
-
|
|
208
|
-
this.rl.on('line', (line) => {
|
|
209
|
-
try {
|
|
210
|
-
const msg = JSON.parse(line);
|
|
211
|
-
if (msg.id !== undefined && this.responseBuffer.has(msg.id)) {
|
|
212
|
-
const handler = this.responseBuffer.get(msg.id)!;
|
|
213
|
-
this.responseBuffer.delete(msg.id);
|
|
214
|
-
handler.resolve(msg);
|
|
215
|
-
}
|
|
216
|
-
} catch {
|
|
217
|
-
// Non-JSON output, ignore
|
|
218
|
-
}
|
|
219
|
-
});
|
|
220
|
-
|
|
221
|
-
this.proc.stderr?.on('data', (data) => {
|
|
222
|
-
// Debug output, ignore in benchmarks
|
|
223
|
-
});
|
|
224
|
-
}
|
|
225
|
-
|
|
226
|
-
async initialize(): Promise<{ timeMs: number; output: string }> {
|
|
227
|
-
const start = performance.now();
|
|
228
|
-
|
|
229
|
-
// Send initialize request
|
|
230
|
-
const initResult = await this.sendRequest('initialize', {
|
|
231
|
-
protocolVersion: '2024-11-05',
|
|
232
|
-
capabilities: {},
|
|
233
|
-
clientInfo: { name: 'benchmark', version: '1.0.0' },
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
// Send initialized notification
|
|
237
|
-
this.sendNotification('notifications/initialized', {});
|
|
238
|
-
|
|
239
|
-
const timeMs = performance.now() - start;
|
|
240
|
-
const output = JSON.stringify(initResult);
|
|
241
|
-
|
|
242
|
-
this.ready = true;
|
|
243
|
-
return { timeMs, output };
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
async callTool(name: string, args: Record<string, unknown>): Promise<{ timeMs: number; output: string }> {
|
|
247
|
-
const start = performance.now();
|
|
248
|
-
const result = await this.sendRequest('tools/call', { name, arguments: args });
|
|
249
|
-
const timeMs = performance.now() - start;
|
|
250
|
-
const output = JSON.stringify(result);
|
|
251
|
-
return { timeMs, output };
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
private sendRequest(method: string, params: Record<string, unknown>): Promise<any> {
|
|
255
|
-
const id = ++this.requestId;
|
|
256
|
-
const request = { jsonrpc: '2.0', id, method, params };
|
|
257
|
-
|
|
258
|
-
return new Promise((resolve, reject) => {
|
|
259
|
-
this.responseBuffer.set(id, { resolve, reject });
|
|
260
|
-
this.proc.stdin!.write(JSON.stringify(request) + '\n');
|
|
261
|
-
|
|
262
|
-
// Timeout after 30s
|
|
263
|
-
setTimeout(() => {
|
|
264
|
-
if (this.responseBuffer.has(id)) {
|
|
265
|
-
this.responseBuffer.delete(id);
|
|
266
|
-
reject(new Error(`Request timeout: ${method}`));
|
|
267
|
-
}
|
|
268
|
-
}, 30000);
|
|
269
|
-
});
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
private sendNotification(method: string, params: Record<string, unknown>): void {
|
|
273
|
-
const notification = { jsonrpc: '2.0', method, params };
|
|
274
|
-
this.proc.stdin!.write(JSON.stringify(notification) + '\n');
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
async close(): Promise<void> {
|
|
278
|
-
this.proc.kill();
|
|
279
|
-
this.rl.close();
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
async function benchmarkPlaywrightMCP(): Promise<void> {
|
|
284
|
-
console.log('\n📦 Benchmarking playwright-mcp...\n');
|
|
285
|
-
|
|
286
|
-
let client: MCPClient | null = null;
|
|
287
|
-
|
|
288
|
-
try {
|
|
289
|
-
// Cold start (includes server startup + initialization)
|
|
290
|
-
console.log(' ⏱️ Cold start (initialize + navigate)...');
|
|
291
|
-
const coldStartBegin = performance.now();
|
|
292
|
-
|
|
293
|
-
client = new MCPClient();
|
|
294
|
-
const init = await client.initialize();
|
|
295
|
-
|
|
296
|
-
// Navigate
|
|
297
|
-
const nav = await client.callTool('browser_navigate', { url: TEST_URL });
|
|
298
|
-
|
|
299
|
-
const coldStartTime = performance.now() - coldStartBegin;
|
|
300
|
-
const coldStartOutput = init.output + nav.output;
|
|
301
|
-
|
|
302
|
-
results.push({
|
|
303
|
-
operation: 'cold_start_navigate',
|
|
304
|
-
tool: 'playwright-mcp',
|
|
305
|
-
timeMs: coldStartTime,
|
|
306
|
-
outputBytes: coldStartOutput.length,
|
|
307
|
-
estimatedTokens: estimateTokens(coldStartOutput),
|
|
308
|
-
});
|
|
309
|
-
console.log(` ${coldStartTime.toFixed(0)}ms, ${formatBytes(coldStartOutput.length)}`);
|
|
310
|
-
|
|
311
|
-
// Warm operations
|
|
312
|
-
for (let i = 0; i < ITERATIONS; i++) {
|
|
313
|
-
// Navigate (warm)
|
|
314
|
-
console.log(` ⏱️ Navigate (warm, iter ${i + 1})...`);
|
|
315
|
-
const navWarm = await client.callTool('browser_navigate', { url: TEST_URL });
|
|
316
|
-
results.push({
|
|
317
|
-
operation: 'navigate_warm',
|
|
318
|
-
tool: 'playwright-mcp',
|
|
319
|
-
timeMs: navWarm.timeMs,
|
|
320
|
-
outputBytes: navWarm.output.length,
|
|
321
|
-
estimatedTokens: estimateTokens(navWarm.output),
|
|
322
|
-
});
|
|
323
|
-
console.log(` ${navWarm.timeMs.toFixed(0)}ms, ${formatBytes(navWarm.output.length)}`);
|
|
324
|
-
|
|
325
|
-
// Snapshot
|
|
326
|
-
console.log(` ⏱️ Snapshot (iter ${i + 1})...`);
|
|
327
|
-
const snapshot = await client.callTool('browser_snapshot', {});
|
|
328
|
-
results.push({
|
|
329
|
-
operation: 'snapshot',
|
|
330
|
-
tool: 'playwright-mcp',
|
|
331
|
-
timeMs: snapshot.timeMs,
|
|
332
|
-
outputBytes: snapshot.output.length,
|
|
333
|
-
estimatedTokens: estimateTokens(snapshot.output),
|
|
334
|
-
});
|
|
335
|
-
console.log(` ${snapshot.timeMs.toFixed(0)}ms, ${formatBytes(snapshot.output.length)}`);
|
|
336
|
-
|
|
337
|
-
// Note: playwright-mcp doesn't have separate get_title/get_url tools
|
|
338
|
-
// Title and URL are included in snapshot, so we'll skip those
|
|
339
|
-
|
|
340
|
-
// Click
|
|
341
|
-
console.log(` ⏱️ Click link (iter ${i + 1})...`);
|
|
342
|
-
// playwright-mcp uses ref from snapshot - we'll use a generic approach
|
|
343
|
-
const click = await client.callTool('browser_click', {
|
|
344
|
-
element: 'More information link',
|
|
345
|
-
ref: 'a' // This might not work exactly the same way
|
|
346
|
-
});
|
|
347
|
-
results.push({
|
|
348
|
-
operation: 'click',
|
|
349
|
-
tool: 'playwright-mcp',
|
|
350
|
-
timeMs: click.timeMs,
|
|
351
|
-
outputBytes: click.output.length,
|
|
352
|
-
estimatedTokens: estimateTokens(click.output),
|
|
353
|
-
});
|
|
354
|
-
console.log(` ${click.timeMs.toFixed(0)}ms, ${formatBytes(click.output.length)}`);
|
|
355
|
-
|
|
356
|
-
// Navigate back
|
|
357
|
-
await client.callTool('browser_navigate', { url: TEST_URL });
|
|
358
|
-
}
|
|
359
|
-
|
|
360
|
-
// Screenshot
|
|
361
|
-
console.log(' ⏱️ Screenshot...');
|
|
362
|
-
const screenshot = await client.callTool('browser_take_screenshot', {});
|
|
363
|
-
results.push({
|
|
364
|
-
operation: 'screenshot',
|
|
365
|
-
tool: 'playwright-mcp',
|
|
366
|
-
timeMs: screenshot.timeMs,
|
|
367
|
-
outputBytes: screenshot.output.length,
|
|
368
|
-
estimatedTokens: estimateTokens(screenshot.output),
|
|
369
|
-
});
|
|
370
|
-
console.log(` ${screenshot.timeMs.toFixed(0)}ms, ${formatBytes(screenshot.output.length)}`);
|
|
371
|
-
|
|
372
|
-
// Close
|
|
373
|
-
console.log(' ⏱️ Close...');
|
|
374
|
-
const closeStart = performance.now();
|
|
375
|
-
const closeResult = await client.callTool('browser_close', {});
|
|
376
|
-
results.push({
|
|
377
|
-
operation: 'close',
|
|
378
|
-
tool: 'playwright-mcp',
|
|
379
|
-
timeMs: closeResult.timeMs,
|
|
380
|
-
outputBytes: closeResult.output.length,
|
|
381
|
-
estimatedTokens: estimateTokens(closeResult.output),
|
|
382
|
-
});
|
|
383
|
-
console.log(` ${closeResult.timeMs.toFixed(0)}ms`);
|
|
384
|
-
|
|
385
|
-
} catch (error) {
|
|
386
|
-
console.error(' ❌ playwright-mcp benchmark failed:', error);
|
|
387
|
-
} finally {
|
|
388
|
-
if (client) {
|
|
389
|
-
await client.close();
|
|
390
|
-
}
|
|
391
|
-
}
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
// ============================================================================
|
|
395
|
-
// Results Summary
|
|
396
|
-
// ============================================================================
|
|
397
|
-
|
|
398
|
-
function printResults(): void {
|
|
399
|
-
console.log('\n' + '='.repeat(80));
|
|
400
|
-
console.log('📊 BENCHMARK RESULTS');
|
|
401
|
-
console.log('='.repeat(80));
|
|
402
|
-
|
|
403
|
-
// Group by operation
|
|
404
|
-
const operations = [...new Set(results.map(r => r.operation))];
|
|
405
|
-
|
|
406
|
-
console.log('\n📈 Speed Comparison (average across iterations):\n');
|
|
407
|
-
console.log('| Operation | agent-browser | playwright-mcp | Difference |');
|
|
408
|
-
console.log('|---------------------|---------------|----------------|------------|');
|
|
409
|
-
|
|
410
|
-
for (const op of operations) {
|
|
411
|
-
const agentResults = results.filter(r => r.operation === op && r.tool === 'agent-browser');
|
|
412
|
-
const mcpResults = results.filter(r => r.operation === op && r.tool === 'playwright-mcp');
|
|
413
|
-
|
|
414
|
-
const agentAvg = agentResults.length > 0
|
|
415
|
-
? agentResults.reduce((sum, r) => sum + r.timeMs, 0) / agentResults.length
|
|
416
|
-
: null;
|
|
417
|
-
const mcpAvg = mcpResults.length > 0
|
|
418
|
-
? mcpResults.reduce((sum, r) => sum + r.timeMs, 0) / mcpResults.length
|
|
419
|
-
: null;
|
|
420
|
-
|
|
421
|
-
const agentStr = agentAvg !== null ? `${agentAvg.toFixed(0)}ms`.padEnd(13) : 'N/A'.padEnd(13);
|
|
422
|
-
const mcpStr = mcpAvg !== null ? `${mcpAvg.toFixed(0)}ms`.padEnd(14) : 'N/A'.padEnd(14);
|
|
423
|
-
|
|
424
|
-
let diff = '';
|
|
425
|
-
if (agentAvg !== null && mcpAvg !== null) {
|
|
426
|
-
const ratio = agentAvg / mcpAvg;
|
|
427
|
-
if (ratio < 1) {
|
|
428
|
-
diff = `${((1 - ratio) * 100).toFixed(0)}% faster`;
|
|
429
|
-
} else if (ratio > 1) {
|
|
430
|
-
diff = `${((ratio - 1) * 100).toFixed(0)}% slower`;
|
|
431
|
-
} else {
|
|
432
|
-
diff = 'same';
|
|
433
|
-
}
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
console.log(`| ${op.padEnd(19)} | ${agentStr} | ${mcpStr} | ${diff.padEnd(10)} |`);
|
|
437
|
-
}
|
|
438
|
-
|
|
439
|
-
console.log('\n📦 Context Usage (output size for AI consumption):\n');
|
|
440
|
-
console.log('| Operation | agent-browser | playwright-mcp |');
|
|
441
|
-
console.log('|---------------------|--------------------|--------------------|');
|
|
442
|
-
|
|
443
|
-
for (const op of operations) {
|
|
444
|
-
const agentResults = results.filter(r => r.operation === op && r.tool === 'agent-browser');
|
|
445
|
-
const mcpResults = results.filter(r => r.operation === op && r.tool === 'playwright-mcp');
|
|
446
|
-
|
|
447
|
-
const agentAvgBytes = agentResults.length > 0
|
|
448
|
-
? agentResults.reduce((sum, r) => sum + r.outputBytes, 0) / agentResults.length
|
|
449
|
-
: null;
|
|
450
|
-
const agentAvgTokens = agentResults.length > 0
|
|
451
|
-
? agentResults.reduce((sum, r) => sum + r.estimatedTokens, 0) / agentResults.length
|
|
452
|
-
: null;
|
|
453
|
-
|
|
454
|
-
const mcpAvgBytes = mcpResults.length > 0
|
|
455
|
-
? mcpResults.reduce((sum, r) => sum + r.outputBytes, 0) / mcpResults.length
|
|
456
|
-
: null;
|
|
457
|
-
const mcpAvgTokens = mcpResults.length > 0
|
|
458
|
-
? mcpResults.reduce((sum, r) => sum + r.estimatedTokens, 0) / mcpResults.length
|
|
459
|
-
: null;
|
|
460
|
-
|
|
461
|
-
const agentStr = agentAvgBytes !== null
|
|
462
|
-
? `${formatBytes(agentAvgBytes)} (~${Math.round(agentAvgTokens!)} tok)`.padEnd(18)
|
|
463
|
-
: 'N/A'.padEnd(18);
|
|
464
|
-
const mcpStr = mcpAvgBytes !== null
|
|
465
|
-
? `${formatBytes(mcpAvgBytes)} (~${Math.round(mcpAvgTokens!)} tok)`.padEnd(18)
|
|
466
|
-
: 'N/A'.padEnd(18);
|
|
467
|
-
|
|
468
|
-
console.log(`| ${op.padEnd(19)} | ${agentStr} | ${mcpStr} |`);
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
// Total context usage
|
|
472
|
-
const agentTotal = results.filter(r => r.tool === 'agent-browser');
|
|
473
|
-
const mcpTotal = results.filter(r => r.tool === 'playwright-mcp');
|
|
474
|
-
|
|
475
|
-
const agentTotalBytes = agentTotal.reduce((sum, r) => sum + r.outputBytes, 0);
|
|
476
|
-
const agentTotalTokens = agentTotal.reduce((sum, r) => sum + r.estimatedTokens, 0);
|
|
477
|
-
const mcpTotalBytes = mcpTotal.reduce((sum, r) => sum + r.outputBytes, 0);
|
|
478
|
-
const mcpTotalTokens = mcpTotal.reduce((sum, r) => sum + r.estimatedTokens, 0);
|
|
479
|
-
|
|
480
|
-
console.log('\n📊 Total Context Usage (all operations combined):');
|
|
481
|
-
console.log(` agent-browser: ${formatBytes(agentTotalBytes)} (~${agentTotalTokens} tokens)`);
|
|
482
|
-
console.log(` playwright-mcp: ${formatBytes(mcpTotalBytes)} (~${mcpTotalTokens} tokens)`);
|
|
483
|
-
|
|
484
|
-
if (agentTotalBytes > 0 && mcpTotalBytes > 0) {
|
|
485
|
-
const ratio = agentTotalBytes / mcpTotalBytes;
|
|
486
|
-
if (ratio < 1) {
|
|
487
|
-
console.log(` → agent-browser uses ${((1 - ratio) * 100).toFixed(0)}% less context`);
|
|
488
|
-
} else {
|
|
489
|
-
console.log(` → playwright-mcp uses ${((1 - 1/ratio) * 100).toFixed(0)}% less context`);
|
|
490
|
-
}
|
|
491
|
-
}
|
|
492
|
-
|
|
493
|
-
console.log('\n' + '='.repeat(80));
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
// ============================================================================
|
|
497
|
-
// Main
|
|
498
|
-
// ============================================================================
|
|
499
|
-
|
|
500
|
-
async function main(): Promise<void> {
|
|
501
|
-
console.log('🚀 Browser Automation Benchmark');
|
|
502
|
-
console.log(` Testing against: ${TEST_URL}`);
|
|
503
|
-
console.log(` Iterations: ${ITERATIONS}`);
|
|
504
|
-
console.log('='.repeat(80));
|
|
505
|
-
|
|
506
|
-
try {
|
|
507
|
-
// Build first
|
|
508
|
-
console.log('\n🔨 Building agent-browser...');
|
|
509
|
-
execSync('pnpm build', { cwd: process.cwd(), stdio: 'inherit' });
|
|
510
|
-
|
|
511
|
-
await benchmarkAgentBrowser();
|
|
512
|
-
await benchmarkPlaywrightMCP();
|
|
513
|
-
|
|
514
|
-
printResults();
|
|
515
|
-
} catch (error) {
|
|
516
|
-
console.error('\n❌ Benchmark failed:', error);
|
|
517
|
-
process.exit(1);
|
|
518
|
-
}
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
main();
|