Component Testing — The Isolation Chamber
E2E tests are thorough but slow. Unit tests are fast but don't render real CSS. Component Testing bridges the gap: mount a single React/Vue/Svelte component in a real browser and test it instantly.
📖 The Story: The CSS Leak
A frontend team was building a complex dashboard. They had 500 Jest unit tests for their React components, and 100 Playwright E2E tests. Everything was green.
One day, a developer updated a global CSS file to use `flexbox` instead of `grid`. The Jest tests passed because Jest uses `jsdom`, a headless browser simulator that doesn't compute layout or CSS. The E2E tests passed because the layout change didn't break the functional clicks.
But in production, users complained. The "Submit" button was rendering 500 pixels wide instead of 100 pixels, completely breaking the UI. Jest couldn't see it. E2E didn't care.
The team enabled Playwright Component Testing. Because it runs in a real Chromium browser engine, it rendered the actual CSS. A simple visual assertion or screenshot comparison instantly caught the 400-pixel layout shift.
If it doesn't run in a real browser, it doesn't run in reality.
🎯 Why Component Testing?
Lightning Fast
No routers, no databases, no login screens. Mount the component and test it in milliseconds.
Real CSS Engine
Unlike jsdom, Playwright uses real browser engines to compute layout, colors, and visibility.
No Backend Needed
Test frontend components in total isolation. Mock API calls directly at the network layer.
Visual Snapshots
Easily use toHaveScreenshot() on individual components without capturing the whole page.
🌍 Real World Example
You have a `DatePicker` component. In E2E, testing every edge case (leap years, timezone boundaries, month-end rollovers) requires navigating to 10 different pages. With Component Testing, you mount the `DatePicker` once, and instantly pass it 50 different prop combinations, asserting the output each time.
🧒 Explain Like I'm 10
Imagine you are testing a car. Unit testing is testing the car's battery on a workbench. E2E testing is driving the whole car from New York to Los Angeles.
Component testing is putting just the car's radio into a dashboard mockup, turning it on, and checking if it plays music. You don't need the engine running, and you don't need to drive across the country. You just test the radio.
🎓 Professional Explanation
Playwright Component Testing allows you to mount a web component (React, Vue, Svelte, Solid, or vanilla JS) directly into a clean Playwright `Page` without navigating to a URL. Under the hood, Playwright spins up a Vite or Webpack dev server, compiles the component on the fly, and injects it into a blank HTML page.
Because it is injected into a real browser, you have access to the full Playwright API: `page.click()`, `expect(locator).toBeVisible()`, network mocking, and trace viewer.
📊 The Modern Testing Pyramid
Where Component Testing Fits
(Jest / Vitest)
Logic & Functions
(Playwright CT)
UI & CSS in Isolation
(Playwright)
Full User Flows
Component Testing replaces the "Integration Testing" middle layer with something faster and more reliable.
⚙️ Setup & Installation
Component testing requires a specific package based on your framework. For React:
npm install --save-dev @playwright/experimental-ct-react
Then, create a `playwright-ct.config.ts` file. Notice it uses `defineConfig` from the CT package, not the main Playwright package.
import { defineConfig, devices } from '@playwright/experimental-ct-react'; export default defineConfig({ testDir: './src/__tests__', use: { // Mount HTML host css ctPort: 3100, } });
🚀 Mounting a Component
Instead of `page.goto()`, you use the `mount` fixture provided by the CT runner.
import { test, expect } from '@playwright/experimental-ct-react'; import { Button } from '../components/Button'; test('button should render with correct text', async ({ mount }) => { // Mount the component. It returns a Locator! const component = await mount(<Button title="Submit" />); // Use standard Playwright assertions await expect(component).toContainText('Submit'); });
The Magic: The `mount` function returns a standard Playwright `Locator`. This means everything you've learned in this course—`click()`, `fill()`, `toHaveScreenshot()`—works exactly the same way on a mounted component as it does on a full web page.
👆 Interacting & Asserting
Let's test a scenario where clicking the button changes its text.
test('button changes text on click', async ({ mount }) => { const component = await mount(<Button title="Click Me" />); // Verify initial state await expect(component).toHaveText('Click Me'); // Interact with it await component.click(); // Verify new state await expect(component).toHaveText('Clicked!'); });
⚠️ Common Mistakes
Mistake 1: Testing Component Logic instead of Component UI
// ❌ BAD: Trying to access internal component state/methods const component = await mount(<Calculator />); await component.calculateTaxes(50000); // Error! Not a DOM method // ✅ GOOD: Interact via the UI like a real user await component.getByLabel('Income').fill('50000'); await component.getByRole('button', { name: 'Calculate' }).click();
Playwright Component Testing is a black-box test. You cannot access internal React state or methods. You must interact with the rendered DOM. If you need to test pure logic, use Jest/Vitest.
Mistake 2: Forgetting Global CSS
If your component relies on global CSS (like Tailwind or a design system), you must configure the CT Vite/Webpack config to include those stylesheets, otherwise the component will render unstyled.
✅ Best Practices
- Use it for Visual Regression. Component testing +
toHaveScreenshot()is the ultimate weapon against CSS bugs. - Mock external dependencies. Use Playwright's
page.route()to mock API calls the component makes on mount. - Don't abandon E2E. Component tests verify the parts. You still need a few E2E tests to verify the whole machine works together.
- Keep components small. If a component is too complex to mount in isolation, it's probably doing too much. Break it down.
🏗️ Senior Engineer Deep Dive
How Mounting Works Internally
When you call `mount(
jsdom vs Real Browser
Tools like Jest use `jsdom`, a JavaScript implementation of the DOM. `jsdom` can parse HTML, but it cannot compute CSS layout (flexbox, grid, z-index). Playwright CT runs the actual Chromium/Firefox/WebKit engine. If a CSS bug exists, Playwright CT will see it; jsdom will not.
💼 Interview Questions
Show Answer
Show Answer
Show Answer
Show Answer
🏋️ Practical Exercise
Mount and Screenshot
- Install `@playwright/experimental-ct-react` (or your framework's equivalent).
- Create a simple `Button` component in your app.
- Write a CT test that mounts the `Button`.
- Use `expect(component).toHaveScreenshot('button.png')` to capture a baseline.
- Change the CSS of the button (e.g., background color to red).
- Run the test again to see the visual regression diff.
🚀 Mini Project
🏗️ The Isolated Form
- Create a `LoginForm` component with email and password fields.
- Mount it in a Component Test.
- Use `page.route()` to mock the `POST /api/login` endpoint.
- Fill in the fields and click submit.
- Assert that the component displays a "Success" message when the mocked API returns 200.
- Assert that it displays an "Error" message when the API returns 401.
📝 Quiz
Test your understanding. Click an option to check your answer.
page.render()mount()component.load()page.goto()click() and expect() on it.❓ FAQ
Q: Is Playwright Component Testing production-ready?
A: It is still labeled "experimental" (hence the package name), but many enterprises use it in production. The API is stable, but minor breaking changes might occur as it approaches v1.0.
Q: Can I use Playwright fixtures in Component Testing?
A: Yes, you can extend the `test` object from the CT package exactly the same way you do in E2E testing to create custom fixtures.
📦 Summary
🎯 Key Takeaways
- Component Testing mounts UI components in isolation without needing a full app.
- It uses a real browser engine, catching CSS and layout bugs that jsdom (Jest) misses.
- Use the `mount()` fixture to render the component, which returns a Playwright Locator.
- It is a black-box test; interact via the DOM, not internal state.
- Combine with `toHaveScreenshot()` for the ultimate component visual regression setup.
- It does not replace E2E but sits perfectly between Unit and E2E tests.
