Skip to content

[test] Cover docs landing-page composites with Argos#48589

Open
LukasTy wants to merge 11 commits into
mui:masterfrom
LukasTy:claude/cover-landing-composites-argos
Open

[test] Cover docs landing-page composites with Argos#48589
LukasTy wants to merge 11 commits into
mui:masterfrom
LukasTy:claude/cover-landing-composites-argos

Conversation

@LukasTy
Copy link
Copy Markdown
Member

@LukasTy LukasTy commented May 28, 2026

Summary

Adds Argos screenshot coverage for the bespoke composites that assemble the product landing pages (/material-ui, /x). They live under docs/src/components/product*/*.tsx and were uncovered by the fixture loader, which globs docs/data/** only.

Coverage is an explicit allowlist of 9 composites that render real MUI / MUI X components in a composed layout — the ones where a CSS/theme regression would actually show. The other ~18 product* composites are marketing chrome (heroes that are mostly links/images, pricing tables, Figma-kit promos, template thumbnails) and are intentionally left out.

Suite Composites
docs-product-material MaterialHero, MaterialStyling, MaterialTheming
docs-product-x XHero, XGridFullDemo, XDateRangeDemo, XChartsDemo, XTreeViewDemo, XTheming

For MUI X, each product is covered by its isolated demo rather than via the XComponents switcher composite (which renders these same demos behind tabs) — that drops the tab chrome and the duplicate XDataGrid section, giving one clean screenshot per product (grid, pickers, charts, tree view) plus the hero and the custom-theme grid.

Ported from mui/mui-x#22283, with two material-ui-specific additions (stub layer + date freeze, below).

How it works

  • next/* stub layer (test/regressions/stubs/, aliased in vite.config.mts). Every kept composite imports from the @mui/internal-core-docs/AppLayout barrel, whose siblings (AppLayoutHead/AppLayoutDocs/AppSearch) import next/router, next/link, next/head; MaterialHero also imports Link directly. Without the stubs the real Next modules get bundled and crash at runtime outside a Next router context (verified: removing them swaps the stubs for the real modules and the bundle balloons). The stubs return safe defaults so <Link> renders a plain anchor (visually equivalent for Argos).
  • MarketingWrapper supplies the docs branding theme (light) so composites can read branding tokens (palette.primaryDark, palette.gradients, applyDarkStyles). It routes through a .d.ts/.js shim (brandingThemeShim) so the nodenext test typecheck doesn't choke on the theme file's @mui/material/* module augmentations.
  • Per-product viewports (demoMeta.ts): product*/** → 1280×800, productX/** → 1440×900. The runner applies page.setViewportSize per route (important now that pages come from a pool and may have been resized by a prior test).
  • Fixed clock (fakeDateSetup.ts + timezoneId: 'UTC'). XHero and XDateRangeDemo compute picker defaults from dayjs() at module scope, which runs when the eager fixture glob loads — too early for a render-time mock. Follows mui/base-ui#4337 + #4370: the Date override (Reflect.construct, preserves the native prototype) lives in fakeDateSetup.ts, imported on index.jsx's first line; the eager globs are extracted into fixtures.js (imported after it) because eager globs prepend their resolved imports to the host module, which would otherwise evaluate the demos' module-scope dayjs() before the clock is installed. The page is pinned to UTC so the frozen instant renders identically across CI machines. (Note: this is not mui-x's mechanism — mui-x uses sinon useFakeTimers.)

Why

These composites combine many components into marketing-style layouts and regress easily (CSS bleed, theme tweaks, internal style changes) without anyone noticing until the page is opened — and they sit outside the docs/data/** glob, so they had no coverage.

Test plan

  • pnpm test:regressions:build — bundle builds clean; exactly the 9 allowlisted composites are included.
  • pnpm eslint test/regressions/, pnpm prettier --check, pnpm typescript (all 19 projects) — all green.
  • demoMeta.test.ts (9/9) — incl. the composite route regex.
  • Date freeze verified against dayjs: dayjs() pins to the fixed instant, dayjs().date(10)/.add(28,'day') are deterministic, explicit new Date(arg), Date.UTC, and instanceof Date still work.
  • CI Argos baselines the 9 screenshots — the runtime render check. Local Playwright/preview browsers were unavailable on the dev machine, so CI produces the first baselines. Review them (esp. the premium MUI X license watermark on XHero/grids, the per-product viewport layout, and the frozen calendars in XHero/XDateRangeDemo) before accepting.

🤖 Generated with Claude Code

Add Argos screenshot coverage for the ~27 bespoke composites under
`docs/src/components/product*/*.tsx` that assemble the product landing
pages (`/material-ui`, `/x`, `/core`, `/design-kits`, `/templates`). The
existing fixture glob walks `docs/data/**` only, so these composites
have historically regressed silently until someone opened the page.

Ports the approach from mui/mui-x#22283 with one material-ui-specific
addition: a tiny `next/*` stub layer in the regression bundle's Vite
config. Nearly every product composite directly or transitively
imports `Link` from `@mui/internal-core-docs/Link`, which calls
`useRouter()` from `next/router`; without the stub, render either
crashes at runtime or fails to bundle (the `AppLayout` barrel pulls
`next/router` in via `AppLayoutHead`/`AppLayoutDocs`/`AppSearch`).

- `stubs/{next-router,next-link,next-head}.{ts,tsx}` (~30 lines total):
  `useRouter` returns safe defaults, `next/link` renders a plain anchor
  (visually equivalent for Argos), `next/head` is `() => null`.
- `vite.config.mts`: alias `next/router`, `next/link`, `next/head` to
  the stubs.
- `MarketingWrapper.tsx`: wraps composite fixtures with
  `BrandingProvider` so they can read docs branding tokens
  (`palette.primaryDark`, `palette.gradients`, `applyDarkStyles`).
- `index.jsx`: second `import.meta.glob` for
  `docs/src/components/product*/[A-Z]*.tsx`; PascalCase → kebab-case
  for the suite name (`productDesignKit` → suite
  `docs-product-design-kit`); composites get wrapped in
  `MarketingWrapper`.
- `demoMeta.ts`: adds a `viewport` field on `ScreenshotRule`, exports
  `DEFAULT_VIEWPORT`, extends `parseRoute` to recognise composite
  routes and round-trip the kebab-case back to a PascalCase directory,
  and appends two viewport rules — `product*/**` → 1280×800,
  `productX/**` → 1440×900 (last-match-wins).
- `index.test.js`: per-route `page.setViewportSize(rule?.viewport ??
  DEFAULT_VIEWPORT)` before each fixture render.
- `demoMeta.test.ts`: new cases for the composite route regex,
  including the kebab-case → PascalCase round-trip.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@code-infra-dashboard
Copy link
Copy Markdown

code-infra-dashboard Bot commented May 28, 2026

Deploy preview

https://deploy-preview-48589--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 marked this pull request as draft May 28, 2026 14:50
@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. scope: code-infra Involves the code-infra product (https://www.notion.so/mui-org/5562c14178aa42af97bc1fa5114000cd). labels May 28, 2026
LukasTy and others added 4 commits May 28, 2026 17:52
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- `stubs/next-link.tsx`: `NextLinkStubProps` was extending
  `AnchorHTMLAttributes<HTMLAnchorElement>` but widening `href` from
  `string | undefined` to `string | { pathname?: string }`. Omit
  `href` from the base before redeclaring it.
- `MarketingWrapper.tsx`: importing `BrandingProvider` from
  `@mui/internal-core-docs/branding` pulled in the package's barrel,
  whose sibling modules use extensionless relative imports
  (`from './brandingTheme'`). That's fine under the package's own
  `moduleResolution: 'bundler'`, but the `test/tsconfig.json` typecheck
  runs under `nodenext` and surfaced them as TS2835. Deep-import
  `brandingLightTheme` from the leaf module (which has zero relative
  imports) and inline the minimal light-mode `ThemeProvider` wrapper.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Caught by CI's `pnpm dedupe --check`. The `eslint-plugin-import`
peer info had a nested `eslint-import-resolver-typescript` resolver
specifier that pnpm dedupe simplifies to the flat form.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
CI's `test_types` job (which runs `tsc -p test/tsconfig.json` under
`moduleResolution: 'nodenext'`) was failing to resolve
`@mui/material/themeCssVarsAugmentation` and friends when typechecking
walked into `packages-internal/core-docs/src/branding/brandingTheme.ts`
via `MarketingWrapper.tsx`'s deep import. The augmentations resolve
fine in the package's own typecheck (`bundler` resolution) and in the
Vite bundle, but not in `test/`'s nodenext context against the test
package's node_modules layout.

Workaround: route the import through a local `brandingThemeShim` —
the `.d.ts` declares the theme as a `@mui/material/styles` `Theme`,
and the `.js` does the runtime re-export. TS picks the `.d.ts` and
never walks into the augmentation source; Vite picks the `.js` and
resolves it normally.

Also added `regressions/brandingThemeShim.js` to `test/tsconfig.json`'s
exclude as belt-and-suspenders so TS doesn't accidentally include the
runtime shim and start chasing imports through it.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@github-actions github-actions Bot added the PR: out-of-date The pull request has merge conflicts and can't be merged. label May 29, 2026
…ing-composites-argos

# Conflicts:
#	test/regressions/index.test.js
@github-actions github-actions Bot removed the PR: out-of-date The pull request has merge conflicts and can't be merged. label May 29, 2026
LukasTy and others added 5 commits May 29, 2026 17:02
The broad `product*/[A-Z]*.tsx` glob pulled in all 27 composites, but
most are marketing chrome — heroes that are mostly links/images,
pricing tables, Figma-kit promos, template thumbnails — with no real
component integration for Argos to guard. Replace it with an explicit
allowlist of the 9 composites that render real MUI / MUI X components
in a composed layout, where a CSS/theme regression would actually show.

Kept:
- productMaterial: MaterialHero, MaterialStyling, MaterialTheming
- productX: XHero, XGridFullDemo, XDateRangeDemo, XChartsDemo,
  XTreeViewDemo, XTheming

For MUI X, each product is covered by its isolated demo rather than via
the `XComponents` switcher composite (which renders these same demos
behind tabs) — that drops the tab chrome and the duplicate grid section
(`XDataGrid`), giving one clean screenshot per product.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
XHero and XDateRangeDemo build their picker default values from
`dayjs()` at *module scope* (e.g. `const startDate = dayjs().date(10)`),
which runs when the eager fixture glob loads the bundle — before any
render-time or per-test mock could intervene. Without a fixed clock the
calendars (and any date columns in the grid demos) would shift every
run and churn the Argos baseline.

Install a fixed `Date` via an inline script in `index.html`, ahead of
the module bundle, so it is in place before any composite module
evaluates. Native `new Date(arg)`, `Date.parse`, `Date.UTC` and
`instanceof Date` keep working; only the "now" reading is pinned.
Mirrors the fixed-date approach used by mui-x's regression bundle.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The previous inline `Date` override was hand-rolled and I incorrectly
described it as mirroring mui-x (mui-x uses sinon `useFakeTimers`; this
bundle does not). Replace the shim with the reviewed approach from
mui/base-ui#4337: `Reflect.construct` preserves the native
`Date.prototype` (timezone-aware date libraries enumerate its own
methods) and forwards `new.target`.

Also pin `timezoneId: 'UTC'` on the playwright page — without it the
frozen instant would render in the CI machine's local timezone and
still churn the baseline. This was missing before.

Kept as an inline classic <script> (rather than base-ui's module +
fixture-extraction in mui#4370): a classic script runs synchronously
before the deferred module bundle, so it beats the eager-glob import
hoisting that mui#4370 worked around — without refactoring the glob
loading that drives the entire VRT suite.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
With coverage narrowed to `productMaterial` and `productX`, the
kebab-case <-> PascalCase round-trip (added for multi-word products
like `productDesignKit`) is dead code. Replace it with a plain
lower-case / re-capitalize of the single-word product segment in both
`index.jsx` (suite name) and `demoMeta.ts` (`parseRoute`), and drop the
now-irrelevant multi-segment test case.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Follows mui/base-ui#4337 + mui#4370 fully, instead of the earlier inline
`<script>` shortcut:

- `fakeDateSetup.ts` holds the `Reflect.construct`-based `Date` override
  (was inline in `index.html`).
- `fixtures.js` hosts the three eager `import.meta.glob` calls, extracted
  from `index.jsx`. Eager globs *prepend* their resolved imports to the
  top of their host module (base-ui#4370), so if they stayed in
  `index.jsx` the demo modules' top-level `dayjs()` would evaluate before
  `index.jsx`'s first import could install the clock. Hosting them in a
  separate module imported *after* `./fakeDateSetup` guarantees the
  override is in place first.
- `index.jsx` imports `./fakeDateSetup` on its first line, then
  `./fixtures`; the inline `<script>` is removed from `index.html`.

Verified the ordering: in the built entry chunk the `globalThis.Date`
install lands at byte ~2k while the module-scope `dayjs().date(10)` /
`.add(28)` from XHero/XDateRangeDemo land at ~3.85M, and an import-order
simulation freezes those module-scope dates to 2025-06-10 / 2025-07-13.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@LukasTy LukasTy marked this pull request as ready for review June 2, 2026 12:35
@LukasTy LukasTy requested review from a team and Copilot June 2, 2026 12:35
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds Argos visual-regression coverage for selected docs product landing-page composites (/material-ui, /x) by extending the regression fixture loader beyond docs/data/**, and ensuring these composites can render safely outside a Next.js runtime.

Changes:

  • Add an explicit allowlist of 9 product-page composites to the regression fixtures and route parsing/config.
  • Introduce a safe next/* stub layer and a branding-theme wrapper/shim so composites can render in the Vite regression bundle.
  • Stabilize screenshots by applying per-route viewports and a deterministic Date setup + pinned timezone in Playwright.

Reviewed changes

Copilot reviewed 13 out of 14 changed files in this pull request and generated 1 comment.

Show a summary per file
File Description
test/tsconfig.json Excludes the runtime-only branding theme shim JS from nodenext typechecking.
test/regressions/vite.config.mts Adds next/* module alias stubs alongside existing monorepo aliases.
test/regressions/stubs/next-router.ts Provides a stubbed router for non-Next rendering.
test/regressions/stubs/next-link.tsx Stubs next/link as a plain anchor for screenshots.
test/regressions/stubs/next-head.tsx Stubs next/head to avoid module-scope crashes.
test/regressions/MarketingWrapper.tsx Wraps composites with the docs branding light theme.
test/regressions/index.test.js Applies per-route viewport sizing and pins timezone for deterministic output.
test/regressions/index.jsx Installs fake date first, imports fixtures from a separate module, and wraps composites in MarketingWrapper.
test/regressions/fixtures.js Centralizes eager globs, adds allowlisted composite fixtures, and marks composites for special wrapping.
test/regressions/fakeDateSetup.ts Overrides Date so date-dependent composites render deterministically.
test/regressions/demoMeta.ts Adds viewport rules for product composites and extends route parsing for /docs-product-*.
test/regressions/demoMeta.test.ts Adds coverage for composite route parsing.
test/regressions/brandingThemeShim.js Runtime re-export of the docs branding theme for the Vite bundle.
test/regressions/brandingThemeShim.d.ts Declaration-only shim for typechecking consumers of the branding theme.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +26 to +30
export function useRouter() {
return router;
}

export default { useRouter };
// file declares augmentations to `@mui/material/styles` and friends, which
// resolve fine in the package's own `bundler`-resolution typecheck and in
// the Vite build, but fail under `test/tsconfig.json`'s `nodenext`
// resolution (`@mui/material/themeCssVarsAugmentation` can't be found in
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Sorry, but no. This file shouldn't be necessary, if vitest can resolve like a bundler then we should configure typescript to resolve like a bundler.

// X composites (large grids, dense charts) want a wider canvas to match
// their live-docs desktop layout. Last-match-wins so this overrides the
// broader product*/** viewport above.
{ test: 'docs/src/components/productX/**', viewport: { width: 1440, height: 900 } },
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

iirc playwright has a fullPage option for its screenshots. Something we can leverage over hardcoding the height?

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

Labels

scope: code-infra Involves the code-infra product (https://www.notion.so/mui-org/5562c14178aa42af97bc1fa5114000cd). 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.

3 participants