Repository navigation
fix(server): preview_open reports a tab it shows as visible #26055
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
1a97461
dfd49a3
0b70531
f731eb4
207feb9
c637dae
4b76f9d
4a94e01
d225ecd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -106,10 +106,24 @@ const AGENT_CURSOR_MOVE_MS = 160; | |
| const AGENT_CURSOR_CLICK_LEAD_MS = 40; | ||
| /** A dialog blocks page script, so a desktop size read gives up after this. */ | ||
| const DESKTOP_VIEWPORT_READ_TIMEOUT = "1 second"; | ||
| // A revealed tab's client opens the panel and attaches its stream a few round trips later, remote ones included. | ||
| const REVEAL_VIEWER_WAIT_MS = 1000; | ||
|
|
||
| const sleepUntil = (deadline: number) => | ||
| new Promise<void>((resolve) => setTimeout(resolve, Math.max(0, deadline - Date.now()))); | ||
|
|
||
| const awaitViewer = async (tab: ServerTab, timeoutMs: number) => { | ||
| const attached = (tab.viewerAttached ??= Promise.withResolvers()); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Release the attachment promise after a timeout. If repeated explicit opens receive no viewer, each
|
||
| let timer: ReturnType<typeof setTimeout> | undefined; | ||
| await Promise.race([ | ||
| attached.promise, | ||
| new Promise<void>((resolve) => { | ||
| timer = setTimeout(resolve, Math.max(0, timeoutMs)); | ||
| }), | ||
| ]); | ||
| clearTimeout(timer); | ||
| }; | ||
|
|
||
| const EDITABLE_AT_POINT_SCRIPT = `(x, y) => { | ||
| let element = document.elementFromPoint(x, y); | ||
| while (element && element.shadowRoot) { | ||
|
|
@@ -303,6 +317,7 @@ interface ServerTab { | |
| readonly createdAt: number; | ||
| usedAt: number; | ||
| readonly viewers: Set<ViewerState>; | ||
| viewerAttached: PromiseWithResolvers<void> | null; | ||
| readonly consoleEntries: Array<PreviewAutomationConsoleEntry>; | ||
| readonly networkEntries: Array<PreviewAutomationNetworkEntry>; | ||
| readonly actionTimeline: Array<PreviewAutomationActionEvent>; | ||
|
|
@@ -755,6 +770,7 @@ const make = Effect.gen(function* () { | |
| if (tabs.get(key) !== tab) return; | ||
| tabs.delete(key); | ||
| tab.closing = true; | ||
| tab.viewerAttached?.resolve(); | ||
| clearAbortedNavigation(tab); | ||
| // A desktop page outlives the connection unless its session closed with it. | ||
| const end = tab.desktop && !closeSession && sessionOpen(tab) ? "reconnect" : "gone"; | ||
|
|
@@ -880,6 +896,7 @@ const make = Effect.gen(function* () { | |
| createdAt: Date.now(), | ||
| usedAt: Date.now(), | ||
| viewers: new Set(), | ||
| viewerAttached: null, | ||
| consoleEntries: [], | ||
| networkEntries: [], | ||
| actionTimeline: [], | ||
|
|
@@ -2135,6 +2152,7 @@ const make = Effect.gen(function* () { | |
| }); | ||
| } | ||
| const reveal = open.open ?? open.show; | ||
| let shown: Promise<void> | undefined; | ||
| if (reveal !== false) { | ||
| await runPromise( | ||
| manager.requestReveal({ | ||
|
|
@@ -2143,7 +2161,14 @@ const make = Effect.gen(function* () { | |
| force: reveal === true, | ||
| }), | ||
| ); | ||
| // Only an explicit show is sure to reach a client; desktop tabs are visible without a viewer. | ||
| if (reveal === true && tab.viewers.size === 0 && tab.desktop === null) | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| shown = awaitViewer( | ||
| tab, | ||
| Math.min(REVEAL_VIEWER_WAIT_MS, deadlineMs - performance.now()), | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| ); | ||
| } | ||
| await shown; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Check whether the tab still exists after the viewer wait. If a client closes the tab during this wait,
|
||
| return statusWithTitle(tab, request.agentSessionId); | ||
| }, | ||
| deadlineMs, | ||
|
|
@@ -2668,6 +2693,8 @@ const make = Effect.gen(function* () { | |
| yield* Effect.acquireRelease( | ||
| Effect.sync(() => { | ||
| tab.viewers.add(viewer); | ||
| tab.viewerAttached?.resolve(); | ||
| tab.viewerAttached = null; | ||
|
Comment on lines
+2696
to
+2697
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Signal visibility only after the stream is ready. If
|
||
| reportLiveTabs(); | ||
| }), | ||
| () => | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Use a deterministic test clock.
This test uses mocked browser contexts, not a subprocess or network fixture. Its success also depends on attachment beating a real one-second timer under load. Use
it.effectand synchronize attachment without relying on wall-clock time. (raw.githubusercontent.com)As per coding guidelines, “
it.liveis wall-clock and only for subprocess or network fixtures.” As per path instructions, tests must not rely on sleeps or polling.Sources: Coding guidelines, Path instructions