Skip to content

Wait for a page target before resizing the display - #435

Open
yummybomb wants to merge 5 commits into
mainfrom
hypeship/wait-for-page-target
Open

yummybomb wants to merge 5 commits into
mainfrom
hypeship/wait-for-page-target

Conversation

@yummybomb

@yummybomb yummybomb commented Oct 5, 2026 •

Copy link
Copy Markdown
Contributor

Summary

PATCH /display and the display step of /chromium/configure can return 500 no page target found right after Chromium starts or restarts. DevTools can accept connections shortly before Chromium opens its first tab, and firstPageTargetID looked up the page target once and failed on an empty result. In the server e2e suite this shows up as intermittent failures in whichever test resizes first, such as TestReplayRecordingIncludesAudioTrack, TestDisplayResolutionChange, or TestChromiumConfigureMultipartPowerset/display. Re-running the job usually passes.

firstPageTargetID now polls Target.getTargets every 100ms for up to 5s, with the last sleep clamped to the deadline, before returning no page target found. It returns early with the context error if the caller's context is cancelled, and a Target.getTargets error still returns immediately. The lookup itself moves into findPageTargetID.

Callers:

  • SetDeviceMetricsOverride and SetWindowBoundsMaximized (via GetWindowBounds): the display handlers run these under a 10s timeout, so the wait fits inside it.
  • DispatchStartURLAndWait: DispatchStartURL already creates a tab if none exists, so the wait normally returns on the first poll.

Tradeoff: when there really is no page target (for example, every tab was closed), the request now fails after about 5s instead of immediately. On the headless path with active recordings, the CDP viewport call is non-fatal, but recordings stay stopped until the handler returns, so the recording gap can grow by up to 5s in that case.

Testing

  • Unit tests in lib/cdpclient, with the wait timeout and poll interval made package vars so tests can adjust them:
    • waits for first page target: the fake browser reports no page target for the first three Target.getTargets calls, and the resize succeeds on the fourth. Fails without the fix.
    • no page target: fails with no page target found after the (shortened) timeout. It runs under a context deadline so it fails instead of hanging if the wait is unbounded, and uses a long poll interval so it also covers the deadline clamp.
    • no page target respects context: cancelling the context ends the wait early and returns context.Canceled.
    • Removing the polling, the deadline check, the ctx.Done() case, the clamp, or the %w wrapping each fails at least one of these.
  • go test -race ./lib/cdpclient/ and go test ./cmd/api/api/ pass. TestSetDeviceMetricsOverride passed 20 runs under -race, including on 1–2 CPUs with busy-loop processes competing.
  • Manual check against a headless image built from main. I closed every page target over CDP and then called PATCH /display with a new size:
    • before the fix: immediate 500 failed to change resolution: CDP setDeviceMetricsOverride: no page target found (the same error CI hits)
    • after the fix, tab reopened 1s after the request started: 200
    • after the fix, no tab at all: 500 ... no page target found after about 5.1s
    • after the fix, ordinary resize: 200, unchanged
  • I could not reproduce the startup race naturally on an idle or CPU-limited local Docker host. It appears to need CI-level contention, which is why the manual check forces the no-tab state.
  • I did not run the full Docker e2e suite.

Note

Medium Risk
Changes timing and error behavior for display/configure CDP paths; legitimate no-tab cases now block up to 5s, which can extend recording gaps on headless flows.

Overview
Fixes intermittent no page target found failures when display resize or Chromium configure runs right after DevTools connects but before the first tab exists.

firstPageTargetID no longer fails on the first empty Target.getTargets result. It polls every 100ms for up to 5s (package-level vars for tests), clamps the final sleep to the deadline, and still exits early on context cancellation. Lookup logic moves to findPageTargetID, which returns "" when no page target exists yet instead of an error.

All callers that resolve the user-facing page window—SetDeviceMetricsOverride, GetWindowBounds / SetWindowBoundsMaximized, and DispatchStartURLAndWait—inherit the wait. When no page target ever appears, operations fail after ~5s rather than immediately.

Tests extend the fake CDP to simulate delayed page targets and add cases for bounded timeout, successful retry after empty polls, and context-cancelled waits.

Reviewed by Cursor Bugbot for commit dee70d1. Bugbot is set up for automated code reviews on this repo. Configure here.

DevTools can accept connections shortly before Chromium opens its first
tab, at startup and after a restart. firstPageTargetID looked up the page
target once and failed immediately, so PATCH /display and the display step
of /chromium/configure returned 500 "no page target found" in that window.

Poll Target.getTargets for up to 5s before giving up, still honoring the
caller's context.
@yummybomb
yummybomb marked this pull request as ready for review October 5, 2026 18:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant