Настройка тестирования
Пошаговая инструкция по настройке стека тестирования в проекте.
Применимо для: feature, improvement — при создании нового проекта или внедрении тестирования.
Prerequisites
- Node.js >= 20
- pnpm >= 9
- Nuxt 4 проект создан
- Доступы к Chromatic и Sentry
1. Vitest (Unit)
Установка
pnpm add -D vitest @vue/test-utils happy-dom @vitest/coverage-v8
Конфигурация
// vitest.config.ts
import { defineConfig } from 'vitest/config'
import vue from '@vitejs/plugin-vue'
import { fileURLToPath } from 'node:url'
export default defineConfig({
plugins: [vue()],
test: {
environment: 'happy-dom',
include: ['app/**/*.test.ts'],
coverage: {
provider: 'v8',
reporter: ['text', 'html', 'lcov'],
exclude: [
'node_modules',
'tests',
'**/*.stories.ts',
'**/*.d.ts',
],
thresholds: {
statements: 60,
branches: 60,
functions: 60,
lines: 60,
},
},
globals: true,
},
resolve: {
alias: {
'~': fileURLToPath(new URL('./app', import.meta.url)),
},
},
})
Scripts
{
"scripts": {
"test": "vitest",
"test:unit": "vitest run",
"test:coverage": "vitest run --coverage"
}
}
Проверка
# Создать тестовый файл
cat > app/utils/example.test.ts << 'EOF'
import { describe, it, expect } from 'vitest'
describe('Example', () => {
it('works', () => {
expect(1 + 1).toBe(2)
})
})
EOF
# Запустить
pnpm test:unit
Ожидаемый результат: Тест проходит.
2. Storybook
Установка
pnpm dlx storybook@latest init --type vue3-vite
Конфигурация для Nuxt
// .storybook/main.ts
import type { StorybookConfig } from '@storybook/vue3-vite'
const config: StorybookConfig = {
stories: ['../app/**/*.stories.@(js|jsx|ts|tsx)'],
addons: [
'@storybook/addon-essentials',
'@storybook/addon-interactions',
],
framework: {
name: '@storybook/vue3-vite',
options: {},
},
viteFinal: async (config) => {
config.resolve = config.resolve || {}
config.resolve.alias = {
...config.resolve.alias,
'~': new URL('../app', import.meta.url).pathname,
}
return config
},
}
export default config
// .storybook/preview.ts
import type { Preview } from '@storybook/vue3'
import '../app/assets/css/main.css' // Tailwind styles
const preview: Preview = {
parameters: {
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
},
}
export default preview
Scripts
{
"scripts": {
"storybook": "storybook dev -p 6006",
"storybook:build": "storybook build"
}
}
Проверка
pnpm storybook
Ожидаемый результат: Storybook открывается на http://localhost:6006
3. Chromatic
Установка
pnpm add -D chromatic
Получение токена
- Зайти на https://www.chromatic.com/
- Создать проект / Выбрать существующий
- Скопировать Project Token
Scripts
{
"scripts": {
"chromatic": "chromatic --exit-zero-on-changes"
}
}
CI интеграция
# .github/workflows/chromatic.yml
name: Chromatic
on: push
jobs:
chromatic:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- run: pnpm install
- run: pnpm chromatic
env:
CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
Проверка
CHROMATIC_PROJECT_TOKEN=<token> pnpm chromatic
Ожидаемый результат: Build загружен в Chromatic.
4. Cypress
Установка
pnpm add -D cypress
Конфигурация
// cypress.config.ts
import { defineConfig } from 'cypress'
export default defineConfig({
e2e: {
baseUrl: 'http://localhost:3000',
specPattern: 'tests/e2e/**/*.cy.ts',
supportFile: 'tests/e2e/support/e2e.ts',
experimentalStudio: true,
viewportWidth: 1280,
viewportHeight: 720,
},
})
Структура
mkdir -p tests/e2e/support
// tests/e2e/support/e2e.ts
import './commands'
// tests/e2e/support/commands.ts
declare global {
namespace Cypress {
interface Chainable {
login(email: string, password: string): Chainable<void>
}
}
}
Cypress.Commands.add('login', (email: string, password: string) => {
cy.session([email, password], () => {
cy.visit('/login')
cy.get('[data-testid="email"]').type(email)
cy.get('[data-testid="password"]').type(password)
cy.get('[data-testid="submit"]').click()
cy.url().should('not.include', '/login')
})
})
export {}
TypeScript
// tests/e2e/tsconfig.json
{
"compilerOptions": {
"target": "ES2020",
"lib": ["ES2020", "DOM"],
"types": ["cypress"]
},
"include": ["**/*.ts"]
}
Scripts
{
"scripts": {
"test:e2e": "cypress run",
"test:e2e:open": "cypress open"
}
}
Проверка
# Создать тестовый файл
cat > tests/e2e/smoke.cy.ts << 'EOF'
describe('Smoke Test', () => {
it('loads homepage', () => {
cy.visit('/')
cy.get('body').should('be.visible')
})
})
EOF
# Запустить (dev сервер должен работать)
pnpm test:e2e:open
Ожидаемый результат: Cypress открывается, тест проходит.
5. Artillery
Установка
pnpm add -D artillery
Конфигурация
# tests/load/api-load.yml
config:
target: "{{ $processEnvironment.API_URL }}"
phases:
- duration: 30
arrivalRate: 5
name: "Warm up"
- duration: 60
arrivalRate: 20
name: "Load"
defaults:
headers:
Authorization: "Bearer {{ $processEnvironment.TEST_TOKEN }}"
scenarios:
- name: "Get orders"
flow:
- get:
url: "/api/orders"
- think: 1
Scripts
{
"scripts": {
"test:load": "artillery run tests/load/api-load.yml"
}
}
Проверка
API_URL=https://api.example.com TEST_TOKEN=xxx pnpm test:load
Ожидаемый результат: Отчёт о нагрузке выводится в консоль.
6. Unlighthouse
Установка
pnpm add -D @unlighthouse/cli puppeteer
Конфигурация
// unlighthouse.config.ts
export default {
site: process.env.SITE_URL || 'http://localhost:3000',
scanner: {
device: 'desktop',
throttle: true,
},
ci: {
budget: {
performance: 80,
accessibility: 90,
'best-practices': 80,
seo: 80,
},
},
outputPath: '.unlighthouse',
}
Scripts
{
"scripts": {
"unlighthouse": "unlighthouse",
"unlighthouse:ci": "unlighthouse-ci"
}
}
Проверка
pnpm unlighthouse --site http://localhost:3000
Ожидаемый результат: Отчёт генерируется в .unlighthouse/
7. Sentry
Установка
pnpm add @sentry/nuxt
Конфигурация
// nuxt.config.ts
export default defineNuxtConfig({
modules: ['@sentry/nuxt/module'],
sentry: {
dsn: process.env.SENTRY_DSN,
// Для загрузки source maps
sourceMapsUploadOptions: {
org: process.env.SENTRY_ORG,
project: process.env.SENTRY_PROJECT,
authToken: process.env.SENTRY_AUTH_TOKEN,
},
},
sourcemap: {
client: true,
},
})
Environment variables
# .env
SENTRY_DSN=https://xxx@sentry.io/xxx
SENTRY_ORG=your-org
SENTRY_PROJECT=your-project
SENTRY_AUTH_TOKEN=xxx
Проверка
// Временно добавить в любую страницу
throw new Error('Test Sentry error')
Ожидаемый результат: Ошибка появляется в Sentry dashboard.
8. CI Pipeline
GitHub Actions
# .github/workflows/ci.yml
name: CI
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
lint:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- run: pnpm install
- run: pnpm lint
- run: pnpm typecheck
unit-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- run: pnpm install
- run: pnpm test:coverage
- uses: codecov/codecov-action@v3
chromatic:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- run: pnpm install
- run: pnpm chromatic
env:
CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
e2e:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- run: pnpm install
- run: pnpm build
- run: pnpm preview &
- run: pnpm test:e2e
performance:
runs-on: ubuntu-latest
if: github.ref == 'refs/heads/main'
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v2
- uses: actions/setup-node@v4
with:
node-version: 20
cache: 'pnpm'
- run: pnpm install
- run: pnpm build
- run: pnpm preview &
- run: pnpm unlighthouse:ci
Troubleshooting
Vitest: Cannot find module
Симптомы: Cannot find module '~/...'
Решение: Проверить alias в vitest.config.ts:
resolve: {
alias: {
'~': fileURLToPath(new URL('./app', import.meta.url)),
},
}
Storybook: Component not rendering
Симптомы: Компонент пустой в Storybook
Решение: Проверить импорт стилей в .storybook/preview.ts
Cypress: Element not found
Симптомы: Timed out retrying: Expected to find element
Решение:
- Добавить
data-testidк элементу - Увеличить timeout:
cy.get('[data-testid="x"]', { timeout: 10000 })
Chromatic: Build failed
Симптомы: Chromatic build fails
Решение:
- Проверить
pnpm storybook:buildлокально - Проверить token:
echo $CHROMATIC_PROJECT_TOKEN
Финальный чек-лист
- Vitest работает (
pnpm test:unit) - Storybook запускается (
pnpm storybook) - Chromatic настроен (build загружается)
- Cypress работает (
pnpm test:e2e:open) - Artillery настроен
- Unlighthouse работает
- Sentry получает ошибки
- CI pipeline настроен