component-testing

from adaptationio/skrillz

No description

1 stars0 forksUpdated Jan 16, 2026
npx skills add https://github.com/adaptationio/skrillz --skill component-testing

SKILL.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({
   

...
Read full content

Repository Stats

Stars1
Forks0