284 lines
9.5 KiB
Markdown
284 lines
9.5 KiB
Markdown
# 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
|
||
# Все тесты
|
||
npx playwright test
|
||
|
||
# Конкретный файл
|
||
npx playwright test e2e/phase2.spec.ts
|
||
|
||
# С UI режимом для отладки
|
||
npx playwright test --ui
|
||
|
||
# Только упавшие тесты
|
||
npx playwright test --last-failed
|
||
```
|