원클릭으로
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/