Skip to content

Commit 081754d

Browse files
authored
fix(browser-use): record sessions through V4 (#45)
1 parent 8b331ba commit 081754d

2 files changed

Lines changed: 89 additions & 5 deletions

File tree

src/providers/browser-use.test.ts

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
import assert from "node:assert/strict";
2+
import { afterEach, beforeEach, test } from "node:test";
3+
4+
import { BrowserUseProvider } from "./browser-use.js";
5+
6+
const originalFetch = globalThis.fetch;
7+
const originalApiKey = process.env.BROWSER_USE_API_KEY;
8+
9+
beforeEach(() => {
10+
process.env.BROWSER_USE_API_KEY = "test";
11+
});
12+
13+
afterEach(() => {
14+
globalThis.fetch = originalFetch;
15+
if (originalApiKey === undefined) delete process.env.BROWSER_USE_API_KEY;
16+
else process.env.BROWSER_USE_API_KEY = originalApiKey;
17+
});
18+
19+
test("requests recording when the benchmark asks for it", async () => {
20+
let requestUrl = "";
21+
let requestInit: RequestInit | undefined;
22+
globalThis.fetch = (async (input, init) => {
23+
requestUrl = String(input);
24+
requestInit = init;
25+
return new Response(JSON.stringify({
26+
id: "browser-1",
27+
cdpUrl: "https://cdp.browser-use.com/browser-1",
28+
}));
29+
}) as typeof fetch;
30+
31+
const session = await new BrowserUseProvider().create({ recording: true });
32+
33+
assert.equal(requestUrl, "https://api.browser-use.com/api/v4/browsers");
34+
assert.deepEqual(JSON.parse(String(requestInit?.body)), {
35+
proxyCountryCode: null,
36+
enableRecording: true,
37+
});
38+
assert.equal(session.cdpUrl, "wss://cdp.browser-use.com/browser-1");
39+
});
40+
41+
test("downloads the recording from the V4 browser record", async () => {
42+
const urls: string[] = [];
43+
globalThis.fetch = (async (input) => {
44+
const url = String(input);
45+
urls.push(url);
46+
if (url.endsWith("/browsers/browser-1")) {
47+
return new Response(JSON.stringify({
48+
status: "stopped",
49+
recordingUrl: "https://recordings.example/browser-1.mp4",
50+
}));
51+
}
52+
return new Response(new Uint8Array([1, 2, 3]));
53+
}) as typeof fetch;
54+
55+
const recording = await new BrowserUseProvider().downloadRecording("browser-1");
56+
57+
assert.deepEqual(urls, [
58+
"https://api.browser-use.com/api/v4/browsers/browser-1",
59+
"https://recordings.example/browser-1.mp4",
60+
]);
61+
assert.deepEqual([...recording.data], [1, 2, 3]);
62+
assert.equal(recording.format, "mp4");
63+
});
64+
65+
test("stops the V4 browser session", async () => {
66+
let requestUrl = "";
67+
let requestInit: RequestInit | undefined;
68+
globalThis.fetch = (async (input, init) => {
69+
requestUrl = String(input);
70+
requestInit = init;
71+
return new Response(JSON.stringify({ status: "stopped" }));
72+
}) as typeof fetch;
73+
74+
await new BrowserUseProvider().release("browser-1");
75+
76+
assert.equal(requestUrl, "https://api.browser-use.com/api/v4/browsers/browser-1");
77+
assert.equal(requestInit?.method, "PATCH");
78+
assert.deepEqual(JSON.parse(String(requestInit?.body)), { action: "stop" });
79+
});

src/providers/browser-use.ts

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import type { ProviderClient, ProviderSession, RecordingResult } from "../types.js";
22
import { requireEnv } from "../utils/env.js";
33

4+
const API_BASE = "https://api.browser-use.com/api/v4";
5+
46
export class BrowserUseProvider implements ProviderClient {
57
readonly name = "BROWSER_USE";
68

@@ -10,15 +12,18 @@ export class BrowserUseProvider implements ProviderClient {
1012
return Math.round((billedSeconds / 3600) * perHour * 1e8) / 1e8;
1113
}
1214

13-
async create(): Promise<ProviderSession> {
15+
async create(opts?: { recording?: boolean }): Promise<ProviderSession> {
1416
const apiKey = requireEnv("BROWSER_USE_API_KEY");
15-
const res = await fetch("https://api.browser-use.com/api/v3/browsers", {
17+
const res = await fetch(`${API_BASE}/browsers`, {
1618
method: "POST",
1719
headers: {
1820
"Content-Type": "application/json",
1921
"X-Browser-Use-API-Key": apiKey,
2022
},
21-
body: JSON.stringify({ proxyCountryCode: null }),
23+
body: JSON.stringify({
24+
proxyCountryCode: null,
25+
enableRecording: opts?.recording ?? false,
26+
}),
2227
signal: AbortSignal.timeout(90_000),
2328
});
2429
if (!res.ok) {
@@ -34,7 +39,7 @@ export class BrowserUseProvider implements ProviderClient {
3439

3540
async release(id: string): Promise<void> {
3641
const apiKey = requireEnv("BROWSER_USE_API_KEY");
37-
const res = await fetch(`https://api.browser-use.com/api/v3/browsers/${id}`, {
42+
const res = await fetch(`${API_BASE}/browsers/${id}`, {
3843
method: "PATCH",
3944
headers: {
4045
"Content-Type": "application/json",
@@ -55,7 +60,7 @@ export class BrowserUseProvider implements ProviderClient {
5560
const delayMs = 3000;
5661

5762
for (let i = 0; i < maxAttempts; i++) {
58-
const res = await fetch(`https://api.browser-use.com/api/v3/sessions/${sessionId}`, {
63+
const res = await fetch(`${API_BASE}/browsers/${sessionId}`, {
5964
headers: { "X-Browser-Use-API-Key": apiKey },
6065
signal: AbortSignal.timeout(30_000),
6166
});

0 commit comments

Comments
 (0)