[test] Stabilize Data Grid demo data in Argos screenshots#48654
Conversation
The product composites that render a Data Grid (XHero, XGridFullDemo, XDataGrid, XTheming via `useDemoData` from `@mui/x-data-grid-generator`) generated random rows on every page visit, so their Argos screenshots diffed on every run (e.g. build 49389). Seed the generator's Chance instances deterministically via a Vite `define` that sets `__DISABLE_CHANCE_RANDOM__` to `true` — the same token `@mui/x-data-grid-generator` checks at module scope to switch from `new Chance()` to `new Chance(() => 0.5)` / `new Chance(42)`. This is the mechanism mui-x's regression bundle uses. With a constant `() => 0.5` source every Chance call returns the same value, so the generated columns are identical across loads regardless of render order; the `Math.random` seed-extension path (only hit by XHero's `rowLength: 10000`) copies from seed rows whose columns are now all identical, so it no longer affects the output either. Verified the built bundle resolves to the seeded branch (`new Chance(() => .5)` / `new ...(42)`), so the `define` reaches the dependency in node_modules. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Deploy previewhttps://deploy-preview-48654--material-ui.netlify.app/ Bundle size
Check out the code infra dashboard for more information about this PR. |
The `aria-busy` gate only tracks font loading, not `useDemoData`'s async row generation, so XHero/XGridFullDemo/XTheming could be screenshotted while their Data Grid still showed the loading skeleton — a source of Argos diffs. Add a `waitForSelector` for a real data cell (`.MuiDataGrid-row .MuiDataGrid-cell`; the skeleton uses `.MuiDataGrid-skeletonRow`). Each rule restates the productX width since rules are last-match-wins. XDataGrid isn't in the composite allowlist, so it needs no rule. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
| @@ -0,0 +1,44 @@ | |||
| // Force programmatic scrolling to be instant so screenshots are deterministic. | |||
| // | |||
| // Some composites scroll on mount with `{ behavior: 'smooth' }` (e.g. | |||
There was a problem hiding this comment.
🤔 Doesn't smooth scroll not respect reduced motion?
`MaterialStyling` scrolled its code panel on mount with
`behavior: 'smooth'` unconditionally. An explicit JS `behavior: 'smooth'`
isn't governed by `prefers-reduced-motion` (only CSS `scroll-behavior`
is), so it animated even for users who asked to reduce motion — and it
made the Argos screenshot non-deterministic (captured mid-scroll at a
varying offset).
Gate the behavior on `matchMedia('(prefers-reduced-motion: reduce)')`,
matching the existing pattern in `AppLayout/components/BackToTop.tsx`.
This respects the preference in the live docs and, because the
regression runner emulates `reduced-motion: reduce`, also makes the
screenshot deterministic — so the test-harness scroll patch from the
previous revision is dropped.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
e67d321 to
4cb681f
Compare
The previous `waitForSelector: '.MuiDataGrid-row .MuiDataGrid-cell'` wasn't enough: the loading overlay renders above the rows (zIndex 5), so the selector matched while the spinner was still painted on top — XHero (`rowLength: 10000`) renders its first rows but keeps the overlay up during seed extrapolation, and its Argos screenshot still showed the spinner. Add a `waitForSelectorDetached` rule field and wait for `.MuiDataGrid-overlayWrapper` to detach (it unmounts when `loading` clears) for the three grid composites. Also scope both waits to the route's testcase element instead of the whole page, so a pooled page's leftover DOM from the previous route can't satisfy the wait early. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`__DISABLE_CHANCE_RANDOM__` makes `@mui/x-data-grid-generator` seed its data `Chance` with `new Chance(() => 0.5)` — a constant source, so every generated row is identical (same name/commodity/quantity, differing only by id). Deterministic, but the grid demos look boring and unrealistic. Add a Vite transform that rewrites that one seed to `new Chance(42)`, a fixed integer that yields a varied-but-deterministic sequence. Verified the rows now differ across rows and stay byte-identical across runs. Safe for the regression runner: each composite renders on its own browser page (its own module-scoped `chance`) and does a single generation pass via `useDemoData`, so concurrent grids never interleave the shared sequence. The transform throws if upstream reformats the seed line, so it can't silently regress to constant data. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
| await page.waitForSelector(screenshotRule.waitForSelector); | ||
| await testcase.waitForSelector(screenshotRule.waitForSelector); | ||
| } | ||
| if (screenshotRule?.waitForSelectorDetached) { |
There was a problem hiding this comment.
Is an extra option really needed? Looks like we can just waitForSelector on some cell content in the grid. waitForSelectorDetached feels wrong as it looks like technically it could also fire before react has the chance to render the loading overlay. Looks like this could be another source of flakyness in a way that waitForSelector could never be.
There was a problem hiding this comment.
Otherwise, even if we do want a negative selector, perhaps it could be something like
waitForSelector: '.MuiDataGrid-root:not(:has(.MuiDataGrid-overlayWrapper))'?
There was a problem hiding this comment.
Thanks for observing this. I backtracked the solution.
AFAIK, X relies on targeting specific cell.
Trying it out. Two demos are still captured with loaders, will refine it to ensure screenshots are captured after data is loaded
Addresses @Janpot: `waitForSelectorDetached` was both an unnecessary extra option and a potential race — it could fire before React has even rendered the loading overlay (at first paint the overlay isn't mounted yet, so "detached" is trivially true). `useDemoData` sets the rows and clears `loading` in the same tick, so a real `.MuiDataGrid-row` cell only exists once loading has finished (the skeleton uses `.MuiDataGrid-skeletonRow`). So a plain `waitForSelector: '.MuiDataGrid-row .MuiDataGrid-cell'` is sufficient and strictly safer — it can only resolve once, when content is present. Reverts the custom rule field and its runner branch. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…lues" This reverts commit b1a2824.
The wait `.MuiDataGrid-row .MuiDataGrid-cell` matched the loading skeleton, not just real data: a skeleton cell composes *both* `.MuiDataGrid-cell` and `.MuiDataGrid-cellSkeleton`, and a skeleton row both `.MuiDataGrid-row` and `.MuiDataGrid-rowSkeleton` (additive classes, not separate ones). So the wait resolved against the skeleton and the screenshot captured the loading state — XTheming showed the skeleton and XHero (10000 rows, slower) showed an empty grid. Exclude skeleton rows: `.MuiDataGrid-row:not(.MuiDataGrid-rowSkeleton) .MuiDataGrid-cell`. This only matches a cell once real data rows have rendered (`useDemoData` sets rows and clears loading in the same tick). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
a3be9eb to
57266bf
Compare
XHero (`rowLength: 10000`) intermittently timed out the 30s grid wait in CI: `useDemoData`'s `asyncWorker` chunks row generation across `requestIdleCallback`, and under CI load those idle callbacks get starved, so the 9000-row `extrapolateSeed` pass occasionally never finished before the screenshot wait. The lighter grids (<=1000 rows) weren't affected. `asyncWorker` already has a synchronous branch, taken when `requestIdleCallback.clock` is set — the affordance mui-x gets from sinon fake timers (`test/utils/setupFakeClock`). Use sinon's `toFake` to fake *only* `requestIdleCallback`: it still gets the `.clock` tag that flips `asyncWorker` to its synchronous branch, while `Date` (handled by `fakeDateSetup`), `setTimeout`, rAF, etc. stay real. Rows are now built before first paint, eliminating both the skeleton race and the starvation timeout. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
57266bf to
f819779
Compare
Problem
#48589 added Argos coverage for the product landing-page composites, but several render non-deterministically, so their screenshots diffed on every Argos run (e.g. build 49389). Three independent causes:
XHero,XGridFullDemo,XTheminguseuseDemoDatafrom@mui/x-data-grid-generator, which generates random rows per page visit.useDemoDataloads rows asynchronously; the screenshot'saria-busygate only tracks fonts, so the grid could be captured mid-load showing its skeleton.behavior: 'smooth', captured mid-scroll at a different offset each run.Fixes
1. Deterministic grid data (
vite.config.mts) -- a Vitedefinesets__DISABLE_CHANCE_RANDOM__totrue, the token@mui/x-data-grid-generatorchecks to seed itsChanceinstances (new Chance(() => 0.5)/new Chance(42)). Same mechanism mui-x's regression bundle uses. Verified the built chunk resolves to the seeded branch, so thedefinereaches the dependency innode_modules. With a constant() => 0.5source every Chance call returns the same value, so data is identical across loads regardless of render order; theMath.randomseed-extension path (onlyXHeroatrowLength: 10000) copies from seed rows whose columns are now all identical, so it doesn't affect output either -- which is why mui-x never needed to seedMath.random.2. Wait for grid rows (
demoMeta.ts) -- awaitForSelector: '.MuiDataGrid-row .MuiDataGrid-cell'rule for the three grid composites gates the screenshot on a real data cell (the skeleton uses.MuiDataGrid-skeletonRow).3. Respect prefers-reduced-motion (
MaterialStyling.tsx) -- gate the mount scroll onmatchMedia('(prefers-reduced-motion: reduce)')('auto'when reduced), matching the existingAppLayout/components/BackToTop.tsxpattern. An explicit JSbehavior: 'smooth'isn't governed byprefers-reduced-motion(only CSSscroll-behavioris), so this both respects the preference in the live docs and -- because the regression runner emulatesreduced-motion: reduce-- makes the screenshot deterministic. Fixes the issue at the source rather than patching the scroll API in the test harness.Test plan
pnpm eslint test/regressions/,pnpm prettier --check,pnpm typescript(all 19 projects incl. docs), build (9 composites) -- all green.Chancebranch present;prefers-reduced-motioncheck present in MaterialStyling.demoMeta.test.ts(9/9); rule resolution verified (grid composites get width 1440 + waitForSelector; others width only).🤖 Generated with Claude Code