一键导入
testing
Testing with Vitest, Jest, and Playwright. Use for unit tests, integration tests, E2E tests, and coverage reports.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
Testing with Vitest, Jest, and Playwright. Use for unit tests, integration tests, E2E tests, and coverage reports.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
MCP-first testing workflow for Xcode apps (iOS, iPadOS, macOS). Use when the user asks to build, test, run, or verify an Xcode project — including UI checks, smoke tests, regression tests, or simulator launches. Prefers Xcode MCP tools via mcpbridge; falls back to xcodebuild/simctl CLI when MCP is unavailable.
Look up Apple Developer Documentation (Swift, SwiftUI, HealthKit, UIKit, etc.) and WWDC session transcripts using the sosumi CLI tool. Use when working with Swift/iOS code and need to check API signatures, find documentation, or understand Apple frameworks.
Automate browser interactions using agent-browser CLI. Use for taking screenshots, testing deployed sites, checking mobile views, form testing, or browser automation tasks.
Generate user-friendly changelogs from git commits. Transforms technical commits into clear release notes.
Request and perform code reviews. Guidelines for reviewing code quality, finding bugs, and ensuring best practices.
Database management with Prisma ORM. Use for migrations, schema management, seeding, and database queries.
| name | testing |
| description | Testing with Vitest, Jest, and Playwright. Use for unit tests, integration tests, E2E tests, and coverage reports. |
| allowed-tools | Bash, Read, Edit |
| user-invocable | true |
Testing with Vitest (unit/integration) and Playwright (E2E).
npm install -D vitest @testing-library/react @testing-library/jest-dom jsdom
import { defineConfig } from 'vitest/config'
import react from '@vitejs/plugin-react'
export default defineConfig({
plugins: [react()],
test: {
environment: 'jsdom',
globals: true,
setupFiles: ['./vitest.setup.ts'],
include: ['**/*.{test,spec}.{js,ts,jsx,tsx}'],
coverage: {
provider: 'v8',
reporter: ['text', 'json', 'html'],
},
},
})
import '@testing-library/jest-dom'
npx vitest # Watch mode
npx vitest run # Single run
npx vitest run --coverage # With coverage
npx vitest run src/utils # Specific folder
npx vitest run -t "test name" # Specific test
npx vitest --ui # UI mode
{
"scripts": {
"test": "vitest",
"test:run": "vitest run",
"test:coverage": "vitest run --coverage",
"test:ui": "vitest --ui"
}
}
import { describe, it, expect, beforeEach, afterEach, vi } from 'vitest'
describe('Calculator', () => {
it('adds two numbers', () => {
expect(1 + 2).toBe(3)
})
it('handles edge cases', () => {
expect(0 + 0).toBe(0)
expect(-1 + 1).toBe(0)
})
})
import { render, screen, fireEvent } from '@testing-library/react'
import { describe, it, expect, vi } from 'vitest'
import { Button } from './Button'
describe('Button', () => {
it('renders with text', () => {
render(<Button>Click me</Button>)
expect(screen.getByRole('button')).toHaveTextContent('Click me')
})
it('calls onClick when clicked', () => {
const handleClick = vi.fn()
render(<Button onClick={handleClick}>Click</Button>)
fireEvent.click(screen.getByRole('button'))
expect(handleClick).toHaveBeenCalledTimes(1)
})
it('is disabled when disabled prop is true', () => {
render(<Button disabled>Click</Button>)
expect(screen.getByRole('button')).toBeDisabled()
})
})
import { describe, it, expect, vi } from 'vitest'
import { fetchUser } from './api'
describe('API', () => {
it('fetches user data', async () => {
const user = await fetchUser(1)
expect(user).toMatchObject({
id: 1,
name: expect.any(String),
})
})
it('handles errors', async () => {
await expect(fetchUser(-1)).rejects.toThrow('User not found')
})
})
import { vi, describe, it, expect, beforeEach } from 'vitest'
import { sendEmail } from './email'
import * as api from './api'
// Mock module
vi.mock('./api', () => ({
fetchUser: vi.fn(),
}))
describe('Email', () => {
beforeEach(() => {
vi.clearAllMocks()
})
it('sends email to user', async () => {
vi.mocked(api.fetchUser).mockResolvedValue({
id: 1,
email: 'test@example.com'
})
await sendEmail(1, 'Hello')
expect(api.fetchUser).toHaveBeenCalledWith(1)
})
})
import { vi, describe, it, expect } from 'vitest'
describe('Logger', () => {
it('logs to console', () => {
const spy = vi.spyOn(console, 'log')
console.log('test message')
expect(spy).toHaveBeenCalledWith('test message')
spy.mockRestore()
})
})
import { renderHook, act } from '@testing-library/react'
import { describe, it, expect } from 'vitest'
import { useCounter } from './useCounter'
describe('useCounter', () => {
it('increments counter', () => {
const { result } = renderHook(() => useCounter())
expect(result.current.count).toBe(0)
act(() => {
result.current.increment()
})
expect(result.current.count).toBe(1)
})
})
// Equality
expect(value).toBe(expected) // Strict equality
expect(value).toEqual(expected) // Deep equality
expect(value).toStrictEqual(expected) // Strict deep equality
// Truthiness
expect(value).toBeTruthy()
expect(value).toBeFalsy()
expect(value).toBeNull()
expect(value).toBeUndefined()
expect(value).toBeDefined()
// Numbers
expect(value).toBeGreaterThan(3)
expect(value).toBeLessThan(5)
expect(value).toBeCloseTo(0.3, 5)
// Strings
expect(value).toMatch(/pattern/)
expect(value).toContain('substring')
// Arrays
expect(array).toContain(item)
expect(array).toHaveLength(3)
expect(array).toContainEqual({ id: 1 })
// Objects
expect(obj).toHaveProperty('key')
expect(obj).toMatchObject({ a: 1 })
// Exceptions
expect(() => fn()).toThrow()
expect(() => fn()).toThrow('error message')
expect(promise).rejects.toThrow()
// Snapshots
expect(value).toMatchSnapshot()
expect(value).toMatchInlineSnapshot()
// DOM (with @testing-library/jest-dom)
expect(element).toBeInTheDocument()
expect(element).toBeVisible()
expect(element).toBeDisabled()
expect(element).toHaveTextContent('text')
expect(element).toHaveAttribute('href', '/path')
expect(element).toHaveClass('active')
npm init playwright@latest
import { defineConfig, devices } from '@playwright/test'
export default defineConfig({
testDir: './e2e',
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 1 : undefined,
reporter: 'html',
use: {
baseURL: 'http://localhost:3000',
trace: 'on-first-retry',
},
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
{ name: 'mobile', use: { ...devices['iPhone 14'] } },
],
webServer: {
command: 'npm run dev',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
},
})
npx playwright test # Run all tests
npx playwright test --headed # With browser UI
npx playwright test --ui # Interactive UI mode
npx playwright test login.spec.ts # Specific file
npx playwright test --project=chromium # Specific browser
npx playwright show-report # View HTML report
npx playwright codegen # Generate tests
import { test, expect } from '@playwright/test'
test.describe('Authentication', () => {
test('user can login', async ({ page }) => {
await page.goto('/login')
await page.fill('input[name="email"]', 'test@example.com')
await page.fill('input[name="password"]', 'password123')
await page.click('button[type="submit"]')
await expect(page).toHaveURL('/dashboard')
await expect(page.locator('h1')).toContainText('Welcome')
})
test('shows error for invalid credentials', async ({ page }) => {
await page.goto('/login')
await page.fill('input[name="email"]', 'wrong@example.com')
await page.fill('input[name="password"]', 'wrongpass')
await page.click('button[type="submit"]')
await expect(page.locator('.error')).toBeVisible()
await expect(page.locator('.error')).toContainText('Invalid credentials')
})
})
// e2e/pages/LoginPage.ts
import { Page, Locator } from '@playwright/test'
export class LoginPage {
readonly page: Page
readonly emailInput: Locator
readonly passwordInput: Locator
readonly submitButton: Locator
constructor(page: Page) {
this.page = page
this.emailInput = page.locator('input[name="email"]')
this.passwordInput = page.locator('input[name="password"]')
this.submitButton = page.locator('button[type="submit"]')
}
async goto() {
await this.page.goto('/login')
}
async login(email: string, password: string) {
await this.emailInput.fill(email)
await this.passwordInput.fill(password)
await this.submitButton.click()
}
}
// e2e/auth.spec.ts
import { test, expect } from '@playwright/test'
import { LoginPage } from './pages/LoginPage'
test('login with page object', async ({ page }) => {
const loginPage = new LoginPage(page)
await loginPage.goto()
await loginPage.login('test@example.com', 'password')
await expect(page).toHaveURL('/dashboard')
})
npx vitest run --coverage
export default defineConfig({
test: {
coverage: {
provider: 'v8',
thresholds: {
lines: 80,
functions: 80,
branches: 80,
statements: 80,
},
},
},
})
name: Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npm run test:run
- run: npm run test:coverage
e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '20'
- run: npm ci
- run: npx playwright install --with-deps
- run: npm run test:e2e
- uses: actions/upload-artifact@v4
if: failure()
with:
name: playwright-report
path: playwright-report/