QA How-To
Playwright Component Testing for React Complete Guide (2026)
Playwright component testing React complete guide with runnable setup, context and API mocking, error boundaries, debugging, best practices, CI, and tips.
22 min read | 4,125 words
TL;DR
Configure Playwright CT for React, mount components with explicit props and providers, intercept browser requests before mounting, and assert user-visible outcomes. Keep unit tests for pure logic and end-to-end tests for assembled system journeys.
Key Takeaways
- Use component tests for browser-level UI behavior that does not require the complete deployed system.
- Mount the smallest React tree that still represents a meaningful user interaction.
- Control props, providers, and browser requests explicitly so every test remains isolated.
- Prefer accessible role and label locators over DOM structure and styling selectors.
- Test success, validation, pending, permission, network failure, and render failure states.
- Run the same component-test command locally and in CI, retaining traces on failure.
Playwright component testing lets you mount a React component in a real browser, interact through its rendered DOM, and assert what a user observes without starting the complete application. This Playwright component testing React complete guide gives you a runnable Vite setup, realistic tests, isolation patterns, debugging advice, and continuous integration.
You will build a profile editor whose behavior depends on props, React context, network responses, and an error boundary. The examples form one coherent TypeScript project and use public Playwright APIs available in 2026.
TL;DR
| Concern | Recommended approach | Why |
|---|---|---|
| Runner | Playwright Component Testing for React | Real browser rendering with Playwright locators |
| Bundling | The React CT package with Vite | Playwright compiles the mount harness |
| Assertions | Roles, labels, and visible text | Tests survive implementation refactors |
| Isolation | Fresh mounts and explicit providers | Failures stay reproducible |
| API behavior | page.route before mount | Browser requests become deterministic |
| CI | Install Chromium and run the CT project | Local and automated commands match |
Use component tests for interactive UI behavior that needs a browser but not the complete deployed system. Keep unit tests for pure functions and end-to-end tests for routing, authentication, backend integration, and critical journeys.
What You Will Build
- A React and TypeScript component-testing project powered by Playwright and Vite.
- A profile editor tested through accessible labels and roles.
- Context-aware tests using a focused provider wrapper.
- Deterministic success and failure responses through network interception.
- An error-boundary test that proves the fallback experience.
- A CI job that runs tests and uploads the HTML report.
Copy the examples into a clean Vite React TypeScript app. Run each command in order and keep the filenames shown.
Prerequisites
Use a supported Node.js LTS release, npm, and a current Playwright component-testing package. Let npm record compatible versions in the lockfile instead of copying a version number that will age.
npm create vite@latest react-ct-guide -- --template react-ts
cd react-ct-guide
npm install
npm init playwright@latest -- --ct
Select React when prompted. If you add CT manually, install the packages and Chromium:
npm install -D @playwright/experimental-ct-react @playwright/test
npx playwright install chromium
The component-testing API remains marked experimental. Review Playwright release notes before upgrades and commit the lockfile. This guide uses mount, locators, assertions, page.route, and standard React composition.
Step 1: Configure Playwright Component Testing for React
Create a dedicated configuration so end-to-end base URLs, storage state, and web-server settings cannot leak into this smaller layer.
// playwright-ct.config.ts
import { defineConfig, devices } from '@playwright/experimental-ct-react';
export default defineConfig({
testDir: './playwright',
snapshotDir: './playwright/__snapshots__',
timeout: 30_000,
fullyParallel: true,
use: {
ctPort: 3100,
trace: 'retain-on-failure',
screenshot: 'only-on-failure',
...devices['Desktop Chrome'],
},
reporter: [
['list'],
['html', { outputFolder: 'playwright-report', open: 'never' }],
],
});
Add scripts to package.json:
{
"scripts": {
"test:ct": "playwright test -c playwright-ct.config.ts",
"test:ct:ui": "playwright test -c playwright-ct.config.ts --ui"
}
}
Playwright CT creates a browser page and mounts JSX inside its harness. The CT port belongs to that harness, not the normal Vite development server. Do not add a webServer unless a component truly calls a separate local service.
Verify the step: run npm run test:ct -- --list. An empty project can report no tests, but the configuration must load without an import or unknown-option error. Install Chromium if the first execution reports a missing browser executable.
Step 2: Build a Testable Profile Editor
Create a component with observable states. It receives initial data, validates input, submits through an injected function, and announces success or failure.
// src/components/ProfileEditor.tsx
import { FormEvent, useState } from 'react';
export type Profile = { name: string; email: string };
type Props = {
initialProfile: Profile;
onSave: (profile: Profile) => Promise<void>;
};
export function ProfileEditor({ initialProfile, onSave }: Props) {
const [profile, setProfile] = useState(initialProfile);
const [status, setStatus] = useState('');
const [saving, setSaving] = useState(false);
async function submit(event: FormEvent) {
event.preventDefault();
if (!profile.name.trim()) {
setStatus('Name is required');
return;
}
setSaving(true);
setStatus('');
try {
await onSave(profile);
setStatus('Profile saved');
} catch {
setStatus('Could not save profile');
} finally {
setSaving(false);
}
}
return (
<form aria-label="Profile editor" onSubmit={submit}>
<label>
Name
<input value={profile.name} onChange={(event) =>
setProfile({ ...profile, name: event.target.value })
} />
</label>
<label>
Email
<input type="email" value={profile.email} onChange={(event) =>
setProfile({ ...profile, email: event.target.value })
} />
</label>
<button disabled={saving} type="submit">
{saving ? 'Saving...' : 'Save profile'}
</button>
<p role="status" aria-live="polite">{status}</p>
</form>
);
}
The async prop is a normal dependency boundary, not a testing-only branch. Accessible labels give users and tests the same contract. The live region communicates validation and completion without exposing React state.
Verify the step: run npm run build. TypeScript and Vite should compile the component. Render it in the application with an async save callback and confirm both initial values appear.
Step 3: Mount React Components and Test Behavior
The CT fixture supplies mount, which accepts JSX and returns a component handle. Use web-first assertions because they retry until browser state matches or the timeout expires.
// playwright/ProfileEditor.spec.tsx
import { expect, test } from '@playwright/experimental-ct-react';
import { ProfileEditor, type Profile } from '../src/components/ProfileEditor';
test('edits and saves a profile', async ({ mount }) => {
let saved: Profile | undefined;
const component = await mount(
<ProfileEditor
initialProfile={{ name: 'Mina', email: 'mina@example.com' }}
onSave={async (profile) => { saved = profile; }}
/>,
);
await component.getByLabel('Name').fill('Mina Patel');
await component.getByRole('button', { name: 'Save profile' }).click();
await expect(component.getByRole('status')).toHaveText('Profile saved');
expect(saved).toEqual({
name: 'Mina Patel',
email: 'mina@example.com',
});
});
test('validates a blank name', async ({ mount }) => {
const component = await mount(
<ProfileEditor
initialProfile={{ name: 'Mina', email: 'mina@example.com' }}
onSave={async () => {}}
/>,
);
await component.getByLabel('Name').fill(' ');
await component.getByRole('button', { name: 'Save profile' }).click();
await expect(component.getByRole('status')).toHaveText('Name is required');
});
Prefer getByRole and getByLabel over CSS classes or component internals. A refactor from state hooks to a reducer should not change these tests. The first case combines a visible assertion with one narrow check at the dependency boundary.
For mounting options and callback patterns, read mount React components in Playwright step by step.
Verify the step: run npm run test:ct -- playwright/ProfileEditor.spec.tsx. Both cases should pass. Temporarily break the expected status to inspect the locator log, then restore it.
Step 4: Supply React Context Without Global State
Wrap the subject in the smallest provider tree required by the scenario. Avoid one universal renderer that silently supplies every application dependency.
// src/components/Permissions.tsx
import { createContext, ReactNode, useContext } from 'react';
type Permissions = { canEditProfile: boolean };
const PermissionsContext = createContext<Permissions>({
canEditProfile: false,
});
export function PermissionsProvider({ value, children }: {
value: Permissions;
children: ReactNode;
}) {
return <PermissionsContext.Provider value={value}>
{children}
</PermissionsContext.Provider>;
}
export function EditProfileAction() {
const { canEditProfile } = useContext(PermissionsContext);
return canEditProfile
? <button type="button">Edit profile</button>
: <p>You do not have permission to edit this profile.</p>;
}
Test meaningful permission branches:
// playwright/EditProfileAction.spec.tsx
import { expect, test } from '@playwright/experimental-ct-react';
import {
EditProfileAction,
PermissionsProvider,
} from '../src/components/Permissions';
test('allows an editor to edit', async ({ mount }) => {
const component = await mount(
<PermissionsProvider value={{ canEditProfile: true }}>
<EditProfileAction />
</PermissionsProvider>,
);
await expect(
component.getByRole('button', { name: 'Edit profile' }),
).toBeVisible();
});
test('explains why a viewer cannot edit', async ({ mount }) => {
const component = await mount(
<PermissionsProvider value={{ canEditProfile: false }}>
<EditProfileAction />
</PermissionsProvider>,
);
await expect(component).toContainText(
'You do not have permission to edit this profile.',
);
});
Explicit wrappers make state readable. If routing or a data client is required, build a focused harness, expose meaningful inputs, and reset client state between tests. The React context mocking tutorial covers nested providers and custom hooks.
Verify the step: execute npm run test:ct -- playwright/EditProfileAction.spec.tsx --repeat-each=2. Both branches should pass in any order with no provider value leaking between mounts.
Step 5: Mock Browser Requests Deterministically
When a component owns its request lifecycle, register page.route before mounting so the initial request cannot race ahead of the mock.
// src/components/UserSummary.tsx
import { useEffect, useState } from 'react';
type User = { name: string; plan: string };
export function UserSummary() {
const [user, setUser] = useState<User>();
const [error, setError] = useState('');
useEffect(() => {
fetch('/api/me')
.then((response) => {
if (!response.ok) throw new Error('Request failed');
return response.json() as Promise<User>;
})
.then(setUser)
.catch(() => setError('Account could not be loaded'));
}, []);
if (error) return <p role="alert">{error}</p>;
if (!user) return <p role="status">Loading account...</p>;
return <section aria-label="Account summary">
<h2>{user.name}</h2>
<p>Plan: {user.plan}</p>
</section>;
}
Cover success and server failure:
// playwright/UserSummary.spec.tsx
import { expect, test } from '@playwright/experimental-ct-react';
import { UserSummary } from '../src/components/UserSummary';
test('renders the returned account', async ({ mount, page }) => {
await page.route('**/api/me', async (route) => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ name: 'Mina Patel', plan: 'Team' }),
});
});
const component = await mount(<UserSummary />);
await expect(
component.getByRole('region', { name: 'Account summary' }),
).toContainText('Mina Patel');
await expect(component).toContainText('Plan: Team');
});
test('renders an API failure', async ({ mount, page }) => {
await page.route('**/api/me', async (route) => {
await route.fulfill({ status: 503, body: 'Unavailable' });
});
const component = await mount(<UserSummary />);
await expect(component.getByRole('alert')).toHaveText(
'Account could not be loaded',
);
});
Assert what the component communicates, not React render counts. Inspect route.request() only when the outgoing method or payload is part of the component contract. API schema and integration still deserve tests at other layers.
Verify the step: run the file with --repeat-each=2. Every execution should pass without a backend. If Vite returns HTML, check that the glob matches and routing occurs before mount.
Step 6: Test Error Boundaries
React error boundaries catch rendering errors below them. A class remains the standard implementation because boundary lifecycle APIs are class APIs.
// src/components/AppErrorBoundary.tsx
import { Component, ErrorInfo, ReactNode } from 'react';
export class AppErrorBoundary extends Component<
{ children: ReactNode },
{ failed: boolean }
> {
state = { failed: false };
static getDerivedStateFromError() {
return { failed: true };
}
componentDidCatch(error: Error, info: ErrorInfo) {
console.error('Profile area failed', error, info.componentStack);
}
render() {
if (this.state.failed) {
return <section role="alert">
<p>Profile area is unavailable.</p>
<button type="button" onClick={() => this.setState({ failed: false })}>
Try again
</button>
</section>;
}
return this.props.children;
}
}
Use a controlled descendant:
// playwright/AppErrorBoundary.spec.tsx
import { useState } from 'react';
import { expect, test } from '@playwright/experimental-ct-react';
import { AppErrorBoundary } from '../src/components/AppErrorBoundary';
function CrashControl() {
const [failed, setFailed] = useState(false);
if (failed) throw new Error('Controlled render failure');
return <button type="button" onClick={() => setFailed(true)}>
Trigger failure
</button>;
}
test('shows a fallback after a descendant crashes', async ({ mount }) => {
const component = await mount(
<AppErrorBoundary><CrashControl /></AppErrorBoundary>,
);
await component.getByRole('button', { name: 'Trigger failure' }).click();
await expect(component.getByRole('alert')).toContainText(
'Profile area is unavailable.',
);
await expect(
component.getByRole('button', { name: 'Try again' }),
).toBeVisible();
});
The fallback is the primary contract. Recovery needs product judgment because resetting only the boundary may render the same broken child again. Production retry might clear invalid state, change a key, or navigate away. See testing React error boundaries with Playwright for recovery and logging patterns.
Verify the step: run the spec headed. You should see the trigger followed by the alert and retry control. The controlled exception may appear in browser logs while the test passes.
Step 7: Choose Test Boundaries and Organize Fixtures
Organize specs by product component, not locator type. Share stable setup such as locale or a configurable provider composition, but keep important scenario state visible.
| Layer | Best fit | Avoid |
|---|---|---|
| Unit | Validation, reducers, formatters | Browser behavior |
| Component | Rendering, interaction, context, request states | Full authentication wiring |
| API | Contracts, authorization, schemas | DOM presentation |
| End-to-end | Critical journeys across real services | Exhaustive UI permutations |
Do not chase one global coverage number. Map tests to risks: blank input, pending save, rejected save, insufficient permission, API failure, and render failure. Keep a few end-to-end journeys that prove the assembled system.
Visual assertions can use toHaveScreenshot for stable, bounded components. Disable animation, control fonts and data, and review snapshot updates as code. Keep behavioral assertions because they explain purpose better than pixels alone.
Verify the step: run all CT tests with npm run test:ct -- --repeat-each=2. No case should rely on another case's mounted tree, route, provider, or mutable module state.
Step 8: Run Component Tests in CI
Add a workflow that installs locked dependencies, installs Chromium with system dependencies, runs the local script, and preserves evidence.
# .github/workflows/component-tests.yml
name: Component tests
on:
pull_request:
push:
branches: [main]
jobs:
playwright-ct:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- run: npx playwright install --with-deps chromium
- run: npm run test:ct
- name: Upload Playwright report
if: always()
uses: actions/upload-artifact@v4
with:
name: playwright-component-report
path: playwright-report/
if-no-files-found: ignore
retention-days: 7
Install only browsers that represent real product risks. Shard large suites after removing shared state and measuring duration. Retries should remain visible diagnostics, since a case that passes only on retry is unstable.
Use the Playwright component tests with Vite and CI tutorial for a deeper automation walkthrough.
Verify the step: push a branch. The job should report each test, upload HTML even after failure, and fail when an assertion fails. Deliberately fail one trial and confirm the artifact contains the locator error, screenshot, and trace.
Playwright Component Testing React Complete Guide: Design Decisions
Mount the smallest meaningful tree. A button may be too narrow when risk lies in form behavior, while a complete routed application may be too broad. Choose the smallest unit that exposes behavior users recognize.
Make three boundaries visible. The component owns rendering and interaction, providers own environmental state, and the browser test controls external effects. Inject callbacks for domain operations and use routing when the component genuinely owns HTTP behavior.
Treat accessibility as a product contract. Semantic locators encourage useful markup, but passing locators do not replace accessibility audits. Test keyboard focus for dialogs, validation, and recoverable failures. Add assistive-technology review for critical workflows.
Keep the harness boring. Reuse deterministic production providers and write thin adapters for environment-specific services. When the harness starts recreating the whole app, an end-to-end test may describe the risk more clearly.
Review a Production Component Test Suite
Before making component tests a required pull-request check, review the suite as an operating system for feedback rather than a collection of green examples. Start with ownership. Every component area should have a team that can explain its risk, update its harness, and investigate recurring failures. A shared testing platform can maintain configuration, but product teams should own behavioral expectations.
Review test names as specifications. A useful name states the condition and observable result, such as "disables save while the request is pending." A weak name such as "works correctly" forces readers to reverse-engineer the body. Arrange each case so its inputs, action, and expectation are visible. Extract mechanical setup only when the abstraction makes intent clearer.
Exercise transitions, not just settled screenshots. Interactive components move through idle, focused, invalid, pending, successful, rejected, and retry states. A test that mounts directly in a final state can miss disabled controls, duplicated submissions, lost focus, or stale announcements. Use a deferred promise when you must hold an operation pending, then verify the button is disabled before resolving it. Keep timing controlled by the test rather than a real delay.
Review negative behavior deliberately. Reject a save operation, return malformed data at a service boundary, remove a permission, and make a descendant render fail. Confirm that the UI explains what happened, preserves safe input where appropriate, and allows a sensible next action. Also confirm that logs and reports do not expose tokens, personal data, or full sensitive payloads.
Check isolation under stress. Run files in parallel and repeat them. Random order is less important than proving each case creates all state it consumes. Query clients, stores, fake clocks, browser routes, and module-level caches are common leak points. Teardown should be a safety net, while fresh construction should be the primary isolation mechanism.
Finally, inspect a real failed report. A useful failure identifies the scenario, locator, expected result, actual UI, and relevant trace without overwhelming the reader. Remove artifacts that never help diagnosis and add evidence only for known blind spots. The suite is ready when a contributor can reproduce a CI failure locally, understand the component state, and fix the cause without guessing.
Handle Advanced React Scenarios
Portals, Suspense, timers, and browser APIs need deliberate treatment. A dialog rendered through a portal may sit outside the component handle. Query it through page.getByRole('dialog') when that reflects the actual document structure, then verify focus enters the dialog and returns to the trigger after closing. Do not force every assertion under the mount root when production rendering does not work that way.
For Suspense-driven UI, assert the fallback first only when users can meaningfully observe it. Then resolve controlled data and assert the completed content. Avoid racing on a fallback that may render too briefly to be a stable contract. If a loading indicator must remain visible for accessibility or product reasons, make the data source controllable and test that requirement explicitly.
Components that use clipboard, geolocation, notifications, or media queries should receive browser context permissions or initialization scripts through Playwright. Test the browser-facing behavior instead of replacing the whole API with an object that behaves unlike the platform. Keep permission-denied cases because real users encounter them.
Timers deserve special care. Prefer injecting a scheduler or configuring the state transition so tests do not wait for long real intervals. If Playwright clock support fits the browser behavior, install and advance the clock through documented APIs for the locked version. Do not combine fake time with uncontrolled network promises unless you understand which queue each operation uses.
Hydration and server-component boundaries usually belong in integration or end-to-end tests because a simple client mount does not reproduce server rendering and transport. You can still component-test a client leaf with serialized props, but state clearly what the test excludes. Choosing the correct boundary is more valuable than forcing every React feature into the CT harness.
Establish a Maintenance Workflow
Treat dependency upgrades as test-platform changes. Update Playwright and its CT package together, install browsers from the updated package, and run the complete suite on a clean checkout. Read migration notes for configuration, bundling, snapshots, and browser versions. Review lockfile changes instead of accepting an automated update solely because existing tests pass.
Track recurring failure signatures, not only pass rate. Categories such as selector ambiguity, leaked state, environment capacity, application defect, and harness defect reveal different actions. Quarantine should be short-lived, owned, and linked to a fix. A permanently retried or skipped test creates the appearance of coverage without dependable feedback.
Apply a clear entry standard to new tests. The case should cover a named product risk, run independently, avoid secrets, use deterministic inputs, and produce a useful failure. It should not duplicate a cheaper unit test or a broader journey without explaining the distinct value. During review, ask what defect this case would catch and whether its assertion would still matter after an internal refactor.
Apply an exit standard too. Remove cases when the behavior disappears, risk moves to another layer, or maintenance cost exceeds unique signal. Consolidate permutations that prove the same branch. Test code is production engineering infrastructure, but that does not mean every historical assertion must live forever.
Document one local command, one debugging workflow, and one CI artifact location in the repository. New contributors should be able to run a single spec, open UI mode, inspect a trace, and update an intentional screenshot without tribal knowledge. A short, accurate runbook is more useful than a long architecture document that no longer matches the configuration.
Troubleshooting
Browser executable is missing -> Run npx playwright install chromium. In Linux CI, use npx playwright install --with-deps chromium.
JSX or TypeScript imports fail -> Ensure the spec is .tsx, React support was selected, and the config imports from @playwright/experimental-ct-react. Match aliases and essential Vite plugins.
The component stays on Loading -> Register the route before mount, match the resolved URL, and return the expected content type and JSON shape. Inspect the trace network panel instead of adding a sleep.
Tests pass alone but fail together -> Remove mutable module state, reset data clients, make route handlers local, and create fresh provider values.
An expected boundary error pollutes logs -> Capture or filter only the known message inside that test. Never silence all browser console errors globally.
A locator matches several elements -> Improve the accessible name or scope it to a named region. Do not use .first() unless order is the actual contract.
The Complete Series
- Mount React components in Playwright step by step covers initialization, JSX mounting, callbacks, and interaction assertions.
- Mock React context in Playwright component tests covers providers, authenticated state, themes, and reusable harnesses.
- Test React error boundaries with Playwright covers controlled crashes, fallback assertions, logging, and recovery.
- Run Playwright component tests with Vite and CI covers configuration, browser installation, reporting, and pull-request automation.
Where To Go Next
Start with mounting if this is your first CT project. Add context patterns when production UI needs permissions, themes, routing, or a query client. Study boundaries for resilience work, then finish the Vite and CI tutorial before requiring the suite on pull requests.
Connect this layer to Playwright locator best practices, maintainable page objects, and test automation in CI/CD. Component tests rarely need page objects, but meaningful actions and observable outcomes still matter.
Interview Questions and Answers
Q: What is Playwright component testing for React?
It mounts React UI in a Playwright-controlled browser and tests rendered DOM behavior. It is narrower than end-to-end testing but exercises more browser behavior than pure unit testing.
Q: When should you choose a component test over a unit test?
Choose it when risk depends on browser rendering, focus, events, accessible DOM, or interaction among children. Keep calculations and reducers in unit tests.
Q: How do you pass React context into a mounted component?
Wrap the subject in its provider during mount with explicit values. Create a configurable harness for repeated complex setup, but avoid hidden global state.
Q: How do you mock an API?
Register page.route before mount and fulfill matching requests deterministically. Assert loading, success, empty, and error UI while separate contract tests cover the backend.
Q: Why prefer role and label locators?
They describe semantics users perceive, pair with auto-waiting, and resist styling refactors. Test IDs are useful where no meaningful semantic target exists.
Q: Where do component tests fit in a layered strategy?
They sit between unit tests and end-to-end journeys. They provide focused browser feedback without complete system setup, but do not replace API contracts or critical full flows.
Q: How do you keep them reliable in CI?
Commit the lockfile, install selected browsers, isolate state, control network data, and retain failure traces. Keep retries visible and investigate instability.
Best Practices and Common Mistakes
- Assert outcomes users observe, not hooks, state variables, or render counts.
- Register mocks before mounting and cover rejection paths.
- Give every case fresh provider values, clients, data, and routes.
- Prefer accessible HTML and user-centered locators.
- Do not split a recognizable interaction into meaningless fragments.
- Never use fixed sleeps when an observable condition exists.
- Keep API contract and end-to-end coverage beside mocked component tests.
- Retain useful failure evidence with proportionate storage.
- Review experimental CT changes during upgrades.
- Make every spec runnable alone and in parallel.
Playwright Component Testing React Complete Guide: Conclusion
This Playwright component testing React complete guide gives you a practical middle layer for real React behavior in a browser. Configure a dedicated project, mount meaningful trees, express dependencies through props and providers, control browser requests, and assert accessible outcomes.
Run the profile editor first. Then add one complex production component with success, validation, pending, and failure states. Once it is deterministic locally, run the same command in CI and retain a thin end-to-end journey to prove the complete system.
Interview Questions and Answers
What is Playwright component testing for React?
It mounts a React component in a Playwright-controlled real browser and tests rendered DOM and interactions. It is narrower than an end-to-end test but exercises more browser behavior than a unit test. React CT integrates its harness with Vite.
When would you choose a component test instead of a unit test?
I choose it when risk depends on browser rendering, focus, events, accessible DOM, or interaction among components. Calculations and reducers remain unit tests. I use the cheapest layer that represents behavior accurately.
How do you provide React context in Playwright CT?
I mount the subject inside its production provider with explicit scenario values. For a complex tree, I create a thin configurable harness. I avoid global provider state so tests stay readable and isolated.
How do you mock network calls?
I register page.route before mounting and fulfill the request with deterministic status, headers, and body. I test loading, success, empty, and failure UI. Separate contract tests prove compatibility with the real service.
Why use role and label locators?
They describe semantics users and assistive technologies perceive. They resist DOM and CSS refactors and work with Playwright auto-waiting. Test IDs are a fallback where no semantic target exists.
How do component tests fit into a layered strategy?
They sit between isolated unit tests and broad end-to-end journeys. They give focused browser feedback without complete system setup. I still test API contracts and retain critical end-to-end flows.
How do you keep component tests reliable in CI?
I commit the lockfile, install selected browsers and system dependencies, isolate every test, and control data and network behavior. I run the same command locally and in CI and retain traces on failure. Retries remain diagnostics, not a default fix.
Frequently Asked Questions
Is Playwright component testing stable for React?
The React component-testing package is still named experimental, so pin dependencies through the lockfile and review release notes during upgrades. Its core workflow uses Playwright locators, assertions, routing, and browser tooling.
Does Playwright component testing use Vite?
Yes. The React CT harness uses Vite to bundle and serve mounted components. Components that depend on custom Vite plugins or aliases may require matching CT configuration.
Should component tests replace end-to-end tests?
No. Component tests isolate UI behavior efficiently, but do not prove deployment, routing, authentication, or live backend integration. Retain end-to-end coverage for critical assembled journeys.
How do I mock React context in Playwright?
Mount the component inside the real provider with a scenario-specific value. For complex trees, create a configurable harness and reset clients or stores for every test.
How do I mock fetch in a component test?
Register page.route before mount and fulfill the matching request with a controlled response. Assert loading, success, and error UI, while keeping separate API contract tests.
Can component tests run in parallel?
Yes, when every test owns its provider values, route handlers, clients, records, and mutable state. Confirm isolation with repeated and parallel runs.
Which locators should component tests use?
Prefer getByRole and getByLabel because they reflect accessible user interactions. Use test IDs when no stable semantic locator exists, and avoid styling classes.
Related Guides
- How to Use Playwright component testing react (2026)
- Penetration testing basics: A Complete Guide for QA (2026)
- Playwright WebSocket Testing Complete Guide (2026)
- A/B test validation: A Complete Guide for QA (2026)
- AI Agent Testing Complete Guide (2026)
- Canary testing: A Complete Guide for QA (2026)