Files
team-planner/E2E_TESTING.md
vigdorov 2e46cc41a1
Some checks failed
continuous-integration/drone/push Build is failing
fix lint
2026-01-15 02:36:24 +03:00

299 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# E2E Testing Guide
Руководство по написанию e2e тестов для Team Planner.
## Принципы
### 1. Тесты следуют требованиям, а не коду
Тесты должны проверять **пользовательские сценарии** из требований, а не адаптироваться под текущую реализацию.
```
❌ Плохо: "Проверить что кнопка имеет класс .MuiButton-contained"
✅ Хорошо: "Проверить что пользователь может создать новую идею"
```
**Порядок работы:**
1. Прочитать требования к фазе/фиче в `ROADMAP.md` и `REQUIREMENTS.md`
2. Выделить пользовательские сценарии
3. Написать тесты для каждого сценария
4. Убедиться что тесты проверяют бизнес-логику, а не детали реализации
### 2. Стабильные селекторы через data-testid
**Никогда не использовать:**
- Позиционные селекторы: `tbody tr`, `.nth(2)`, `:first-child`
- CSS классы MUI: `.MuiButton-root`, `.MuiTableCell-body`
- Структурные селекторы: `table > tbody > tr > td`
**Всегда использовать:**
- `data-testid` для уникальной идентификации элементов
- `[role="..."]` только для стандартных ARIA ролей (tab, dialog, listbox)
- Текстовые селекторы только для статичного контента
```typescript
// ❌ Плохо - сломается при изменении структуры
const row = page.locator('tbody tr').nth(2);
const button = page.locator('.MuiIconButton-root').first();
// ✅ Хорошо - стабильно при рефакторинге
const row = page.locator('[data-testid="idea-row-123"]');
const button = page.locator('[data-testid="delete-idea-button"]');
```
## Соглашения по data-testid
### Именование
| Паттерн | Пример | Использование |
|---------|--------|---------------|
| `{component}-{element}` | `ideas-table` | Основные элементы |
| `{component}-{element}-{id}` | `idea-row-123` | Динамические элементы |
| `{action}-{target}-button` | `delete-idea-button` | Кнопки действий |
| `{name}-input` | `member-name-input` | Поля ввода |
| `{name}-modal` | `team-member-modal` | Модальные окна |
| `filter-{name}` | `filter-status` | Фильтры |
### Обязательные data-testid по компонентам
#### Таблицы
```
{name}-table - сам table элемент
{name}-table-container - обёртка таблицы
{name}-empty-state - состояние "нет данных"
{item}-row-{id} - строка с данными
```
#### Формы и модалки
```
{name}-modal - Dialog компонент
{name}-form - form элемент
{field}-input - поля ввода (TextField)
{field}-select - выпадающие списки (FormControl)
submit-{action}-button - кнопка отправки
cancel-{action}-button - кнопка отмены
```
#### Действия в строках
```
edit-{item}-button - редактирование
delete-{item}-button - удаление
toggle-{feature}-button - переключение
```
## Работа с MUI компонентами
### Popover / Menu
MUI Popover рендерится через Portal в `<body>`. Для добавления `data-testid` используй `slotProps`:
```tsx
<Popover
slotProps={{
paper: {
'data-testid': 'color-picker-popover',
} as React.HTMLAttributes<HTMLDivElement>,
}}
>
```
### Dialog
Dialog также использует Portal. Добавляй `data-testid` напрямую:
```tsx
<Dialog data-testid="team-member-modal">
```
### Select / Combobox
Для работы с MUI Select:
```typescript
// Открыть dropdown
await page.locator('[data-testid="filter-status"] [role="combobox"]').click();
// Выбрать опцию из listbox
const listbox = page.locator('[role="listbox"]');
await listbox.locator('[role="option"]').filter({ hasText: 'Бэклог' }).click();
```
### TextField
TextField в MUI оборачивает input в несколько div. Для доступа к самому input:
```typescript
// data-testid на TextField
<TextField data-testid="member-name-input" />
// В тесте - добавляем input селектор
const input = page.locator('[data-testid="member-name-input"] input');
await input.fill('Имя');
```
## Структура тестов
### Файловая организация
```
tests/
├── e2e/
│ ├── auth.setup.ts # Аутентификация (запускается первой)
│ ├── phase1.spec.ts # Тесты фазы 1
│ ├── phase2.spec.ts # Тесты фазы 2
│ └── phase3.spec.ts # Тесты фазы 3
└── playwright.config.ts
```
### Шаблон тестового файла
```typescript
import { test, expect } from '@playwright/test';
/**
* E2E тесты для Фазы N Team Planner
* - Фича 1
* - Фича 2
*
* Используем data-testid для стабильных селекторов
*/
test.describe('Фаза N: Название фичи', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/');
// Ждём загрузки основного элемента
await page.waitForSelector('[data-testid="main-element"]', { timeout: 10000 });
});
test('Описание сценария', async ({ page }) => {
// Arrange - подготовка
const element = page.locator('[data-testid="element"]');
// Act - действие
await element.click();
// Assert - проверка
await expect(element).toBeVisible();
});
});
```
### Группировка тестов
Группируй тесты по фичам/сценариям, а не по компонентам:
```typescript
// ❌ Плохо - группировка по компонентам
test.describe('Button tests', () => { ... });
test.describe('Modal tests', () => { ... });
// ✅ Хорошо - группировка по фичам
test.describe('Фаза 2: Управление командой - CRUD участников', () => { ... });
test.describe('Фаза 2: Управление командой - Вкладка Роли', () => { ... });
```
## Обработка edge cases
### Проверка наличия данных
```typescript
test('Тест с данными', async ({ page }) => {
const emptyState = page.locator('[data-testid="ideas-empty-state"]');
const hasData = !(await emptyState.isVisible().catch(() => false));
// Пропускаем тест если нет данных
test.skip(!hasData, 'Нет данных для тестирования');
// Продолжаем тест...
});
```
### Работа с динамическими ID
```typescript
// Для элементов с динамическими ID используй prefix-селектор
const ideaRows = page.locator('[data-testid^="idea-row-"]');
const rowCount = await ideaRows.count();
```
### Ожидание после действий
```typescript
// После клика, который вызывает API запрос
await button.click();
await page.waitForTimeout(500); // Даём время на запрос
// Лучше - ждать конкретный результат
await expect(newElement).toBeVisible({ timeout: 5000 });
```
## Чеклист перед написанием тестов
- [ ] Прочитаны требования к фиче в ROADMAP.md
- [ ] Определены пользовательские сценарии
- [ ] Проверено наличие data-testid в компонентах
- [ ] Если data-testid отсутствуют - добавить их в компоненты
- [ ] Тесты не зависят от порядка/позиции элементов в DOM
- [ ] Тесты корректно обрабатывают случай отсутствия данных
## Добавление data-testid в компоненты
При добавлении новых компонентов или фич, сразу добавляй data-testid:
```tsx
// Таблица
<Table data-testid="ideas-table">
<TableBody>
{items.map(item => (
<TableRow key={item.id} data-testid={`idea-row-${item.id}`}>
<TableCell>
<IconButton data-testid="delete-idea-button">
<Delete />
</IconButton>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
// Модалка с формой
<Dialog data-testid="create-idea-modal">
<form data-testid="create-idea-form">
<TextField data-testid="idea-title-input" />
<Button data-testid="submit-create-idea">Создать</Button>
<Button data-testid="cancel-create-idea">Отмена</Button>
</form>
</Dialog>
```
## Запуск тестов
```bash
# Все тесты (из корня проекта)
npm run test
# Конкретный файл
npx playwright test e2e/phase2.spec.ts
# Конкретный тест по имени
npx playwright test -g "Drag handle имеет правильный курсор"
# С UI режимом для отладки
npx playwright test --ui
# Только упавшие тесты
npx playwright test --last-failed
```
## Правила исправления тестов
**ВАЖНО:** При исправлении сломанных тестов:
1. **НЕ запускай полный прогон** после каждого исправления
2. **Запускай только сломанный тест** для проверки исправления:
```bash
npx playwright test -g "Название теста"
```
3. **Полный прогон** делай только когда все сломанные тесты исправлены
4. Это экономит время и ресурсы при отладке