Skip to content

Commit 81c187f

Browse files
authored
chore(browserbase): add ensureActivePage method for page management (#1919)
1 parent 665ee9e commit 81c187f

File tree

1 file changed

+52
-10
lines changed

1 file changed

+52
-10
lines changed

apps/api/src/browserbase/browserbase.service.ts

Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,31 @@ export class BrowserbaseService {
4747
return process.env.BROWSERBASE_PROJECT_ID || '';
4848
}
4949

50+
/**
51+
* Stagehand sometimes has no active page (or the page gets closed mid-run),
52+
* which causes errors like: "No Page found for awaitActivePage: no page available".
53+
* Ensure there's at least one non-closed page available, and create one if needed.
54+
*/
55+
private async ensureActivePage(stagehand: Stagehand) {
56+
const MAX_WAIT_MS = 5000;
57+
const POLL_MS = 250;
58+
const startedAt = Date.now();
59+
60+
while (Date.now() - startedAt < MAX_WAIT_MS) {
61+
// Stagehand's Page type doesn't always expose Playwright's `isClosed()` in typings.
62+
// We still want to filter out closed pages at runtime when possible.
63+
const pages = stagehand.context.pages().filter((p) => {
64+
const maybeIsClosed = (p as { isClosed?: () => boolean }).isClosed;
65+
return typeof maybeIsClosed === 'function' ? !maybeIsClosed() : true;
66+
});
67+
if (pages[0]) return pages[0];
68+
await delay(POLL_MS);
69+
}
70+
71+
// Last resort: create a page (may still fail if the CDP session already died)
72+
return await stagehand.context.newPage();
73+
}
74+
5075
// ===== Organization Context Management =====
5176

5277
async getOrCreateOrgContext(
@@ -250,10 +275,7 @@ export class BrowserbaseService {
250275
const stagehand = await this.createStagehand(sessionId);
251276

252277
try {
253-
const page = stagehand.context.pages()[0];
254-
if (!page) {
255-
throw new Error('No page found in browser session');
256-
}
278+
const page = await this.ensureActivePage(stagehand);
257279

258280
await page.goto(url, {
259281
waitUntil: 'domcontentloaded',
@@ -278,6 +300,18 @@ export class BrowserbaseService {
278300
isLoggedIn: result.isLoggedIn,
279301
username: result.username,
280302
};
303+
} catch (err) {
304+
const message = err instanceof Error ? err.message : String(err);
305+
const isNoPage =
306+
message.includes('awaitActivePage') ||
307+
message.includes('no page available') ||
308+
message.includes('No page found');
309+
if (isNoPage) {
310+
throw new Error(
311+
'Browser session ended before we could verify login status. Please retry.',
312+
);
313+
}
314+
throw err;
281315
} finally {
282316
await stagehand.close();
283317
}
@@ -696,10 +730,7 @@ export class BrowserbaseService {
696730
const stagehand = await this.createStagehand(sessionId);
697731

698732
try {
699-
const page = stagehand.context.pages()[0];
700-
if (!page) {
701-
throw new Error('No page found in browser session');
702-
}
733+
let page = await this.ensureActivePage(stagehand);
703734

704735
// Navigate to target URL
705736
await page.goto(targetUrl, {
@@ -746,6 +777,9 @@ export class BrowserbaseService {
746777

747778
// Evaluate if the automation fulfills the task requirements BEFORE taking screenshot
748779
if (taskContext) {
780+
// Re-acquire page in case the agent closed/replaced it during execution
781+
page = await this.ensureActivePage(stagehand);
782+
749783
const evaluationSchema = z.object({
750784
passes: z
751785
.boolean()
@@ -808,6 +842,7 @@ Only pass if there is clear evidence the requirement is properly configured and
808842
}
809843

810844
// Only take screenshot if evaluation passed (or no task context)
845+
page = await this.ensureActivePage(stagehand);
811846
const screenshot = await page.screenshot({
812847
type: 'jpeg',
813848
quality: 80,
@@ -822,10 +857,17 @@ Only pass if there is clear evidence the requirement is properly configured and
822857
};
823858
} catch (err) {
824859
this.logger.error('Failed to execute automation', err);
860+
const message = err instanceof Error ? err.message : String(err);
861+
const isNoPage =
862+
message.includes('awaitActivePage') ||
863+
message.includes('no page available') ||
864+
message.includes('No page found');
825865
return {
826866
success: false,
827-
error:
828-
err instanceof Error ? err.message : 'Failed to execute automation',
867+
needsReauth: isNoPage ? true : undefined,
868+
error: isNoPage
869+
? 'Browser session ended before we could capture evidence. Please retry.'
870+
: message,
829871
};
830872
} finally {
831873
await stagehand.close();

0 commit comments

Comments
 (0)