Runbooks

Настройка тестирования

Пошаговая инструкция по настройке стека тестирования в проекте.

Пошаговая инструкция по настройке стека тестирования в проекте.

Применимо для: 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

Получение токена

  1. Зайти на https://www.chromatic.com/
  2. Создать проект / Выбрать существующий
  3. Скопировать 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

Решение:

  1. Добавить data-testid к элементу
  2. Увеличить timeout: cy.get('[data-testid="x"]', { timeout: 10000 })

Chromatic: Build failed

Симптомы: Chromatic build fails

Решение:

  1. Проверить pnpm storybook:build локально
  2. Проверить token: echo $CHROMATIC_PROJECT_TOKEN

Финальный чек-лист

  • Vitest работает (pnpm test:unit)
  • Storybook запускается (pnpm storybook)
  • Chromatic настроен (build загружается)
  • Cypress работает (pnpm test:e2e:open)
  • Artillery настроен
  • Unlighthouse работает
  • Sentry получает ошибки
  • CI pipeline настроен

Связанные документы