← Back to microsoft/playwright
microsoft / playwright · Issue No. 42139
With v1.62, Playwright shipped a new approach to component testing built on stories and a gallery page, driven by the built-in mount fixture of plain @playwright/test. It REPLACES the @playwright/experimental-ct-react and @playwright/experimental-ct-vue packages, which are now deprecated.
The new workflow is intentionally minimal, and parts of it — the mount API in particular — are still taking their final shape. We want your feedback before we lock it in. This issue explains why we made the change, how to try it, what we already know is rough, and what we'd like to hear from you.
The experimental packages let tests write JSX inline:
const component = await mount(<Button title='Submit' onClick={() => ++clicks} />);
It looks great in a snippet, but to make that single line work, Playwright had to control your entire pipeline: scan test files for components, compile a bundle with its own copy of Vite and its own config, serve it from its own server, and marshal props and callbacks across the Node.js/browser boundary.
That design is what kept the packages experimental for years:
ctViteConfig by hand. Projects on webpack, Next.js or custom pipelines could not use their own build at all. Every framework needed its own package with its own runtime glue.These were not bugs we could fix — they were consequences of the architecture. So we inverted it.
Three small concepts replace the whole machine:
*.story.tsx files; each named export is one story.window.mount()/window.unmount() to render a story into a root element. It is a few dozen lines of framework-specific glue — and it is yours.mount fixture navigates to the gallery, mounts a story by id, and returns a Locator:import { test, expect } from '@playwright/test';
test('click should expand', async ({ mount }) => {
const component = await mount('components/Expandable/Stateful');
await component.getByRole('button').click();
await expect(component.getByTestId('expanded')).toHaveValue('true');
});
What this buys you:
@playwright/test, and mount is a documented built-in fixture. No experimental package to depend on.The gallery is application code, so Playwright does not ship it — instead, it ships the entire methodology as an agent skill. The fastest way to get set up:
npx playwright init-skills
then ask your coding agent (Claude Code, GitHub Copilot or similar):
Set up component testing using the playwright-component-testing skill.
The agent detects your framework and bundler, implements the gallery for your stack, adds a Playwright project to your config, and writes the first story and spec. It can also migrate an existing experimental-ct suite — see the migration guide.
Prefer to stay hands-on? The installed skill contains the full gallery specification with worked React and Vue examples (references/gallery-spec.md) — writing it by hand is entirely supported.
Early adopters have already sent great feedback — thank you. Here is what we know is rough today:
mount<typeof Story> type-checks props against the story, but does not tie the props to the id you passed. We are exploring a story-registry design (generated types plus declaration merging, in the spirit of TanStack Router) — this is the main reason the mount signature is not final yet.npx playwright setup-ct scaffold, and agent results vary across agents and models.mount API: string ids vs. a typed registry, the update()/unmount() surface, how props should be typed.Reply below with your experience — including "it just worked", which is signal too. The experimental-ct packages remain available while we finalize this, but all new investment is going into the gallery workflow, so now is the moment your feedback shapes the API.
Relay reads this issue against the repository's contribution signals: the files it is likely to touch, how the maintainers triage work this size, and what the first contribution would exercise.
The full analysis for this issue is still being assembled. Until then, the description above and the thread on GitHub are the most reliable context.