📖 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

Unit Tests
(Jest / Vitest)
Logic & Functions
Component Tests
(Playwright CT)
UI & CSS in Isolation
E2E Tests
(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:

bash
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.

typescript · playwright-ct.config.ts
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.

tsx · Button.spec.tsx
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.

tsx · Button.spec.tsx
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

tsx · ❌ Wrong focus
// ❌ 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

  1. Use it for Visual Regression. Component testing + toHaveScreenshot() is the ultimate weapon against CSS bugs.
  2. Mock external dependencies. Use Playwright's page.route() to mock API calls the component makes on mount.
  3. Don't abandon E2E. Component tests verify the parts. You still need a few E2E tests to verify the whole machine works together.
  4. 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()`, Playwright sends the serialized component over the CDP (Chrome DevTools Protocol) to a hidden Vite dev server running in the background. Vite compiles the JSX/SFC on the fly, generates a tiny HTML wrapper, and injects the result into the Playwright test page. This is why it requires Vite/Webpack—it needs a bundler to understand modern framework syntax.

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

Beginner
What is Playwright Component Testing?
Show Answer
It is a feature that allows you to mount individual UI components (React, Vue, Svelte) in isolation within a real browser environment and test them using standard Playwright APIs, without needing to run the full application.
Intermediate
How does Component Testing differ from End-to-End (E2E) testing?
Show Answer
E2E testing spins up the whole app, navigates via URL, and tests full user flows including the backend. Component testing bypasses the router and backend, directly mounting a single component in a blank page to test it in isolation. It is much faster.
Advanced
Why use Playwright Component Testing instead of Jest + Testing Library?
Show Answer
Jest uses `jsdom`, which simulates the DOM but does not compute actual CSS layout, colors, or rendering. Playwright CT runs a real browser engine, meaning it can catch CSS layout bugs, perform visual regression screenshots, and test true browser focus/scroll behaviors that jsdom cannot.
Scenario
You have a complex `UserCard` component that fetches user data on mount. How do you test the "loading" and "loaded" states in isolation?
Show Answer
I would use Playwright's `page.route()` to intercept the API call. For the "loading" state, I would delay the response (e.g., `route.fulfill` after a timeout) and assert the spinner is visible. For the "loaded" state, I would instantly fulfill the route with mock JSON data and assert the user's name renders.

🏋️ Practical Exercise

🎯 Hands-On Practice Medium

Mount and Screenshot

  1. Install `@playwright/experimental-ct-react` (or your framework's equivalent).
  2. Create a simple `Button` component in your app.
  3. Write a CT test that mounts the `Button`.
  4. Use `expect(component).toHaveScreenshot('button.png')` to capture a baseline.
  5. Change the CSS of the button (e.g., background color to red).
  6. Run the test again to see the visual regression diff.

🚀 Mini Project

🏗️ The Isolated Form

  1. Create a `LoginForm` component with email and password fields.
  2. Mount it in a Component Test.
  3. Use `page.route()` to mock the `POST /api/login` endpoint.
  4. Fill in the fields and click submit.
  5. Assert that the component displays a "Success" message when the mocked API returns 200.
  6. Assert that it displays an "Error" message when the API returns 401.

📝 Quiz

Test your understanding. Click an option to check your answer.

1. What method is used to render a component in Playwright Component Testing?
A) page.render()
B) mount()
C) component.load()
D) page.goto()
Answer: B — The `mount` fixture is injected into the test and handles the rendering.
2. What does the `mount()` method return?
A) A React element
B) A JSON object
C) A Playwright Locator
D) A Promise of booleans
Answer: C — It returns a standard Locator, allowing you to use click() and expect() on it.
3. Why is Playwright CT better at catching CSS bugs than Jest with jsdom?
A> It runs faster
B) It uses a real browser engine that computes CSS layout
C) It has better TypeScript support
D) It automatically includes Tailwind CSS
Answer: B — jsdom cannot compute flexbox, grid, or visual rendering. Playwright uses Chromium/Firefox/WebKit.
4. Can you access the internal state (e.g., React `useState`) of a mounted component?
A) Yes, via `component.getState()`
B) No, it is a black-box test. You must interact via the DOM.
C) Yes, if you use the React DevTools plugin
D) Only in debug mode
Answer: B — Playwright interacts with the rendered UI, not the internal JavaScript memory.
5. What bundler does Playwright CT use under the hood to compile the components?
A) Rollup
B) esbuild
C) Vite or Webpack
D) Babel standalone
Answer: C — It uses a Vite or Webpack dev server to compile JSX/SFCs on the fly.
6. How do you test a component that makes an API call on mount?
A) You cannot; you must use E2E for this
B) Use `page.route()` to intercept and mock the API response
C) Pass the data as a prop
D) Disable the API call in the test environment
Answer: B — Even in CT, you have full access to Playwright's network mocking capabilities.
7. Should Component Testing completely replace E2E testing?
A) Yes, it is faster and better
B) No, you still need a few E2E tests to verify the whole app works together
C) Yes, if you achieve 100% coverage
D) No, because CT cannot run in CI
Answer: B — CT tests the parts; E2E tests the whole. You need both for a robust suite.
8. Which framework is officially supported by Playwright CT packages?
A) Only React
B) React, Vue, Svelte, and Solid
C) Angular and Ember
D) Vanilla JS only
Answer: B — Playwright provides specific experimental packages for React, Vue, Svelte, and Solid.
9. What is a common use case for Playwright CT?
A) Testing database schemas
B) Visual Regression Testing (screenshots) of individual components
C) Load testing APIs
D) Testing CSS server-side rendering
Answer: B — Taking pixel-perfect screenshots of isolated components to catch CSS leaks.
10. If your component relies on a global stylesheet, what must you do?
A) Inline the CSS into the component
B) Configure the CT Vite/Webpack config to import the global styles
C) Use E2E instead
D) Playwright automatically downloads global CSS
Answer: B — You must explicitly tell the bundler to include global CSS, otherwise the component renders unstyled.

❓ 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.