Repository navigation
Stop Chromium from backgrounding pages in multi-context runs - #39
Merged
Merged
Conversation
A test that opened and closed a second context could leave a page in the first one marked hidden: `document.visibilityState` read `hidden`, its timers were throttled, and code waiting for the page to be visible timed out a long way from the cause. Each context gets a window of its own, and Motus launched Chromium without the switches that stop it backgrounding a window it thinks is covered. Chrome for Testing 149 headless did this on every run; Chrome 156 headless did not. `--disable-backgrounding-occluded-windows`, `--disable-renderer-backgrounding` and `--disable-background-timer-throttling` are now among the default Chromium arguments, as they are in Playwright and Puppeteer, and `IgnoreDefaultArgs` still removes any of them. Every page Motus opens in a context it created is also given `Emulation.setFocusEmulationEnabled`, so `document.hasFocus()` is true in each context's pages at once. That is skipped on Firefox, which has no BiDi equivalent, and for contexts that were already open in an attached browser, and a browser that rejects the command still opens the page. The configuration guide now lists the default arguments and why each is there. Fixes #26 Signed-off-by: Torrey Betts <torrey.betts@gmail.com>
SummarySummary
CoverageMotus - 80.9%
Motus.Abstractions - 87.7%
Motus.Analyzers - 91%
Motus.Cli - 82.2%
Motus.Codegen - 89.8%
Motus.Mcp - 91.9%
Motus.Mcp.Http - 98.6%
Motus.Recorder - 77.8%
Motus.Testing - 75.4%
Motus.Testing.MSTest - 79%
Motus.Testing.MSTest.v4 - 22.8%
Motus.Testing.xUnit - 63.6%
|
Brings in the pull requests merged since this branch was cut. Nothing conflicted, and none of them change ChromiumArgs or how BrowserContext sets up a new page. Signed-off-by: Torrey Betts <torrey.betts@gmail.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A test that uses more than one context, such as an admin and a visitor, could leave a page in the first context marked hidden once a second context had opened and closed.
document.visibilityStatereadhidden,visibilitychangehandlers ran, timers were throttled, and code waiting for the page to be visible timed out a long way from the cause. Each context gets a window of its own, and Motus launched Chromium without the switches that stop it from backgrounding a window it thinks is covered.I reproduced it headless with Chrome for Testing 149.0.7827.54 (CI's unit legs pin 149.0.7827.156) on every run: the first page went hidden right after the second context closed and its 50 ms interval dropped to about 8 ticks a second. Chrome 156 headless on the same machine did not do it, so the symptom depends on the build.
The change
ChromiumArgs.DefaultArgsgains--disable-backgrounding-occluded-windows,--disable-renderer-backgroundingand--disable-background-timer-throttling, the same three Playwright, Puppeteer and chrome-launcher pass.IgnoreDefaultArgsremoves any of them, one at a time or all together. In the repro,--disable-backgrounding-occluded-windowsalone is what kept the page visible; the other two keep a page that is backgrounded anyway (a background tab in the same window) from being throttled.Emulation.setFocusEmulationEnabled, as Playwright does, sodocument.hasFocus()is true in each context's pages at once. On Chrome for Testing 149 headless it was false even for a single page. It goes through the existing per-page setup inBrowserContextrather thanPage.InitializeAsync. It is skipped when the session lacks CDP emulation (Firefox over BiDi has no equivalent), and skipped for contexts that were already open in an attached browser, whose focus belongs to the person using them; contexts Motus creates in an attached browser get it. A browser that rejects the command answers with a protocol error, which is caught, so the page still opens. In the repro, focus emulation on its own also kept the page visible, so the switches and the emulation each fix it.IgnoreDefaultArgs, and the focus emulation. The attaching guide notes that an attached browser runs with whatever switches it was started with, and the lifecycle doc mentions both.Considered and left out:
--disable-features=CalculateNativeWinOcclusion(chrome-launcher only).--disable-backgrounding-occluded-windowsalready stops an occluded window from being treated as hidden on every platform, and Motus has no--disable-featuresof its own. Adding one would mean merging it with a caller's, since Chromium honors only the last feature-list switch. A unit test now pins that the defaults carry no feature-list switch.--disable-back-forward-cache(Playwright). It changes navigation semantics and has nothing to do with backgrounding.--disable-features=IntensiveWakeUpThrottling. Neither Playwright nor Puppeteer passes it, and it only applies to pages hidden for minutes, which the occlusion switch already prevents for pages in their own windows.--disable-ipc-flooding-protection(Playwright and Puppeteer). Unrelated to visibility.Behavior changes
--disable-backgrounding-occluded-windows,--disable-renderer-backgroundingand--disable-background-timer-throttlingby default. Pages stay visible and keep their timer cadence when other contexts or windows come and go. Name them inIgnoreDefaultArgsto test backgrounding.document.hasFocus()is true, and focus and blur events fire as though each page's window had focus. Pages in contexts adopted from an attached browser and Firefox pages are unchanged.No public API changes.
Tests
ChromiumBackgroundingArgsTests(unit): the three switches are in the defaults, headless and headed; each can be ignored on its own while the others stay; ignoring every default removes them all; no switch is given twice; no default feature-list switch competes with one inArgs.FocusEmulationTests(unit): a new page sends the command on its own session withenabled: true; a protocol error for it still opens the page (fails if the catch is removed); a page opened in an adopted context sends nothing.BackgroundingIntegrationTests(integration, Chromium): a page in one context stays visible, sees novisibilitychange, and keeps its timer cadence after a second context opens and closes; pages in two contexts both reportdocument.hasFocus(); the running browser's command line (fromchrome://version) has the three switches, and lacks them when they are inIgnoreDefaultArgs. Without the fix, on Chrome for Testing 149 headless (MOTUS_EXECUTABLE_PATH), three of the four fail: hidden page, no focus, missing switches. On Chrome 156 only the command-line test fails without the fix, because that build doesn't background the page. CI's integration job runs stable Chrome, so the visibility test may pass there either way; the command-line test guards the switches regardless.Emulation.setFocusEmulationEnabledas bookkeeping, so the queued fixtures across the suite didn't shift.Run locally on macOS:
BackgroundingIntegrationTests,BrowserContextPopupTests,BrowserAttachIntegrationTests,BrowserIntegrationTests,PageIntegrationTests,BrowserContextRouteIntegrationTests(43 each), plus Motus.Mcp.TestsBrowserAttachIntegrationTests(6 each). All pass, except thatPageClosedAfterConnect_IsRetiredtimed out once on net8.0 and passed when the class was rerun; it's in the known-flaky attach class.BackgroundingIntegrationTestswith Chrome for Testing 149 (net10.0): 4 pass with the fix, 3 fail without it.TestCategory=Firefox), net8.0 and net10.0: 6 of 6 each.Fixes #26