Wait for frames to load after an action

Merged
YO
10d ago
+388-18

Summary

On a public bid-information site built of framesets, a press that posts a form into a frame (the search, and each result's 「表示」 detail link) returned before the frame loaded the answer. In a browser session:

{"click": "7-2214"}   # 表示 on the first result
-> status done, duration 1231 ms, writes: [], outline: still the results list

The detail page had loaded in the frame by then, and the POST that loaded it was reported only by the next command. In a browser.run step the operation after such a press reads the frame as it was before it, so an extract after a search reads the search form, not the results.

With a local frameset whose content frame is answered 1.5 s after the post, the session command reports writes: [] and the old frame on main, and the results with the POST on this branch.

Root cause

settleDocument decides when what an action started has finished by watching the top document only: its fetch/XHR requests and DOM mutations (watchExpression/quietExpression), and, for a new top document, network idle. A form posted into a frame changes neither: the top document stays the same frameset, and the frame keeps its old document until the answer commits. So the top document reported quiet and the wait ended at once.

Two smaller gaps made it worse:

  • The quiet period was counted from when the watch was installed, before the act's model call. After an act whose model took a second or more, the period had already run out when the click happened, so a navigation the click started a moment later (a handler that submits on a timer, or a posted form task) was not waited for.
  • The site's search link runs top.openSubWindow(); document.frm.submit(): it opens a small "searching" window, which becomes the page in front, and the results close it. The wait read the page it found at the start, the window, and once the window closed it fell into a network-idle wait on a closed page until the settle limit.

Fix

  • browserhost.WatchFrameLoads: a watcher, like the dialog and response watchers, that attaches to every page and to the cross-origin frames under it (their own targets), enables the Page domain on each, and tracks Page.frameStartedLoading/frameStoppedLoading/frameDetached per session, dropping a session when its target detaches. Loading() reports whether any frame of any page is loading a document.
  • The engine starts it with the other watchers and closes it in release.
  • settleDocument waits while any frame loads, reads the page in front on each check instead of once, and returns on a quiet document only after watching for settleQuiet from the end of the action.

The first change is the one that fixes the frame case; the post-action window costs up to 500 ms after an act whose page does not change, which a replay already paid since its watch is installed just before the press.

Tests

  • TestStagehandWaitsForTheFrameAPressLoads serves a frameset whose menu frame posts into the content frame, answered after 1.5 s. Three presses: a replayed one; an act whose model answers after a second and whose button submits 200 ms after it is pressed; and one that opens a "searching" window that the results close. Each must leave the results in the snapshot, and return well before the settle limit (raised to a minute for the test). All three fail on main. Removing the frame-load check fails all three, counting the window from the watch instead of the action fails the act case, and reading the page once fails the window case (it waits out the limit).
  • TestFrameLoadWatcherTellsLoadingFrames drives the watcher with a scripted browser: loading while either of two frames loads, still loading after one stops, not after the other is detached, and not after a loading page's target detaches. TestFrameLoadWatcherTellsCrossOriginFrames checks that a load in a cross-origin frame target is seen once the watcher attaches to it under its page.

Also checked with the built binary against the live site: the search, and 「表示」 on a result, each return with the page they loaded and the write they sent.

Summary by CodeRabbit

  • Bug Fixes
    • Browser actions now wait for content in embedded frames and newly opened pages to finish loading before continuing. This helps ensure that page snapshots include results and other content that loads asynchronously within frames.
    • Loading in cross-origin frames is also accounted for, and waiting ends when the relevant content finishes loading or its page closes.