Skip to content

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

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

[test] Cover docs landing-page composites with Argos#48589
LukasTy wants to merge 10 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 (index.html inline script + 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. The Date override follows mui/base-ui#4337 (Reflect.construct, preserves the native prototype) and is delivered as an inline classic <script> so it runs before the deferred module bundle — sidestepping the eager-glob import hoisting that base-ui#4370 had to work around, without refactoring the suite-wide glob loading. 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.

Follow-ups (not in this PR)

  • Backport the next/* stub pattern to mui-x to retire their ChartFeaturesGrid/ChartComponentsGrid/ChartComponentsUpcoming exclusions.
  • Broader landing-page coverage (home/, showcase/) is now mostly mechanical with the stub + wrapper in place.

🤖 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 4 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>
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.

1 participant