Skip to content

Commit 7c9fd43

Browse files
committed
add BB session URL & debug URL accessors
1 parent 44bb4f5 commit 7c9fd43

File tree

3 files changed

+152
-13
lines changed

3 files changed

+152
-13
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@browserbasehq/stagehand": patch
3+
---
4+
5+
Add Browserbase session URL and debug URL accessors

packages/core/lib/v3/v3.ts

Lines changed: 31 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -137,9 +137,17 @@ export class V3 {
137137
private readonly domSettleTimeoutMs?: number;
138138
private _isClosing = false;
139139
public browserbaseSessionId?: string;
140+
private browserbaseSessionUrl = "";
141+
private browserbaseDebugUrl = "";
140142
public get browserbaseSessionID(): string | undefined {
141143
return this.browserbaseSessionId;
142144
}
145+
public get browserbaseSessionURL(): string {
146+
return this.browserbaseSessionUrl;
147+
}
148+
public get browserbaseDebugURL(): string {
149+
return this.browserbaseDebugUrl;
150+
}
143151
private _onCdpClosed = (why: string) => {
144152
// Single place to react to the transport closing
145153
this._immediateShutdown(`CDP transport closed: ${why}`).catch(() => {});
@@ -679,6 +687,7 @@ export class V3 {
679687
} as unknown as import("chrome-launcher").LaunchedChrome,
680688
ws: lbo.cdpUrl,
681689
};
690+
this.resetBrowserbaseSessionMetadata();
682691
// Post-connect settings (downloads and viewport) if provided
683692
await this._applyPostConnectLocalOptions(lbo);
684693
return;
@@ -767,7 +776,7 @@ export class V3 {
767776
createdTempProfile: createdTemp,
768777
preserveUserDataDir: !!lbo.preserveUserDataDir,
769778
};
770-
this.browserbaseSessionId = undefined;
779+
this.resetBrowserbaseSessionMetadata();
771780

772781
// Post-connect settings (downloads and viewport) if provided
773782
await this._applyPostConnectLocalOptions(lbo);
@@ -841,18 +850,21 @@ export class V3 {
841850

842851
await this._ensureBrowserbaseDownloadsEnabled();
843852

853+
const resumed = !!this.opts.browserbaseSessionID;
854+
let debugUrl: string | undefined;
855+
try {
856+
const dbg = (await bb.sessions.debug(sessionId)) as unknown as {
857+
debuggerUrl?: string;
858+
};
859+
debugUrl = dbg?.debuggerUrl;
860+
} catch {
861+
// Ignore debug fetch failures; continue with sessionUrl only
862+
}
863+
const sessionUrl = `https://www.browserbase.com/sessions/${sessionId}`;
864+
this.browserbaseSessionUrl = sessionUrl;
865+
this.browserbaseDebugUrl = debugUrl ?? "";
866+
844867
try {
845-
const resumed = !!this.opts.browserbaseSessionID;
846-
let debugUrl: string | undefined;
847-
try {
848-
const dbg = (await bb.sessions.debug(sessionId)) as unknown as {
849-
debuggerUrl?: string;
850-
};
851-
debugUrl = dbg?.debuggerUrl;
852-
} catch {
853-
// Ignore debug fetch failures; continue with sessionUrl only
854-
}
855-
const sessionUrl = `https://www.browserbase.com/sessions/${sessionId}`;
856868
this.logger({
857869
category: "init",
858870
message: resumed
@@ -926,6 +938,12 @@ export class V3 {
926938
}
927939
}
928940

941+
private resetBrowserbaseSessionMetadata(): void {
942+
this.browserbaseSessionId = undefined;
943+
this.browserbaseSessionUrl = "";
944+
this.browserbaseDebugUrl = "";
945+
}
946+
929947
/**
930948
* Run an "act" instruction through the ActHandler.
931949
*
@@ -1277,7 +1295,7 @@ export class V3 {
12771295
this.state = { kind: "UNINITIALIZED" };
12781296
this.ctx = null;
12791297
this._isClosing = false;
1280-
this.browserbaseSessionId = undefined;
1298+
this.resetBrowserbaseSessionMetadata();
12811299
try {
12821300
unbindInstanceLogger(this.instanceId);
12831301
} catch {
Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
import { describe, expect, it, vi, beforeEach, afterEach } from "vitest";
2+
import { V3 } from "../lib/v3/v3";
3+
4+
const MOCK_SESSION_ID = "session-123";
5+
const MOCK_SESSION_URL = `https://www.browserbase.com/sessions/${MOCK_SESSION_ID}`;
6+
const MOCK_DEBUG_URL = `https://debug.browserbase.com/${MOCK_SESSION_ID}`;
7+
8+
vi.mock("../lib/v3/understudy/context", () => {
9+
class MockConnection {
10+
onTransportClosed = vi.fn();
11+
offTransportClosed = vi.fn();
12+
send = vi.fn(async () => {});
13+
}
14+
15+
class MockV3Context {
16+
static async create(): Promise<MockV3Context> {
17+
return new MockV3Context();
18+
}
19+
20+
conn = new MockConnection();
21+
22+
pages(): never[] {
23+
return [];
24+
}
25+
26+
async close(): Promise<void> {
27+
// noop
28+
}
29+
}
30+
31+
return { V3Context: MockV3Context };
32+
});
33+
34+
vi.mock("../lib/v3/launch/browserbase", () => ({
35+
createBrowserbaseSession: vi.fn(async () => ({
36+
ws: "wss://mock-browserbase",
37+
sessionId: MOCK_SESSION_ID,
38+
bb: {
39+
sessions: {
40+
debug: vi.fn(async () => ({ debuggerUrl: MOCK_DEBUG_URL })),
41+
},
42+
},
43+
})),
44+
}));
45+
46+
vi.mock("../lib/v3/launch/local", () => ({
47+
launchLocalChrome: vi.fn(async () => ({
48+
ws: "ws://local-cdp",
49+
chrome: { kill: vi.fn(async () => {}) },
50+
})),
51+
}));
52+
53+
describe("browserbase accessors", () => {
54+
beforeEach(() => {
55+
process.env.BROWSERBASE_API_KEY = "fake-key";
56+
process.env.BROWSERBASE_PROJECT_ID = "fake-project";
57+
});
58+
59+
afterEach(() => {
60+
delete process.env.BROWSERBASE_API_KEY;
61+
delete process.env.BROWSERBASE_PROJECT_ID;
62+
vi.clearAllMocks();
63+
});
64+
65+
it("exposes Browserbase session and debug URLs after init", async () => {
66+
const v3 = new V3({
67+
env: "BROWSERBASE",
68+
disableAPI: true,
69+
verbose: 0,
70+
});
71+
72+
try {
73+
await v3.init();
74+
75+
expect(v3.browserbaseSessionURL).toBe(MOCK_SESSION_URL);
76+
expect(v3.browserbaseDebugURL).toBe(MOCK_DEBUG_URL);
77+
} finally {
78+
await v3.close().catch(() => {});
79+
}
80+
});
81+
82+
it("clears stored URLs after close", async () => {
83+
const v3 = new V3({
84+
env: "BROWSERBASE",
85+
disableAPI: true,
86+
verbose: 0,
87+
});
88+
89+
await v3.init();
90+
await v3.close();
91+
92+
expect(v3.browserbaseSessionURL).toBe("");
93+
expect(v3.browserbaseDebugURL).toBe("");
94+
});
95+
});
96+
97+
describe("local accessors", () => {
98+
it("stay empty for LOCAL environments", async () => {
99+
const v3 = new V3({
100+
env: "LOCAL",
101+
disableAPI: true,
102+
verbose: 0,
103+
localBrowserLaunchOptions: {
104+
cdpUrl: "ws://local-existing-session",
105+
},
106+
});
107+
108+
try {
109+
await v3.init();
110+
expect(v3.browserbaseSessionURL).toBe("");
111+
expect(v3.browserbaseDebugURL).toBe("");
112+
} finally {
113+
await v3.close().catch(() => {});
114+
}
115+
});
116+
});

0 commit comments

Comments
 (0)