# Component Testing
## Table of Contents
1. [Setup & Configuration](#setup--configuration)
2. [Mounting Components](#mounting-components)
3. [Props & State Testing](#props--state-testing)
4. [Events & Interactions](#events--interactions)
5. [Slots & Children](#slots--children)
6. [Mocking Dependencies](#mocking-dependencies)
7. [Framework-Specific Patterns](#framework-specific-patterns)
## Setup & Configuration
### Installation
```bash
# React
npm init playwright@latest -- --ct
# Vue
npm init playwright@latest -- --ct
# Svelte
npm init playwright@latest -- --ct
# Solid
npm init playwright@latest -- --ct
```
### Configuration
```typescript
// playwright-ct.config.ts
import { defineConfig, devices } from "@playwright/experimental-ct-react";
export default defineConfig({
testDir: "./tests/components",
snapshotDir: "./tests/components/__snapshots__",
use: {
ctPort: 3100,
ctViteConfig: {
resolve: {
alias: {
"@": "/src",
},
},
},
},
projects: [
{ name: "chromium", use: { ...devices["Desktop Chrome"] } },
{ name: "firefox", use: { ...devices["Desktop Firefox"] } },
{ name: "webkit", use: { ...devices["Desktop Safari"] } },
],
});
```
### Project Structure
```
src/
components/
Button.tsx
Modal.tsx
tests/
components/
Button.spec.tsx
Modal.spec.tsx
playwright/
index.html # CT entry point
index.tsx # CT setup (providers, styles)
```
## Mounting Components
### Basic Mount
```tsx
// Button.spec.tsx
import { test, expect } from "@playwright/experimental-ct-react";
import { Button } from "@/components/Button";
test("renders button with text", async ({ mount }) => {
const component = await mount();
await expect(component).toContainText("Click me");
await expect(component).toBeVisible();
});
```
### Mount with Props
```tsx
test("renders with all props", async ({ mount }) => {
const component = await mount(
,
);
await expect(component).toHaveClass(/primary/);
await expect(component).toHaveClass(/large/);
await expect(component.locator("svg")).toBeVisible(); // icon
});
```
### Mount with Wrapper/Provider
```tsx
// playwright/index.tsx - Global providers
import { ThemeProvider } from "@/providers/theme";
import { QueryClientProvider } from "@tanstack/react-query";
import "@/styles/globals.css";
export default function PlaywrightWrapper({ children }) {
return (
{children}
);
}
```
```tsx
// Or per-test wrapper
test("with custom provider", async ({ mount }) => {
const component = await mount(
,
);
await expect(component.getByText("Test")).toBeVisible();
});
```
## Props & State Testing
### Testing Prop Variations
```tsx
test.describe("Button variants", () => {
const variants = ["primary", "secondary", "danger", "ghost"] as const;
for (const variant of variants) {
test(`renders ${variant} variant`, async ({ mount }) => {
const component = await mount();
await expect(component).toHaveClass(new RegExp(variant));
});
}
});
```
### Updating Props
```tsx
test("responds to prop changes", async ({ mount }) => {
const component = await mount();
await expect(component.getByTestId("count")).toHaveText("0");
// Update props
await component.update();
await expect(component.getByTestId("count")).toHaveText("10");
});
```
### Testing Controlled Components
```tsx
test("controlled input", async ({ mount }) => {
let externalValue = "";
const component = await mount(
{
externalValue = e.target.value;
}}
/>,
);
await component.locator("input").fill("hello");
// For controlled components, update with new value
await component.update(
(externalValue = e.target.value)} />,
);
await expect(component.locator("input")).toHaveValue("hello");
});
```
### Testing Internal State
```tsx
test("internal state updates", async ({ mount }) => {
const component = await mount();
// Initial state
await expect(component.locator('[role="switch"]')).toHaveAttribute(
"aria-checked",
"false",
);
// Trigger state change
await component.click();
// Verify state updated
await expect(component.locator('[role="switch"]')).toHaveAttribute(
"aria-checked",
"true",
);
});
```
## Events & Interactions
### Testing Click Events
```tsx
test("click event fires", async ({ mount }) => {
let clicked = false;
const component = await mount(
,
);
await component.click();
expect(clicked).toBe(true);
});
```
### Testing Event Payloads
```tsx
test("onChange provides correct value", async ({ mount }) => {
const values: string[] = [];
const component = await mount(