perf: render command log in an isolated iframe to prevent renderer crash#34242
Open
mschile wants to merge 25 commits into
Open
perf: render command log in an isolated iframe to prevent renderer crash#34242mschile wants to merge 25 commits into
mschile wants to merge 25 commits into
Conversation
The Chromium renderer process could crash ('We detected that the Chrome
Renderer process just crashed') when the application under test performed
heavy layout work driven by a ResizeObserver loop (e.g. an Element Plus
dialog dynamically showing ~10+ form fields with an open dropdown popper)
while the command log was visible.
The crash is a Blink layout-phase fault that occurs when the live,
re-rendering React/MobX command-log tree is laid out in the same document
as the AUT's heavy reflow. Weaker isolation was verified insufficient:
Shadow DOM and CSS containment (contain: strict) still crash, as do
visibility/opacity tricks. Only moving the reporter into its own document
(a same-origin iframe) prevents the crash, while the reporterBus
EventEmitter and MobX store keep working across frames by reference.
Because the reporter's JS still runs in the top window, code that binds
document-level listeners or portals DOM nodes now targets the document
the reporter renders into (tracked via setReporterDocument):
- keyboard shortcuts bind to both the top and reporter documents
- tooltips append to the reporter's body instead of the top body
- the runnable options popover portals and binds click-outside/scroll
listeners in the reporter's document
- all parent stylesheets (not just cypress_runner.css) and root classes
are cloned into the iframe, since reporter layout relies on app-level
resets like the Tailwind preflight box-sizing rule
Falls back to inline rendering if the iframe cannot be set up.
Fixes #33962
Fixes #34218
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The reporter now renders inside a same-origin iframe (#reporter-frame), so cypress-in-cypress specs can no longer reach command-log content with top-document queries. Adds a cy.reporter() helper that yields the iframe's body once the reporter has mounted (the body rather than the .reporter root, so portaled UI like tooltips and popovers stays reachable), and migrates reporter-owned queries across the runner, studio, subscriptions, and specs-list suites. Top-document app chrome, AUT-iframe, and cloud-studio-bundle selectors are intentionally left unscoped. Also hardens the spec loaders against the reporter remount that happens on in-app spec navigation: loadSpec/runSpec now wait for the new spec's file name in the runnable header so queries can't anchor to the outgoing spec's reporter (two specs can render identical test titles), and the reporter defers its React mount until the iframe's cloned stylesheets load so clicks never land mid-layout-shift. The clear-all-sessions test now proves the rerun via the session tags flipping to 'created' instead of asserting the transient loading state, and the origin-communicator spec toggles the spec list with a click — cy.type() resolves focus against the AUT document, which doesn't work for elements in a nested iframe. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Panel resizing tracks mousemove on the resizable-panels root, but iframes swallow mouse events, so dragging the reporter panel's handle stalled as soon as the cursor crossed the command-log iframe. Apply the same pointer-events guard the AUT panel already uses: the panel2 slot now exposes isDragging (true while any panel drags, since panel 1's handle also crosses the reporter) and both runner modes disable pointer events on the reporter content during a drag. The guard is scoped to the slot content rather than the panel so the resize handle itself stays interactive. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The popover renders in the command-log iframe and can no longer overflow into the rest of the app the way it could when the reporter was inline, so a narrow panel clipped it at the iframe's left edge. Clamp its left position inside the frame and cap its width to the frame's viewport so it shrinks instead of hiding. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The app stylesheets cloned into the command-log iframe include Tailwind's responsive `.container` component, which collides with the reporter's `.container` element. Inline this never mattered because the media queries resolved against the full window width, but inside the iframe they resolve against the frame's own width, so a panel wider than a Tailwind breakpoint clamped the command log to that breakpoint's max-width (640px, 768px, ...) and the content stopped tracking panel resizes. Override the max-width inside the iframe. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Deferring the React mount until the iframe's cloned stylesheets loaded meant setupReporter marked the reporter initialized before its event listeners existed — a resetReporter round-trip could wait forever for reporter:restarted and early driver events could be missed. Mount synchronously and hide the iframe until the stylesheets load instead; hidden elements fail actionability, which equally protects interactions from landing mid-layout-shift. Also track the iframe as soon as it is appended so the inline fallback removes it if the remaining setup throws, and relax the event-manager reset assertions to tolerate the delayed file-watcher rerun that re-scaffolding the fixture project can trigger (it runs the same teardown path, making exactly-once flaky). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ress-io/cypress into mschile/busy-poincare-345146
Reveal the command log after a short timeout even if a cloned stylesheet never fires load/error, so a hung request cannot leave the reporter invisible. Also scope the sessions.ui expander clicks to the first session command row. The pre-migration chained `.get()` reset to the document root, so the migration had preserved a click on the reporter's first expander; targeting the expander inside the session row matches the test's intent and the pattern used elsewhere in the spec. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ering If reporter setup throws after the React root is created on the iframe document, the inline fallback previously created a second root while the first remained mounted on the removed iframe, leaking a detached tree and its listeners. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Contributor
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 5c45eb3. Configure here.
The popover's outside-click and scroll handlers only listened on the reporter's document, so clicking or scrolling elsewhere in the app (AUT panel, specs list, sidebar) no longer closed it the way it did when the reporter rendered inline. Bind the handlers on both the top and reporter documents. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…g iframe The driver suite's command-log helpers and the reporter's special characters spec queried the runner's reporter through the top document, which no longer reaches it now that the command log renders in a same-origin iframe. Resolve the reporter's document through #reporter-frame with an inline-rendering fallback. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The initial migration swept the top-level runner specs but not the runner/issues/ subdirectory, and left one block in studio-new-tests unscoped. Point these command-log queries at the reporter iframe: - issue-9162: the failure message assertion - issue-18042: the Spies/Stubs instrument toggle and call-count cells (clicking the .hook-header directly, since a bare contains from the iframe body matched a different element than it did inline) - studio-new-tests: the recorded new-test row and its commands Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
cypress
|
||||||||||||||||||||||||||||||||||
| Project |
cypress
|
| Branch Review |
mschile/busy-poincare-345146
|
| Run status |
|
| Run duration | 09m 08s |
| Commit |
|
| Committer | Matthew Schile |
| View all properties for this run ↗︎ | |
| Test results | |
|---|---|
|
|
1
|
|
|
0
|
|
|
1
|
|
|
0
|
|
|
9
|
| View all changes introduced in this branch ↗︎ | |
Warning
No Report: Something went wrong and we could not generate a report for the Application Quality products.
Tests for review

cypress/e2e/studio/studio-basic.cy.ts • 1 failed test • app-e2e
| Test | Artifacts | |
|---|---|---|
| Cypress Studio - Basic Functionality > removes pending commands when rerunning the test |
Test Replay
Screenshots
|
|
…frame System-test project fixtures inspect the command log to verify error rendering, session tags, and mount logs, and drive the reporter's stop/restart controls. They queried the reporter through the top document, which no longer reaches it now that the command log renders in a same-origin iframe — so the verify/inspection tests failed, adding an extra failure to each fixture run (error-ui sourcemaps, cy.origin error stack, svelte mount log, session persistence, runner reload). Resolve the reporter's document through #reporter-frame with an inline-rendering fallback in the shared fail/verify helper and the affected fixtures. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The BiDi automation assumed the AUT iframe was always the first direct child browsing context created under the top-level context. With the command log now rendering in its own iframe, the top-level context has two direct children whose creation order is not guaranteed, so the reporter frame could be misidentified as the AUT — breaking cy.origin network interception in Firefox. Identify the AUT by its window.name (seeded with AUT_FRAME_NAME_IDENTIFIER), matching how the CDP and WebKit automations locate the AUT frame. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The bidi_automation unit tests emit a single child browsing context and expect it to be adopted as the AUT. Now that the AUT is identified by its window.name rather than by creation order, stub scriptEvaluate to return a name carrying AUT_FRAME_NAME_IDENTIFIER, and add a case asserting a non-AUT child frame (the reporter iframe) is ignored. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The zonejs-mocha-skip/only Angular CT fixtures read the reporter command log via window.top.document.body to verify skipped/only test titles. With the command log now in the #reporter-frame iframe, those queries missed — zonejs-mocha-skip failed its 6 verifications (Angular "with mount tests" exit code 6) and zonejs-mocha-only would false-pass its not.exist checks against the wrong document. Resolve the reporter body from the iframe with a fallback to the top document, matching the other migrated fixtures. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The Percy snapshot override manager mutates dynamic elements (command progress bars, spinners, tooltips, active-test animations) to visibility:hidden/frozen before serialization so snapshots are deterministic. It queried only the top document, but the command log now renders in the same-origin #reporter-frame iframe, so those reporter-targeting selectors matched nothing and the dynamic content leaked into snapshots. Also query and observe the reporter iframe document so the same overrides apply there and are reverted after the snapshot. No-op when the reporter renders inline (component tests). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Rendering the command log created its React root directly on the iframe's <body>. React warns this causes subtle reconciliation issues because body's children get mutated by other code, and it showed up as unstable reporter rendering under load (off styling, and the f-shortcut/spec-list toggle not taking) — flaking cy-in-cy specs on slower CI agents while passing locally. Mount into a dedicated #unified-reporter container inside the iframe body, mirroring the inline layout, and give the frame's html/body/container full height so the reporter fills the panel. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Cloning the app's <link rel="stylesheet"> nodes into the reporter iframe copied their raw href attributes. The built app references its main CSS with a relative href (Vite's ./assets/*.css), which resolves against the iframe's about:blank base and silently fails to load (no request, no 404) — leaving the reporter without the app stylesheet (Tailwind preflight + design-system tokens) and visibly unstyled in the built binary/CI while resolving fine under the dev server. Assign each clone the resolved absolute URL so it loads regardless of the iframe's base. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
With the command log now in the #reporter-frame iframe, opening the specs list
with cy.get('body').type('f') (a synthetic keydown to the app body) no longer
reaches the reporter's shortcut handler in the cy-in-cy harness, so the panel
never opened and these two specs failed on Windows CI. Real keystrokes are
unaffected. Click the reporter's specs-list toggle (in the iframe) instead —
the same deterministic approach other cy-in-cy specs already use.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Follow-up to the previous commit. The reporter's f keydown handler binds in a
passive effect after it mounts in the #reporter-frame iframe, so pressing f
right after the runnable header appears can race that binding on slower
machines. Rather than route around the shortcut everywhere:
- specs_list_component: restore cy.get('body').type('f') and wait for the run
to finish first (reporter fully mounted), keeping the shortcut exercised.
- cypress-in-cypress-e2e: this test switches specs mid-run, so it can't wait
for the run to finish; keep clicking the reporter toggle (onClick binds at
render) and note the f shortcut is covered by reporter_header.cy.ts.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.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.

Additional details
Why was this change necessary?
When the application under test drives heavy layout work through a
ResizeObserverloop (e.g. an Element Plus dialog dynamically showing ~10+ form fields with an open dropdown popper), the Chromium renderer process can crash (We detected that the Chrome Renderer process just crashed) — but only under Cypress. The crash is a Blink layout-phase fault that occurs when the live, re-rendering React/MobX command-log tree is laid out in the same document as the AUT's heavy reflow. It happens at the engine level, before JS error handling, soCypress.on('uncaught:exception')cannot intercept it.Weaker isolation was verified insufficient during investigation: Shadow DOM still crashed (3/3),
contain: strictstill crashed (3/3), andvisibility: hidden/opacity: 0still crashed — only rendering the command log into its own document prevents the crash (0/4 crashes at the reproduction threshold, versus 4/4 inline). A standalone (non-Cypress) reproduction for an upstream Chromium report was attempted and is not currently feasible; the crash requires the integrated runner environment.What is affected by this change?
The command log now renders into a same-origin iframe (
#reporter-frame) instead of inline in the app document. The reporter's JS still runs in the top window — thereporterBusEventEmitter and MobX stores are passed by reference across the frame boundary — but its DOM, layout, and paint live in a separate document.Because the reporter's code executes in the top window while its DOM lives in the iframe, code that binds document-level listeners or portals DOM nodes now targets the reporter's document (tracked via
setReporterDocument):@cypress/react-tooltip) append to the reporter's body via a wrapper, so their coordinates and document agreebox-sizingrule), with a targeted override for Tailwind's responsive.containercomponent, which collides with the reporter's.containerelement — inside the iframe its media queries resolve against the frame width and would otherwise clamp the command log at Tailwind breakpointsmousemoveevents that drive draggingTest changes
Cypress-in-Cypress specs can no longer reach command-log content with top-document queries. A
cy.reporter()helper (packages/app/cypress/e2e/support/reporter.ts) yields the iframe's body once the reporter has mounted, and ~40 spec files were migrated to scope reporter-owned queries through it. The spec loaders also now wait for the new spec's file name in the runnable header before asserting, since in-app spec navigation remounts the iframe and consecutive specs can render identical test titles.Note
Medium Risk
Changes how the interactive command log is mounted and how document-level UI (shortcuts, tooltips, popovers) binds, which is user-visible runner infrastructure; mitigated by inline fallback and broad cy-in-cy test updates.
Overview
Fixes Chromium renderer crashes when heavy AUT layout (e.g.
ResizeObserverloops with dialogs and many fields) runs while the command log is visible, by hosting the reporter in a same-origin iframe (#reporter-frame) so its layout is no longer in the same document as the AUT panel.Runner / reporter: Mounts React into the iframe after cloning parent stylesheets and root classes, with a Tailwind
.containeroverride and a short delay before showing the frame so it is not interactive unstyled. Cleans up the frame on unmount and falls back to inline rendering if setup fails. ExposessetReporterDocumentso shortcuts, tooltips, and the runnable options popover target the iframe document (including popover width/position clamping in narrow panels). Reporter panel resizing disables pointer events on panel content while dragging so iframe hit-testing does not block resize.Automation: BiDi AUT context selection uses
window.name(AUT_FRAME_NAME_IDENTIFIER) instead of assuming the first child iframe, now that the reporter iframe is also a direct child of the top context.Tests / tooling: Adds
cy.reporter()for Cypress-in-Cypress specs and migrates reporter assertions across the app e2e suite; spec loaders wait for the runnable header file name after navigation. Driver command-log helpers and Percy overrides query the reporter iframe document when present.Changelog documents the performance fix for #33962 and #34218.
Reviewed by Cursor Bugbot for commit 0be5e19. Bugbot is set up for automated code reviews on this repo. Configure here.
Steps to test
count = 9dynamic form fields and a filterable select).cypress runorcypress open). Before this change, on affected machines the renderer crashes when the select is filtered; with this change the spec passes.r,f,a) with focus inside the command log, and drag-resizing the reporter panel across a range of widths.How has the user experience changed?
No intended visual or behavioral change — the command log looks and behaves as before; it is now hosted in an isolated document. Verified visually and via scripted interaction tests (snapshot hover/pin, popover placement, tooltip placement, panel resizing, keyboard shortcuts).
PR Tasks
cypress-documentation?type definitions?