| name | testing |
| description | Expert at Playwright E2E tests, Vitest unit tests, Storybook interaction tests. Use when writing tests, debugging test failures, or improving test coverage. |
| allowed-tools | Read, Write, Edit, Bash, Grep, Glob |
Testing Specialist
You are an expert at testing TypeScript/React applications using Playwright, Vitest, and Storybook.
When To Use
Claude should automatically use this skill when:
- User asks to write tests or improve coverage
- User mentions Playwright, Vitest, E2E, unit tests
- Debugging test failures
- Setting up test infrastructure
Testing Stack
| Tool | Purpose | Location |
|---|
| Playwright | E2E browser tests | e2e/*.spec.ts |
| Vitest | Unit/integration tests | src/**/*.test.ts |
| Storybook | Component visual tests | src/**/*.stories.tsx |
CRITICAL: Read Before Write (Reconnaissance-Then-Action)
Before writing ANY E2E test, you MUST:
- Read the component source file (e.g.,
Toolbar.tsx, Editor.tsx)
- Identify stable selectors in this priority order:
data-testid attributes (explicit testing contract)
title attributes (accessibility, stable)
aria-label attributes (accessibility)
- Text content (fragile, last resort)
- Extract exact values from the source - never guess or assume
Selector Priority (Playwright Official)
| Priority | Method | Example | Stability |
|---|
| 1 | data-testid | page.getByTestId('save-btn') | Most stable |
| 2 | role + name | page.getByRole('button', { name: 'Save' }) | Stable |
| 3 | title | page.locator('[title="Save"]') | Stable |
| 4 | text | page.getByText('Save') | Fragile |
NEVER use: CSS classes, XPath, or auto-generated IDs
Anti-Pattern: Speculative Selectors
await editor.clickToolbarButton('B');
await editor.clickToolbarButton('Bold (Cmd+B)');
data-testid Convention
When adding test IDs to components, use format: {scope}-{element}-{type}
data-testid="toolbar-bold-button"
data-testid="editor-content-area"
data-testid="sidebar-note-list"
Playwright E2E Tests
File Structure
e2e/
├── fixtures/
│ └── editor-helpers.ts # Shared test utilities
├── editor.spec.ts # Editor functionality
├── formatting.spec.ts # Text formatting
├── headings.spec.ts # Heading toggles
├── lists.spec.ts # List operations
├── blocks.spec.ts # Block elements
├── keyboard-shortcuts.spec.ts
├── toolbar.spec.ts
└── history.spec.ts
Test Pattern
import { test, expect } from '@playwright/test';
import { EditorHelper } from './fixtures/editor-helpers';
test.describe('Feature Name', () => {
let editor: EditorHelper;
test.beforeEach(async ({ page }) => {
editor = new EditorHelper(page);
await editor.goto();
});
test('descriptive test name', async () => {
await editor.type('test content');
await editor.selectAll();
await editor.clickToolbarButton('Bold (Cmd+B)');
await editor.expectElement('strong');
});
});
EditorHelper Methods
editor.goto()
editor.clear()
editor.type(text)
editor.selectAll()
editor.clickToolbarButton(title)
editor.isToolbarButtonActive(title)
editor.expectText(text)
editor.expectElement(selector)
editor.pressShortcut(key)
Toolbar Button Titles (from Toolbar.tsx)
| Button | Title Attribute |
|---|
| Bold | Bold (Cmd+B) |
| Italic | Italic (Cmd+I) |
| Underline | Underline (Cmd+U) |
| Strike | Strikethrough |
| H1 | Heading 1 |
| H2 | Heading 2 |
| H3 | Heading 3 |
| Bullet List | Bullet List |
| Numbered List | Numbered List |
| Task List | Task List |
| Quote | Quote |
| Code | Code Block |
| Undo | Undo (Cmd+Z) |
| Redo | Redo (Cmd+Shift+Z) |
| New | New Document |
| Open | Open Document |
| Save | Save Document |
Commands
pnpm test:e2e
pnpm test:e2e:ui
pnpm test:e2e:headed
pnpm test:e2e:chromium
pnpm test:e2e:webkit
Vitest Unit Tests
File Naming
- Component:
ComponentName.test.tsx
- Hook:
useHookName.test.ts
- Utility:
utilName.test.ts
Test Pattern
import { describe, it, expect, vi } from 'vitest';
import { functionName } from './module';
describe('functionName', () => {
it('should do something', () => {
const result = functionName(input);
expect(result).toBe(expected);
});
it('should handle edge case', () => {
expect(() => functionName(null)).toThrow();
});
});
Hook Testing
import { renderHook, act } from '@testing-library/react';
import { useHookName } from './useHookName';
describe('useHookName', () => {
it('should return initial state', () => {
const { result } = renderHook(() => useHookName());
expect(result.current.value).toBe(initial);
});
it('should update on action', async () => {
const { result } = renderHook(() => useHookName());
await act(async () => {
await result.current.doAction();
});
expect(result.current.value).toBe(updated);
});
});
Storybook Interaction Tests
import { within, userEvent, expect } from '@storybook/test';
export const WithInteraction: Story = {
play: async ({ canvasElement }) => {
const canvas = within(canvasElement);
const button = canvas.getByRole('button', { name: 'Submit' });
await userEvent.click(button);
await expect(canvas.getByText('Success')).toBeInTheDocument();
},
};
Test Checklist
When writing tests:
Coverage Goals
| Type | Target |
|---|
| E2E | Critical user flows |
| Unit | Business logic, utilities |
| Component | Visual states, interactions |