Component Testing with Playwright
Playwright’s component testing lets you mount and test React, Vue, and Svelte components in a real browser — without a full app. Faster than E2E. More realistic than JSDOM. The sweet spot for complex UI components.
1 The Hook
A Wellington SaaS team has a complex date range picker component used in 12 different places across their app. E2E tests for date scenarios take 45 seconds each: full app load, auth, navigation to the page that contains the picker, then the interaction itself.
They have 8 edge cases to cover: timezone crossing, daylight saving, date order validation, min/max bounds, and four more. That’s 6 minutes of E2E test time for one component.
With Playwright component testing, each scenario runs in 800ms. The component mounts directly in the browser without the full app around it. The full 8 edge cases complete in 6.4 seconds.
45× faster. Same browser. Same assertions. The only difference is isolation.
2 The Rule
Test UI components in isolation when the component has complex logic. E2E tests verify integration; component tests verify the component. Both have their place. Do not replace one with the other.
3 The Analogy
Component testing is like testing a car engine on a test bench, not in a car.
You can run the engine at different speeds, check all sensors, simulate every load condition, and inject faulty inputs — without the weight, cost, and complexity of the rest of the vehicle. When the engine passes the bench tests, you put it in the car and run the integration test. Two different things. Both necessary.
Component testing on the bench does not tell you whether the car drives. Integration testing in the car does not easily let you isolate an engine problem. You need both, at different times, for different purposes.
4 Watch Me Do It
Setup — initialise Playwright CT in an existing project:
npm init playwright@latest -- --ct
# Follow the prompts: choose React, Vue, or Svelte
This adds a playwright/index.html entry point and a separate CT config. Your existing E2E tests are unaffected.
The component — a KiwiSaver contribution calculator:
// KiwiSaverCalculator.tsx
interface Props {
grossSalary: number;
contributionRate: 3 | 4 | 6 | 8 | 10;
}
export function KiwiSaverCalculator({ grossSalary, contributionRate }: Props) {
const annual = grossSalary * (contributionRate / 100);
const employerMatch = annual * 3 / contributionRate;
return (
<div data-testid="calculator">
<p>Annual contribution: <strong>${annual.toFixed(2)}</strong></p>
<p>Employer match: <strong>${employerMatch.toFixed(2)}</strong></p>
</div>
);
}
The component tests:
// KiwiSaverCalculator.spec.tsx
import { test, expect } from '@playwright/experimental-ct-react';
import { KiwiSaverCalculator } from './KiwiSaverCalculator';
test('calculates 3% contribution correctly', async ({ mount }) => {
const component = await mount(
<KiwiSaverCalculator grossSalary={80000} contributionRate={3} />
);
await expect(component.getByText('$2400.00')).toBeVisible();
});
test('calculates 10% contribution correctly', async ({ mount }) => {
const component = await mount(
<KiwiSaverCalculator grossSalary={80000} contributionRate={10} />
);
await expect(component.getByText('$8000.00')).toBeVisible();
});
test('all valid contribution rates render without error', async ({ mount }) => {
for (const rate of [3, 4, 6, 8, 10] as const) {
const component = await mount(
<KiwiSaverCalculator grossSalary={60000} contributionRate={rate} />
);
await expect(component.getByTestId('calculator')).toBeVisible();
}
});
Run component tests separately from E2E tests:
npx playwright test --config=playwright-ct.config.ts
5 When to Use It
Good candidates for component testing:
- Components with complex computation or state logic (calculators, formatters, validators)
- Date pickers, multi-step forms, currency inputs
- Components used in many places — high ROI on isolation tests
- Components where an E2E test takes more than 20 seconds to reach the interaction point
Not suited for component testing:
- Simple presentational components with no logic (a styled button, a static card)
- Integration scenarios that require real API calls or routing
- User journeys that span multiple pages — that is what E2E is for
6 Common Mistakes
✗ I used to think: component tests replace E2E tests.
Actually: they test at a different level. A component test proves the KiwiSaver calculator computes correctly in isolation. An E2E test proves the calculator appears on the right step of the enrolment form, receives the right data, and its output feeds correctly into the next step. You need both. Replacing one with the other leaves a gap.
✗ I used to think: Playwright CT is production-ready stable.
Actually: it is marked experimental. The core API — mount, locators, assertions — is stable enough for real projects, but the CT-specific internals can change between Playwright major versions. Pin your version in package.json and treat CT upgrades with the same care as a framework upgrade.
✗ I used to think: I can use the full page object from my E2E tests in component tests.
Actually: component tests do not have a full page — they have a mounted component. page.goto() does not exist in a CT test. Your locators must target within the mounted component using the component handle returned by mount(). E2E page objects cannot be reused directly; they must be adapted or replaced with component-level helpers.
7 Now You Try
A NZ government form has a GST calculator component that takes a pre-tax amount and returns the amount plus 15% GST, formatted in NZD. Write 4 Playwright component tests covering: correct calculation, boundary value at $0, a very large amount ($1,000,000), and display of the correct currency symbol.
8 Self-Check
Click each question to reveal the answer.
What is the key difference between a Playwright E2E test and a component test?
An E2E test loads the full application in a browser and navigates to a page to interact with a component in its real context. A component test mounts a single component directly in the browser, without the full application around it. E2E tests verify integration; component tests verify the component in isolation. The speed difference is significant: component tests run in under a second, E2E tests often take 20–60 seconds to reach the interaction point.
When does component testing provide the most value over E2E?
When a component has complex internal logic (calculation, validation, state machines) that has many test cases, and when the E2E setup cost is high (login, navigation, data setup). If each E2E test takes 45 seconds to reach a component and you have 20 test cases for that component, you spend 15 minutes on E2E setup for one component. Component testing makes each case take under a second.
Can you use page.goto() in a component test?
No. Component tests do not have a navigable page. The component is mounted directly into a minimal HTML shell — there is no routing, no full app, and no URL to navigate to. Use the component handle returned by mount() to locate and interact with elements. If you need page.goto(), you need an E2E test, not a component test.
9 ISTQB Mapping
CTAL-TAE Section 3.3 — Test isolation: component-level testing strategies. Component testing is an explicit level in the test automation pyramid, sitting between unit tests and integration tests. The standard recognises that testing components in isolation, before testing their integration, finds defects earlier and at lower cost. Playwright CT is a browser-native implementation of this principle — isolation without sacrificing the fidelity of a real browser environment.