Restore active-page waiting in V4 - #2430
Conversation
|
There was a problem hiding this comment.
5 issues found across 27 files
Confidence score: 2/5
- In
packages/server/understudy/context.ts, popup detection can race with concurrent target creation and bind to the wrong tab, so callers may automate an unrelated page instead of the popup; tie the popup signal toTarget.targetCreated.info.targetIdand wait for that target’sPageentry before returning. - In
packages/server/understudy/context.ts(activePage()path), a popup that arrives during the Chrome await can be missed, causing the previous page to be returned immediately and skipping intended popup handling; re-check popup recency after the await so in-flight events still enter the registration wait. - In
packages/sdk-python/src/stagehand/browser_context.py,await_active_page()with the documented default (timeout=None) fails client-side before RPC, which breaks the default call path for Python users; avoid markingtimeoutas explicitly set when it isNoneso the request can be sent. - Coverage is thin for the protocol/RPC behavior changes in
packages/sdk-python/src/stagehand/rpc_client.pyandpackages/protocol/schemas.ts, so regressions in indefinite waits, cancellation cleanup, and timeout validation may slip through; add focused tests for delayed response/cancel flows and omitted/valid/invalid timeout payloads.
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="packages/sdk-python/src/stagehand/rpc_client.py">
<violation number="1" location="packages/sdk-python/src/stagehand/rpc_client.py:147">
P3: The removed RPC deadline has no regression coverage for its new indefinite-wait behavior. Add focused tests for a delayed response and cancellation so pending-request cleanup remains guaranteed.
(Based on your team's feedback about unit tests for changed behavior.) [FEEDBACK_USED]</violation>
</file>
<file name="packages/protocol/schemas.ts">
<violation number="1" location="packages/protocol/schemas.ts:1378">
P3: New `context.await_active_page` wire validation has no regression coverage. Add focused parsing tests for omitted/valid timeout and rejected negative, fractional, or extra fields.</violation>
</file>
<file name="packages/server/understudy/context.ts">
<violation number="1" location="packages/server/understudy/context.ts:288">
P1: A popup arriving while `activePage()` is awaiting Chrome can be missed and return the prior page immediately. Evaluate popup recency after the await so events delivered during the lookup enter the registration wait.</violation>
<violation number="2" location="packages/server/understudy/context.ts:297">
P1: Concurrent target creation can return an unrelated newly registered tab instead of the popup. Associate `Target.targetCreated`'s `info.targetId` with the popup signal and wait for that target's `Page` entry before falling back.</violation>
</file>
<file name="packages/sdk-python/src/stagehand/browser_context.py">
<violation number="1" location="packages/sdk-python/src/stagehand/browser_context.py:72">
P2: Calling `await_active_page()` with the documented default currently fails before the RPC is sent: `ContextAwaitActivePageParams(timeout=None)` marks `timeout` as explicitly set, and the Python RPC client rejects the resulting `null` value against the integer-only protocol field. Construct the params model without the field when `timeout` is `None`, so the server can apply its default timeout.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| while (Date.now() < deadline) { | ||
| let newestTargetId: TargetId | undefined; | ||
| let newestCreatedAt = -1; | ||
| for (const [targetId] of this.pagesByTarget) { |
There was a problem hiding this comment.
P1: Concurrent target creation can return an unrelated newly registered tab instead of the popup. Associate Target.targetCreated's info.targetId with the popup signal and wait for that target's Page entry before falling back.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/server/understudy/context.ts, line 297:
<comment>Concurrent target creation can return an unrelated newly registered tab instead of the popup. Associate `Target.targetCreated`'s `info.targetId` with the popup signal and wait for that target's `Page` entry before falling back.</comment>
<file context>
@@ -271,6 +272,46 @@ export class V3Context {
+ while (Date.now() < deadline) {
+ let newestTargetId: TargetId | undefined;
+ let newestCreatedAt = -1;
+ for (const [targetId] of this.pagesByTarget) {
+ const createdAt = this.createdAtByTarget.get(targetId) ?? 0;
+ if (createdAt > newestCreatedAt) {
</file context>
| const timeout = timeoutMs ?? defaultTimeout; | ||
| const recentWindowMs = this.env === "BROWSERBASE" ? 1000 : 300; | ||
| const now = Date.now(); | ||
| const hasRecentPopup = now - this._lastPopupSignalAt <= recentWindowMs; |
There was a problem hiding this comment.
P1: A popup arriving while activePage() is awaiting Chrome can be missed and return the prior page immediately. Evaluate popup recency after the await so events delivered during the lookup enter the registration wait.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/server/understudy/context.ts, line 288:
<comment>A popup arriving while `activePage()` is awaiting Chrome can be missed and return the prior page immediately. Evaluate popup recency after the await so events delivered during the lookup enter the registration wait.</comment>
<file context>
@@ -271,6 +272,46 @@ export class V3Context {
+ const timeout = timeoutMs ?? defaultTimeout;
+ const recentWindowMs = this.env === "BROWSERBASE" ? 1000 : 300;
+ const now = Date.now();
+ const hasRecentPopup = now - this._lastPopupSignalAt <= recentWindowMs;
+
+ const immediate = await this.activePage();
</file context>
| return None if result.root is None else Page(self._rpc_client, result.root) | ||
|
|
||
| async def await_active_page(self, timeout: int | None = None) -> Page: | ||
| params = ContextAwaitActivePageParams(timeout=timeout) |
There was a problem hiding this comment.
P2: Calling await_active_page() with the documented default currently fails before the RPC is sent: ContextAwaitActivePageParams(timeout=None) marks timeout as explicitly set, and the Python RPC client rejects the resulting null value against the integer-only protocol field. Construct the params model without the field when timeout is None, so the server can apply its default timeout.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/sdk-python/src/stagehand/browser_context.py, line 72:
<comment>Calling `await_active_page()` with the documented default currently fails before the RPC is sent: `ContextAwaitActivePageParams(timeout=None)` marks `timeout` as explicitly set, and the Python RPC client rejects the resulting `null` value against the integer-only protocol field. Construct the params model without the field when `timeout` is `None`, so the server can apply its default timeout.</comment>
<file context>
@@ -67,6 +68,15 @@ async def active_page(self) -> Page | None:
return None if result.root is None else Page(self._rpc_client, result.root)
+ async def await_active_page(self, timeout: int | None = None) -> Page:
+ params = ContextAwaitActivePageParams(timeout=timeout)
+ page_ref = await self._rpc_client.send(
+ "context.await_active_page",
</file context>
| params = ContextAwaitActivePageParams(timeout=timeout) | |
| params = ContextAwaitActivePageParams() if timeout is None else ContextAwaitActivePageParams(timeout=timeout) |
| except TimeoutError as error: | ||
| raise TimeoutError(f"RPC request timed out: {method}") from error | ||
| ) | ||
| result = await response |
There was a problem hiding this comment.
P3: The removed RPC deadline has no regression coverage for its new indefinite-wait behavior. Add focused tests for a delayed response and cancellation so pending-request cleanup remains guaranteed.
(Based on your team's feedback about unit tests for changed behavior.)
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/sdk-python/src/stagehand/rpc_client.py, line 147:
<comment>The removed RPC deadline has no regression coverage for its new indefinite-wait behavior. Add focused tests for a delayed response and cancellation so pending-request cleanup remains guaranteed.
(Based on your team's feedback about unit tests for changed behavior.) </comment>
<file context>
@@ -142,17 +138,14 @@ async def send(
- except TimeoutError as error:
- raise TimeoutError(f"RPC request timed out: {method}") from error
+ )
+ result = await response
+ return cast(ResultT, result)
finally:
</file context>
| .strict() | ||
| .meta({ id: "ContextNewPageParams" }); | ||
|
|
||
| export const ContextAwaitActivePageParamsSchema = z |
There was a problem hiding this comment.
P3: New context.await_active_page wire validation has no regression coverage. Add focused parsing tests for omitted/valid timeout and rejected negative, fractional, or extra fields.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/protocol/schemas.ts, line 1378:
<comment>New `context.await_active_page` wire validation has no regression coverage. Add focused parsing tests for omitted/valid timeout and rejected negative, fractional, or extra fields.</comment>
<file context>
@@ -1375,6 +1375,15 @@ export const ContextNewPageParamsSchema = z
.strict()
.meta({ id: "ContextNewPageParams" });
+export const ContextAwaitActivePageParamsSchema = z
+ .object({
+ timeout: z.number().int().nonnegative().optional().meta({
</file context>
Adds in the missing
awaitActivePage- we only hadawait activePage()which functioned differently.V4 could read the active page before a newly opened tab had registered. This restores V3’s popup-aware active-page waiting across the protocol, server, and TypeScript/Python SDKs, and removes fixed RPC response deadlines.
tab_handlingmulti_tabawaitActivePage()and no fixed RPC response deadlinesSummary by cubic
Restores V3-style, popup-aware active-page waiting in V4 and adds
context.await_active_pageacross the protocol and TS/Python SDKs. Also removes fixed RPC response deadlines to avoid premature failures while popups register.New Features
context.await_active_pagewith optionaltimeout(ms); returnsPageRef.BrowserContext.awaitActivePage(timeoutMs?); Python:BrowserContext.await_active_page(timeout=None).Page.windowOpen/Target.targetCreatedto wait for newly opened tabs before resolving.Bug Fixes
PageNotFoundErrorif none is available.google/gemini-2.5-flash: V4 now 3/3 fortab_handlingandmulti_tabunder concurrency.Written for commit e28edcf. Summary will update on new commits.