Skip to content

[test] Add a visual regression fixture for the DocSearch modal - #49002

Open
LukasTy wants to merge 8 commits into
mui:masterfrom
LukasTy:claude/docsearch-argos-fixture
Open

[test] Add a visual regression fixture for the DocSearch modal#49002
LukasTy wants to merge 8 commits into
mui:masterfrom
LukasTy:claude/docsearch-argos-fixture

Conversation

@LukasTy

@LukasTy LukasTy commented Aug 20, 2026

Copy link
Copy Markdown
Member

Why

Argos renders @mui/material fixtures and docs demos. It never mounts the docs app shell, so AppSearch and the DocSearch overrides it carries have no coverage at all -- grep -rl "AppSearch\|DocSearch" test/regressions/ returns nothing. The only DocSearch test is test/e2e-website/material-docs.spec.ts, which types a query and clicks a result; it asserts nothing about layout.

Two layout regressions reached review in #48983 that way:

  • v5 added padding-inline-start: 1em to the hit lists, indenting the result cards but not the section headings above them.
  • v5 wraps the hit title and path below 768px, turning the compact rows into multi-line cards.

Both are CSS-only, on class names we already override, and nothing in CI flagged either.

What

test/regressions/AppSearchFixture.tsx renders the real AppSearch, with one route per colour scheme behind it. index.test.js opens the modal and screenshots it; the route loop also captures the closed search button. Four baselines in total: light and dark, closed and open.

Dark mode is covered because AppSearch carries a second GlobalStyles block just for it, and that is what the v3 -> v4 migration broke. The fixture uses the docs theme through getTheme rather than BrandingProvider, so it carries theme.vars and the dark block keys off [data-mui-color-scheme="dark"] exactly as in production rather than the .mode-dark fallback.

Both screens are covered. The start screen comes from localStorage seeded with two stored recent searches, in the exact shape DocSearch persists. The results screen comes from a canned /1/indexes/*/queries response that index.test.js fulfills through page.route, so the real search client, transformItems and hit rendering all run, but the baseline never touches the live index. Stubbing at the network layer means AppSearch needs no test-only prop.

That matters because stored recent searches carry no _snippetResult. Without the results screen there would be no <mark> anywhere in the baseline, and highlighted matches, breadcrumbs and the tree connector between a section and its children are the markup most likely to shift between DocSearch majors.

Eight baselines: light and dark, each closed, open, with results, and with results at 767px. The narrow capture is what reaches the rule that motivated half of this fixture -- below 768px DocSearch lets the hit title and path wrap instead of truncating, and media queries read the viewport, so cropping to the modal never gets there. 767px keeps the modal at the width our max-width override gives it on desktop, leaving the typography as the only thing that differs.

Two things the regression bundle does not give us that the docs do:

  • Layer order. BrandingCssVarsProvider declares docsearch ahead of mui. Without it the DocSearch stylesheet outranks every override and the modal renders unstyled. A layer's position is fixed where it is first named, and emotion keeps prepending its tags above everything in <head> as components mount, so the only stable point is after the modal has rendered. That is why the declaration lives in the test rather than the fixture.
  • CssBaseline. TestViewer sets box-sizing: content-box on purpose, to catch components that lean on the docs' reset. With the layer order corrected that now beats the border-box DocSearch sets on its own subtree, and the modal overflows. The docs render CssBaseline, so the fixture does too.

Notes

The shared setup lives at test/regressions/AppSearchFixture.tsx rather than under fixtures/, where the eager glob turns every file into a route. MarketingWrapper.tsx sets the same precedent.

Based on master, so the baseline is DocSearch v4. #48983 should then show the v4 -> v5 diff in Argos once it picks this up.

test:regressions:dev currently fails to boot on an unrelated sinon dep-optimization error, so this was verified through test:regressions:build plus test:regressions:server, which is what CI runs.

One known wobble: the light desktop results screenshot is not byte-identical between runs. The difference is 50 pixels on a single row, one unit apart in the green channel -- the underline under a highlighted match. It sits half inside the overflow: hidden clip on .DocSearch-Hit-title, so it composites at fractional pixel coverage and Chromium rounds it either way. Two consecutive captures of the same DOM differ, so it is rasterization rather than anything the test can wait for. It is invisible and far below any sane diff threshold, but worth knowing about, and it settles once #48983 lands, since the fix there gives the underline real room instead of half a pixel. Byte hashes are reproducible within a session but shift between sessions for every screenshot in the suite, the pre-existing ones included, which is why Argos compares perceptually rather than by hash.

Argos covers `@mui/material` fixtures and docs demos. It never mounts the docs
app shell, so `AppSearch` and the DocSearch overrides it carries had no
coverage. Two layout regressions reached review during the v4 -> v5 bump that
way: the hit list picked up a 1em indent the section headings did not, and the
hit title started wrapping below 768px.

Render the real `AppSearch`, seed the stored recent searches so the hit list
comes up without an Algolia round-trip, and screenshot the open modal. The
route loop also captures the closed search button.

Two things the regression bundle does not give us that the docs do:

- The layer order. `BrandingCssVarsProvider` declares `docsearch` ahead of
  `mui`; without it the DocSearch stylesheet outranks every override. A layer's
  position is fixed where it is first named and emotion keeps prepending its
  tags above everything in `<head>`, so the test declares it once the modal has
  rendered.
- `CssBaseline`. `TestViewer` sets `box-sizing: content-box` on purpose, and
  with the layer order corrected that beats the `border-box` DocSearch sets on
  the modal subtree, which overflows it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@code-infra-dashboard

code-infra-dashboard Bot commented Aug 20, 2026

Copy link
Copy Markdown

Deploy preview

https://deploy-preview-49002--material-ui.netlify.app/
QR code for https://deploy-preview-49002--material-ui.netlify.app/

Bundle size

Bundle Parsed size Gzip size
@mui/material 0B(0.00%) 0B(0.00%)
@mui/lab 0B(0.00%) 0B(0.00%)
@mui/private-theming 0B(0.00%) 0B(0.00%)
@mui/system 0B(0.00%) 0B(0.00%)
@mui/utils 0B(0.00%) 0B(0.00%)

Details of bundle changes


Check out the code infra dashboard for more information about this PR.

@LukasTy LukasTy added test type: enhancement It’s an improvement, but we can’t make up our mind whether it's a bug fix or a new feature. labels Aug 20, 2026
@LukasTy LukasTy self-assigned this Aug 20, 2026
@LukasTy LukasTy mentioned this pull request Aug 20, 2026
1 task
LukasTy and others added 3 commits August 20, 2026 14:24
The v3 -> v4 migration broke dark mode, and `AppSearch` carries a second
`GlobalStyles` block just for it, so screenshot both schemes.

Move the setup into `AppSearchFixture` and leave one route per scheme behind it.
Helpers cannot live under `fixtures/`, where the eager glob turns every file
into a route.

Use the docs theme through `getTheme` rather than `BrandingProvider`. It carries
`theme.vars`, so the dark block keys off `[data-mui-color-scheme="dark"]` as it
does in production instead of the `.mode-dark` fallback. The light screenshot is
byte-identical either way. The mode storage key is per scheme: the route loop
reuses one page, and a mode persisted by one route would otherwise carry into
the other.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The start screen shows stored recent searches, which carry no `_snippetResult`,
so the baseline had no highlighted matches, no breadcrumbs and no tree
connectors -- the markup most likely to shift between DocSearch majors.

Answer the search request from `index.test.js` with a canned
`/1/indexes/*/queries` response and screenshot the results screen. Stubbing at
the network layer keeps `AppSearch` untouched and still exercises the real
search client, `transformItems` and hit rendering. It also keeps the baseline
off the live index, which is recrawled regularly.

Six baselines now: light and dark, each closed, open and with results.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@zannager
zannager requested a review from brijeshb42 August 21, 2026 13:45
@LukasTy
LukasTy requested review from Janpot and a balanced review from Copilot August 24, 2026 07:56

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds visual regression coverage for the real DocSearch-based AppSearch UI across light and dark themes.

Changes:

  • Adds fixtures for closed, start, and results states.
  • Stubs Algolia responses for deterministic screenshots.
  • Configures theme, storage, CSS layers, and search index.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
test/regressions/vite.config.mts Defines the fixture search index.
test/regressions/index.test.js Captures modal and results screenshots.
test/regressions/fixtures/AppSearch/SearchModalDark.tsx Adds the dark fixture route.
test/regressions/fixtures/AppSearch/SearchModal.tsx Adds the light fixture route.
test/regressions/AppSearchFixture.tsx Provides production-like AppSearch setup.
test/regressions/algoliaSearchStub.js Supplies deterministic search results.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread test/regressions/index.test.js
Comment thread test/regressions/index.test.js Outdated
LukasTy and others added 3 commits August 24, 2026 13:09
Two items from review.

The dedicated modal tests inherit the suite's 1000px viewport, so they could not
reach the rule that motivated half of this fixture: below 768px DocSearch lets
the hit title and path wrap instead of truncating. Cropping to the modal does
not help, since media queries read the viewport. Add a results capture at 767px,
which keeps the modal at the width our `max-width` override gives it on desktop
so the typography is the only thing that differs. The stub's content hit is now
long enough to actually wrap; a string that fits on one line rendered the same
either way.

The open capture waited on `.DocSearch-Modal` and the lazy stylesheet, and
neither says anything about the two areas it screenshots. The stylesheet request
starts when `AppSearch` mounts, well before the modal opens, so that wait can
already be satisfied. Wait for a stored hit and a start-screen item instead.

That surfaced a real ordering bug: the seed was written by the fixture on mount,
but `pooled` clears storage on acquisition and navigating to a route the page
already rendered does not remount it, so the modal could open with no recent
searches. The previous baselines were luck. Seed from the test instead, with the
data and the index name shared through `docsearchFixtureData`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Pooled pages only get their viewport and storage reset between tests, so the
`@layer docsearch, mui;` style stayed in `<head>` for every later test on that
page. It is inert for fixtures that put nothing in `docsearch`, but leaving it
behind makes their layer order depend on which test ran first.

Move it inside the existing `try` and drop it alongside the search stub.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@LukasTy
LukasTy requested a balanced review from Copilot August 24, 2026 11:39

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

test type: enhancement It’s an improvement, but we can’t make up our mind whether it's a bug fix or a new feature.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants