Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Guide for testing practices and frameworks
.claude/skills/aiskillstore-testing/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-19 | ✗→✓ | ▲ Improved | 50% | 0% |
| case-01 | ✗→✓ | ▲ Improved | 59% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 61% | 0% |
| case-18 | ✗→✓ | ▲ Improved | 100% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 128% | 0% |
You are an expert at testing TypeScript/React applications using Playwright, Vitest, and Storybook.
Claude should automatically use this skill when:
| Tool | Purpose | Location | |------|---------|----------| | Playwright | E2E browser tests | e2e/*.spec.ts | | Vitest | Unit/integration tests | src/**/*.test.ts | | Storybook | Component visual tests | src/**/*.stories.tsx |
Before writing ANY E2E test, you MUST:
Toolbar.tsx, Editor.tsx)data-testid attributes (explicit testing contract)title attributes (accessibility, stable)aria-label attributes (accessibility)| 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
typescript// ❌ WRONG - Guessing without reading source await editor.clickToolbarButton('B'); // Assumes button text is "B" // ✅ CORRECT - After reading Toolbar.tsx:54 shows title="Bold (Cmd+B)" await editor.clickToolbarButton('Bold (Cmd+B)');
When adding test IDs to components, use format: {scope}-{element}-{type}
tsxdata-testid="toolbar-bold-button" data-testid="editor-content-area" data-testid="sidebar-note-list"
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.tstypescriptimport { 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(); // Use title from Toolbar.tsx - NOT guessed text await editor.clickToolbarButton('Bold (Cmd+B)'); await editor.expectElement('strong'); }); });
typescripteditor.goto() // Navigate to app editor.clear() // Clear editor content editor.type(text) // Type text editor.selectAll() // Select all (Cmd+A) editor.clickToolbarButton(title) // Click toolbar button by title attribute editor.isToolbarButtonActive(title) // Check button state by title editor.expectText(text) // Assert text exists editor.expectElement(selector) // Assert element exists editor.pressShortcut(key) // Press keyboard shortcut
| 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 |
bashpnpm test:e2e # Run all E2E tests pnpm test:e2e:ui # Open Playwright UI pnpm test:e2e:headed # Run with browser visible pnpm test:e2e:chromium # Chrome only pnpm test:e2e:webkit # Safari only
ComponentName.test.tsxuseHookName.test.tsutilName.test.tstypescriptimport { 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(); }); });
typescriptimport { 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); }); });
typescriptimport { within, userEvent, expect } from '@storybook/test'; export const WithInteraction: Story = { play: async ({ canvasElement }) => { const canvas = within(canvasElement); // Find and interact with elements const button = canvas.getByRole('button', { name: 'Submit' }); await userEvent.click(button); // Assert results await expect(canvas.getByText('Success')).toBeInTheDocument(); }, };
When writing tests:
| Type | Target | |------|--------| | E2E | Critical user flows | | Unit | Business logic, utilities | | Component | Visual states, interactions |
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-19 | fail→pass | 9,512 | 3,957 | -58% | 1 | 1 | 0% | 1,675 | 2,511 | +50% | 0 | 0 | — |
case-20 | pass→pass | 13,015 | 11,518 | -12% | 1 | 1 | 0% | 2,721 | 4,203 | +54% | 0 | 0 | — |
case-01 | fail→pass | 10,306 | 6,264 | -39% | 1 | 1 | 0% | 1,922 | 3,049 | +59% | 0 | 0 | — |
case-02 | fail→pass | 10,221 | 7,107 | -30% | 1 | 1 | 0% | 1,980 | 3,196 | +61% | 0 | 0 | — |
case-03 | pass→pass | 8,739 | 5,066 | -42% | 1 | 1 | 0% | 1,523 | 2,756 | +81% | 0 | 0 | — |
case-18 | fail→pass | 6,935 | 1,988 | -71% | 1 | 1 | 0% | 1,064 | 2,125 | +100% | 0 | 0 | — |
case-04 | fail→pass | 8,650 | 7,219 | -17% | 1 | 1 | 0% | 1,317 | 3,006 | +128% | 0 | 0 | — |
case-05 | pass→pass | 10,312 | 5,544 | -46% | 1 | 1 | 0% | 1,649 | 2,833 | +72% | 0 | 0 | — |
case-06 | fail→pass | 9,078 | 3,564 | -61% | 1 | 1 | 0% | 1,590 | 2,454 | +54% | 0 | 0 | — |
case-07 | fail→pass | 9,377 | 3,991 | -57% | 1 | 1 | 0% | 1,895 | 2,485 | +31% | 0 | 0 | — |
case-08 | pass→pass | 12,311 | 7,939 | -36% | 1 | 1 | 0% | 2,317 | 3,293 | +42% | 0 | 0 | — |
case-09 | pass→pass | 7,228 | 3,559 | -51% | 1 | 1 | 0% | 1,323 | 2,489 | +88% | 0 | 0 | — |
case-10 | pass→pass | 20,504 | 9,493 | -54% | 1 | 1 | 0% | 1,922 | 3,545 | +84% | 0 | 0 | — |
case-11 | pass→pass | 9,510 | 4,437 | -53% | 1 | 1 | 0% | 1,578 | 2,643 | +67% | 0 | 0 | — |
case-12 | pass→pass | 6,152 | 2,327 | -62% | 1 | 1 | 0% | 1,131 | 2,262 | +100% | 0 | 0 | — |
case-13 | fail→pass | 3,609 | 1,679 | -53% | 1 | 1 | 0% | 601 | 2,050 | +241% | 0 | 0 | — |
case-14 | fail→pass | 6,096 | 3,877 | -36% | 1 | 1 | 0% | 1,106 | 2,518 | +128% | 0 | 0 | — |
case-15 | fail→pass | 6,884 | 5,285 | -23% | 1 | 1 | 0% | 1,251 | 2,856 | +128% | 0 | 0 | — |
case-16 | fail→pass | 5,070 | 4,073 | -20% | 1 | 1 | 0% | 922 | 2,565 | +178% | 0 | 0 | — |
case-17 | pass→pass | 5,321 | 4,702 | -12% | 1 | 1 | 0% | 899 | 2,614 | +191% | 0 | 0 | — |
case-21 | pass→pass | 5,570 | 5,085 | -9% | 1 | 1 | 0% | 971 | 2,779 | +186% | 0 | 0 | — |
case-22 | pass→pass | 9,799 | 9,295 | -5% | 1 | 1 | 0% | 1,925 | 3,708 | +93% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +50 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.