From 1f39349a8504267bb6945283e0ce2ba884962285 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Tue, 21 Jul 2026 14:27:47 -0700 Subject: [PATCH] feat(ct): type the mount fixture via story template argument Make the mount fixture generic over the story: mount() type-checks props and update() against the story component signature. Works for React function/class components and Vue components; a plain props type can be passed directly for other frameworks. Document mount officially in class-fixtures.md. --- docs/src/test-api/class-fixtures.md | 45 + docs/src/test-components-js.md | 902 ++++-------------- packages/html-reporter/src/chip.spec.ts | 12 +- packages/html-reporter/src/headerView.spec.ts | 6 +- .../html-reporter/src/testCaseView.spec.ts | 16 +- .../playwright-component-testing/SKILL.md | 10 + .../references/react.md | 17 + .../references/vue.md | 23 + packages/playwright/src/index.ts | 4 +- packages/playwright/types/test.d.ts | 52 +- .../src/components/codeMirrorWrapper.spec.ts | 16 +- .../web/src/components/expandable.spec.ts | 12 +- packages/web/src/components/splitView.spec.ts | 12 +- packages/web/src/shared/imageDiffView.spec.ts | 6 +- utils/generate_types/index.js | 1 - utils/generate_types/overrides-test.d.ts | 11 +- 16 files changed, 406 insertions(+), 739 deletions(-) diff --git a/docs/src/test-api/class-fixtures.md b/docs/src/test-api/class-fixtures.md index 0c08b19da3b2a..96c4926982386 100644 --- a/docs/src/test-api/class-fixtures.md +++ b/docs/src/test-api/class-fixtures.md @@ -69,6 +69,51 @@ test('example test', async ({ page, context }) => { }); ``` +## method: Fixtures.mount +* since: v1.62 +- returns: <[Locator]> + +Mounts a component story and returns a [Locator] pointing to the root element the story was rendered into. Scope your queries from the returned locator: `component.getByRole('button')`, not `page.getByRole('button')`. + +A **story** is a small wrapper component that embeds the component under test in one specific scenario: hard-coded props, mock data, providers, recorded callbacks. Stories are rendered by a **gallery** page that you implement and serve at [`property: TestOptions.baseURL`]. The gallery exposes `window.mount(params)` and `window.unmount()` functions that render a story into its root element. Each call to [`method: Fixtures.mount`] navigates to [`property: TestOptions.baseURL`] and calls `window.mount()` with the story id and props, so tests are fully isolated from each other. + +**Usage** + +```js +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'); +}); +``` + +Pass the story type as a template argument to type-check the props: + +```js +import type { WithTitle } from './Button.story'; + +test('renders the title', async ({ mount }) => { + const component = await mount('Button/WithTitle', { title: 'Hello' }); + await expect(component).toContainText('Hello'); +}); +``` + +The returned locator is augmented with two methods: +* `update(props)` - re-renders the same story with new props without remounting, preserving component state; +* `unmount()` - unmounts the story. + +### param: Fixtures.mount.storyId +* since: v1.62 +- `storyId` <[string]> + +Identifier of the story to mount, as resolved by the gallery page. Conventionally, the story file path plus the exported story name, for example `'components/Button/Primary'`. + +### param: Fixtures.mount.props +* since: v1.62 +- `props` ?<[Object]> + +Optional plain, serializable props passed to the story. + ## property: Fixtures.page * since: v1.10 - type: <[Page]> diff --git a/docs/src/test-components-js.md b/docs/src/test-components-js.md index 34fa303ab09da..8d411a66ffa84 100644 --- a/docs/src/test-components-js.md +++ b/docs/src/test-components-js.md @@ -1,351 +1,158 @@ --- id: test-components -title: "Components (experimental)" +title: "Component testing" --- -import LiteYouTube from '@site/src/components/LiteYouTube'; - ## Introduction -Playwright Test can now test your components. - - - -## Example - -Here is what a typical component test looks like: +Playwright Test can test the components of your web application in isolation. A component test is a regular Playwright end-to-end test that runs against a small **story gallery** page served by your own dev server. There is no dedicated component-testing runtime, no bundler integration and no extra npm packages — the built-in [`method: Fixtures.mount`] fixture of `@playwright/test` drives it all. ```js -test('event should work', async ({ mount }) => { - let clicked = false; - - // Mount a component. Returns locator pointing to the component. - const component = await mount( - - ); +import { test, expect } from '@playwright/test'; - // As with any Playwright test, assert locator text. - await expect(component).toContainText('Submit'); - - // Perform locator click. This will trigger the event. - await component.click(); - - // Assert that respective events have been fired. - expect(clicked).toBeTruthy(); +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'); }); ``` -## How to get started - -Adding Playwright Test to an existing project is easy. Below are the steps to enable Playwright Test for a React or Vue project. - -### Step 1: Install Playwright Test for components for your respective framework - - - - -```bash -npm init playwright@latest -- --ct -``` - - +Tests run in Node.js while components run in a real browser: real clicks are triggered, real layout is executed, visual regression is possible. At the same time, tests get everything Playwright Test offers: parallelism, parametrization, retries and post-mortem tracing. - +:::note +This guide replaces the experimental `@playwright/experimental-ct-react` and `@playwright/experimental-ct-vue` packages. If you are using them today, see the [migration guide](#migration-from-the-experimental-packages) below. +::: -```bash -yarn create playwright --ct -``` +## Why a framework-agnostic approach - +The `@playwright/experimental-ct-*` packages let tests write JSX inline — `mount(