From 063800a593d6b9e244d1246e2c6e2deb39c937ba Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Fri, 10 Jul 2026 15:01:25 -0700 Subject: [PATCH 1/2] feat(skills): add component-testing agent skill Adds a playwright-component-testing skill shipped in the playwright package, replacing @playwright/experimental-ct-* with plain e2e tests against a story gallery: stories colocated with components, a gallery page served by the app's dev server, and a `mount(storyId)` fixture returning a locator. Installed via `npx playwright init-skills`. Skill templates live in top-level packages/skills, outside of package sources, so they need no check-deps exception; they are copied into playwright/lib/skills at build time. Includes React and Vue templates, a migration guide, and two example projects under tests/components that instantiate the templates byte-for-byte (enforced by a sync test). --- docs/src/test-components-js.md | 4 + eslint.config.mjs | 1 + packages/playwright/src/program.ts | 13 ++- packages/skills/component-testing/SKILL.md | 66 +++++++++++ .../component-testing/references/migration.md | 66 +++++++++++ .../component-testing/references/patterns.md | 100 ++++++++++++++++ .../component-testing/references/react.md | 52 +++++++++ .../component-testing/references/vue.md | 57 +++++++++ .../templates/common/fixtures.ts | 31 +++++ .../templates/common/vite.config.ts | 18 +++ .../templates/react/Button.story.tsx | 19 +++ .../templates/react/button.spec.ts | 19 +++ .../templates/react/index.html | 13 +++ .../templates/react/main.tsx | 108 ++++++++++++++++++ .../templates/vue/Button.story.ts | 21 ++++ .../templates/vue/button.spec.ts | 19 +++ .../templates/vue/index.html | 13 +++ .../component-testing/templates/vue/main.ts | 99 ++++++++++++++++ .../components/gallery-react-vite/.gitignore | 6 + .../gallery-react-vite/package.json | 21 ++++ .../gallery-react-vite/playwright.config.ts | 47 ++++++++ .../gallery-react-vite/playwright/fixtures.ts | 31 +++++ .../playwright/gallery/index.html | 13 +++ .../playwright/gallery/main.tsx | 108 ++++++++++++++++++ .../src/components/Button.story.tsx | 19 +++ .../src/components/Button.tsx | 7 ++ .../src/components/UserCard.story.tsx | 7 ++ .../src/components/UserCard.tsx | 18 +++ .../tests/components/button.spec.ts | 19 +++ .../tests/components/gallery.spec.ts | 17 +++ .../tests/components/user-card.spec.ts | 13 +++ .../gallery-react-vite/tsconfig.json | 15 +++ .../gallery-react-vite/vite.config.ts | 7 ++ tests/components/gallery-vue-vite/.gitignore | 6 + tests/components/gallery-vue-vite/env.d.ts | 1 + .../components/gallery-vue-vite/package.json | 20 ++++ .../gallery-vue-vite/playwright.config.ts | 47 ++++++++ .../gallery-vue-vite/playwright/fixtures.ts | 31 +++++ .../playwright/gallery/index.html | 13 +++ .../playwright/gallery/main.ts | 99 ++++++++++++++++ .../src/components/Button.story.ts | 21 ++++ .../src/components/Button.vue | 8 ++ .../src/components/Card.story.vue | 10 ++ .../src/components/UserCard.story.ts | 12 ++ .../src/components/UserCard.vue | 15 +++ .../tests/components/button.spec.ts | 19 +++ .../tests/components/gallery.spec.ts | 23 ++++ .../tests/components/user-card.spec.ts | 13 +++ .../components/gallery-vue-vite/tsconfig.json | 7 ++ .../gallery-vue-vite/vite.config.ts | 7 ++ tests/components/test-all.spec.js | 20 ++++ tsconfig.json | 1 + utils/build/build.js | 6 + 53 files changed, 1443 insertions(+), 3 deletions(-) create mode 100644 packages/skills/component-testing/SKILL.md create mode 100644 packages/skills/component-testing/references/migration.md create mode 100644 packages/skills/component-testing/references/patterns.md create mode 100644 packages/skills/component-testing/references/react.md create mode 100644 packages/skills/component-testing/references/vue.md create mode 100644 packages/skills/component-testing/templates/common/fixtures.ts create mode 100644 packages/skills/component-testing/templates/common/vite.config.ts create mode 100644 packages/skills/component-testing/templates/react/Button.story.tsx create mode 100644 packages/skills/component-testing/templates/react/button.spec.ts create mode 100644 packages/skills/component-testing/templates/react/index.html create mode 100644 packages/skills/component-testing/templates/react/main.tsx create mode 100644 packages/skills/component-testing/templates/vue/Button.story.ts create mode 100644 packages/skills/component-testing/templates/vue/button.spec.ts create mode 100644 packages/skills/component-testing/templates/vue/index.html create mode 100644 packages/skills/component-testing/templates/vue/main.ts create mode 100644 tests/components/gallery-react-vite/.gitignore create mode 100644 tests/components/gallery-react-vite/package.json create mode 100644 tests/components/gallery-react-vite/playwright.config.ts create mode 100644 tests/components/gallery-react-vite/playwright/fixtures.ts create mode 100644 tests/components/gallery-react-vite/playwright/gallery/index.html create mode 100644 tests/components/gallery-react-vite/playwright/gallery/main.tsx create mode 100644 tests/components/gallery-react-vite/src/components/Button.story.tsx create mode 100644 tests/components/gallery-react-vite/src/components/Button.tsx create mode 100644 tests/components/gallery-react-vite/src/components/UserCard.story.tsx create mode 100644 tests/components/gallery-react-vite/src/components/UserCard.tsx create mode 100644 tests/components/gallery-react-vite/tests/components/button.spec.ts create mode 100644 tests/components/gallery-react-vite/tests/components/gallery.spec.ts create mode 100644 tests/components/gallery-react-vite/tests/components/user-card.spec.ts create mode 100644 tests/components/gallery-react-vite/tsconfig.json create mode 100644 tests/components/gallery-react-vite/vite.config.ts create mode 100644 tests/components/gallery-vue-vite/.gitignore create mode 100644 tests/components/gallery-vue-vite/env.d.ts create mode 100644 tests/components/gallery-vue-vite/package.json create mode 100644 tests/components/gallery-vue-vite/playwright.config.ts create mode 100644 tests/components/gallery-vue-vite/playwright/fixtures.ts create mode 100644 tests/components/gallery-vue-vite/playwright/gallery/index.html create mode 100644 tests/components/gallery-vue-vite/playwright/gallery/main.ts create mode 100644 tests/components/gallery-vue-vite/src/components/Button.story.ts create mode 100644 tests/components/gallery-vue-vite/src/components/Button.vue create mode 100644 tests/components/gallery-vue-vite/src/components/Card.story.vue create mode 100644 tests/components/gallery-vue-vite/src/components/UserCard.story.ts create mode 100644 tests/components/gallery-vue-vite/src/components/UserCard.vue create mode 100644 tests/components/gallery-vue-vite/tests/components/button.spec.ts create mode 100644 tests/components/gallery-vue-vite/tests/components/gallery.spec.ts create mode 100644 tests/components/gallery-vue-vite/tests/components/user-card.spec.ts create mode 100644 tests/components/gallery-vue-vite/tsconfig.json create mode 100644 tests/components/gallery-vue-vite/vite.config.ts diff --git a/docs/src/test-components-js.md b/docs/src/test-components-js.md index 34fa303ab09da..af7be62500c94 100644 --- a/docs/src/test-components-js.md +++ b/docs/src/test-components-js.md @@ -7,6 +7,10 @@ import LiteYouTube from '@site/src/components/LiteYouTube'; ## Introduction +:::warning[Deprecated] +The `@playwright/experimental-ct-react` and `@playwright/experimental-ct-vue` packages are deprecated. We recommend testing components with regular Playwright tests against a small story gallery in your app. Run `npx playwright init-skills` to install the `playwright-component-testing` skill that sets this up for you, or ask your coding agent to use it. +::: + Playwright Test can now test your components. ', 'Agentic loop provider'); option.choices(['claude', 'generic']); option.default('generic'); @@ -195,7 +198,11 @@ function addInitSkillsCommand(program: Command) { command.action(async opts => { // Claude Code only reads skills from `.claude/skills`, every other agent reads // them from the universal `.agents/skills` folder. - await tools.initWorkspace(opts.loop === 'claude' ? 'claude' : 'agents'); + const target = opts.loop === 'claude' ? 'claude' : 'agents'; + await tools.initWorkspace(target); + const skillDestDir = path.join(process.cwd(), `.${target}`, 'skills', 'playwright-component-testing'); + await fs.promises.cp(libPath('skills', 'component-testing'), skillDestDir, { recursive: true }); + console.log(`✅ Skills installed to \`${path.relative(process.cwd(), skillDestDir)}\`.`); }); } diff --git a/packages/skills/component-testing/SKILL.md b/packages/skills/component-testing/SKILL.md new file mode 100644 index 0000000000000..67daf9face823 --- /dev/null +++ b/packages/skills/component-testing/SKILL.md @@ -0,0 +1,66 @@ +--- +name: playwright-component-testing +description: Set up component testing with Playwright using a story gallery — scaffold stories, a gallery dev page and a mount fixture, no dedicated component-testing runtime. Use when asked to test React or Vue components in isolation with Playwright, or to migrate off @playwright/experimental-ct-react / -vue. +--- + +# Component Testing with Playwright + +Test components with regular Playwright e2e tests against a small **story gallery** page hosted by the app's own dev server. No extra test runner, bundler integration or npm packages are required. + +## Concept + +- A **story** is a tiny wrapper component that embeds the component under test in one specific scenario: hard-coded props, mock data, providers, recorded callbacks. Stories live next to the component in `*.story.tsx` (or `.ts`/`.jsx`/`.js`/`.vue`) files; each named export is one story. +- The **gallery** is a single page (`playwright/gallery/index.html` + `main.tsx`) that discovers all story files with `import.meta.glob` and renders the story named by the `?story=` URL parameter. Without the parameter it renders a browsable index of all stories. +- Tests are plain Playwright tests. A **`mount` fixture** navigates to a story's URL, waits for it to render, and returns a `Locator` for the story root — so specs read just like the old component tests. + +Everything the component needs must be set up *inside the story* (it runs in the browser); everything the test asserts must be observable *through the page* (DOM, URL, network). There is no marshalling of props or callbacks between test and browser. + +## Setup workflow + +1. **Detect the framework and bundler.** React vs Vue decides which templates to use. Then: + - **App runs on Vite** (has `vite.config.*`): the gallery is served by the existing dev server at `/playwright/gallery/index.html` — Vite serves any `.html` file under the project root, the app's plugins/aliases/CSS apply automatically, and `vite build` ignores it. No extra server needed. + - **Anything else** (Next.js, webpack, no dev server): copy `templates/common/vite.config.ts` to `playwright/vite.config.ts` — a standalone Vite server whose root is the gallery directory. Requires `vite` and the framework plugin as devDependencies. +2. **Copy the gallery**: `templates//index.html` and `main.tsx` (Vue: `main.ts`) into `/playwright/gallery/`. Adjust the `import.meta.glob` pattern in `main.*` if components do not live under `src/`. Import the app's global CSS into `index.html` or `main.*` the same way the app's own entry does. +3. **Copy the fixture**: `templates/common/fixtures.ts` into `/playwright/fixtures.ts`. If using the standalone gallery server, change `galleryUrl` to `'/'`. +4. **Configure Playwright** — add to `playwright.config.ts`: + + ```ts + projects: [ + { + name: 'components', + testDir: './tests/components', + use: { ...devices['Desktop Chrome'], baseURL: 'http://localhost:5173' }, + }, + ], + webServer: { + command: 'npm run dev', // or: npx vite --config playwright/vite.config.ts + url: 'http://localhost:5173/playwright/gallery/index.html', // standalone server: http://localhost:3100/ + reuseExistingServer: !process.env.CI, + }, + ``` + + Match the port to the dev server. If the config already has projects/webServer, merge instead of replacing. +5. **Write a first story** next to an existing component, modeled on `templates//Button.story.*`. +6. **Write a first spec**, modeled on `templates/react/button.spec.ts`, importing `test`/`expect` from the fixtures file. +7. **Run**: `npx playwright test --project=components`. Open `http://localhost:5173/playwright/gallery/index.html` in a browser to eyeball all stories. + +## Conventions + +- Story id: path under `src/` without the `.story.*` extension, plus the export name — `src/components/Button.story.tsx` export `Primary` → `components/Button/Primary`. Any unique suffix works too: `mount('Button/Primary')`. A `.story.vue` single-file component is one story, addressable by its path alone (its `default` export). +- One export per scenario. Prefer a new story export over parameterizing an existing one — stories are greppable, reviewable documentation of component states. +- Wire callbacks inside the story and record their effect into the DOM (e.g. ``), then assert with locators. See `references/patterns.md`. +- Each `mount()` performs a fresh navigation, so tests are fully isolated; multiple mounts in one test are fine. + +## Decision points + +- **Monorepos / non-`src` layouts**: change the glob in `main.*` and the `baseId` prefix stripping to match. +- **Global providers** (theme, i18n, store, router): create a shared `decorator` helper next to the gallery and wrap components in stories; see `references/react.md` / `references/vue.md`. +- **Network**: mock with `page.route()` in the test, or start MSW inside a story; see `references/patterns.md`. +- **Viewport, color scheme, locale**: standard Playwright — `test.use({ viewport, colorScheme, locale })`. + +## References + +- `references/react.md` — React walkthrough: providers, StrictMode, CSS. +- `references/vue.md` — Vue walkthrough: `.story.ts` and `.story.vue` stories, plugins. +- `references/patterns.md` — callbacks and events, per-test args, network mocking, visual testing. +- `references/migration.md` — migrating from `@playwright/experimental-ct-react` / `-vue`. diff --git a/packages/skills/component-testing/references/migration.md b/packages/skills/component-testing/references/migration.md new file mode 100644 index 0000000000000..0521848c4b794 --- /dev/null +++ b/packages/skills/component-testing/references/migration.md @@ -0,0 +1,66 @@ +# Migrating from @playwright/experimental-ct-react / -vue + +The CT packages mounted JSX from the test over a serialization boundary. The gallery pattern moves that setup into story files that run natively in the browser. Migration is mechanical: every distinct `mount(...)` call site becomes a story export. + +## Concept mapping + +| `@playwright/experimental-ct-*` | Gallery pattern | +|---|---| +| `mount(