npx skills add https://github.com/adaptationio/skrillz --skill component-testingSKILL.md
Component Testing with Playwright
Test UI components in isolation using Playwright's experimental component testing feature. Supports React, Vue, Svelte, and Solid.
Quick Start
// Button.spec.tsx
import { test, expect } from '@playwright/experimental-ct-react';
import { Button } from './Button';
test('button click triggers callback', async ({ mount }) => {
let clicked = false;
const component = await mount(
<Button onClick={() => clicked = true}>Click me</Button>
);
await component.click();
expect(clicked).toBe(true);
});
Installation
React
npm init playwright@latest -- --ct
# Select React when prompted
Or manually:
npm install -D @playwright/experimental-ct-react
Vue
npm install -D @playwright/experimental-ct-vue
Svelte
npm install -D @playwright/experimental-ct-svelte
Configuration
playwright-ct.config.ts:
import { defineConfig, devices } from '@playwright/experimental-ct-react';
export default defineConfig({
testDir: './src',
testMatch: '**/*.spec.tsx',
use: {
ctPort: 3100,
ctViteConfig: {
// Custom Vite config for component tests
},
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
],
});
React Component Testing
Basic Mount
import { test, expect } from '@playwright/experimental-ct-react';
import { UserCard } from './UserCard';
test('displays user info', async ({ mount }) => {
const component = await mount(
<UserCard
name="John Doe"
email="john@example.com"
/>
);
await expect(component.getByText('John Doe')).toBeVisible();
await expect(component.getByText('john@example.com')).toBeVisible();
});
With Props
test('button variants', async ({ mount }) => {
// Primary variant
const primary = await mount(<Button variant="primary">Save</Button>);
await expect(primary).toHaveClass(/btn-primary/);
// Secondary variant
const secondary = await mount(<Button variant="secondary">Cancel</Button>);
await expect(secondary).toHaveClass(/btn-secondary/);
});
With Event Handlers
test('form submission', async ({ mount }) => {
const submittedData: any[] = [];
const component = await mount(
<ContactForm onSubmit={(data) => submittedData.push(data)} />
);
await component.getByLabel('Name').fill('John');
await component.getByLabel('Email').fill('john@example.com');
await component.getByRole('button', { name: 'Submit' }).click();
expect(submittedData).toHaveLength(1);
expect(submittedData[0]).toEqual({
name: 'John',
email: 'john@example.com',
});
});
With Context Providers
// Create wrapper for providers
import { ThemeProvider } from './ThemeContext';
test('themed component', async ({ mount }) => {
const component = await mount(
<ThemeProvider theme="dark">
<ThemedButton>Click</ThemedButton>
</ThemeProvider>
);
await expect(component).toHaveClass(/dark-theme/);
});
With Slots/Children
test('card with custom content', async ({ mount }) => {
const component = await mount(
<Card>
<CardHeader>Title</CardHeader>
<CardBody>Content here</CardBody>
<CardFooter>
<Button>Action</Button>
</CardFooter>
</Card>
);
await expect(component.getByText('Title')).toBeVisible();
await expect(component.getByText('Content here')).toBeVisible();
await expect(component.getByRole('button')).toBeVisible();
});
Vue Component Testing
// Counter.spec.ts
import { test, expect } from '@playwright/experimental-ct-vue';
import Counter from './Counter.vue';
test('counter increments', async ({ mount }) => {
const component = await mount(Counter, {
props: {
initialCount: 0,
},
});
await expect(component.getByText('Count: 0')).toBeVisible();
await component.getByRole('button', { name: '+' }).click();
await expect(component.getByText('Count: 1')).toBeVisible();
});
With Slots
test('card with slots', async ({ mount }) => {
const component = await mount(Card, {
slots: {
default: '<p>Card content</p>',
header: '<h2>Card Title</h2>',
},
});
await expect(component.getByText('Card Title')).toBeVisible();
await expect(component.getByText('Card content')).toBeVisible();
});
With Vuex/Pinia
import { test, expect } from '@playwright/experimental-ct-vue';
import { createTestingPinia } from '@pinia/testing';
import UserProfile from './UserProfile.vue';
test('displays user from store', async ({ mount }) => {
const component = await mount(UserProfile, {
global: {
plugins: [
createTestingPinia({
...
Repository
adaptationio/skrillzParent repository
Repository Stats
Stars1
Forks0