Files
team-planner/tests/e2e/phase2.spec.ts
2026-01-15 00:18:35 +03:00

674 lines
26 KiB
TypeScript
Raw 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.

import { test, expect } from '@playwright/test';
/**
* E2E тесты для Фазы 2 Team Planner
* - Drag & Drop
* - Цветовая маркировка
* - Комментарии
* - Управление командой
*
* Используем data-testid для стабильных селекторов
*/
test.describe('Фаза 2: Drag & Drop', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/');
await page.waitForSelector('[data-testid="ideas-table"]', { timeout: 10000 });
});
test('Drag handle присутствует в каждой строке', async ({ page }) => {
// Проверяем есть ли данные
const emptyState = page.locator('[data-testid="ideas-empty-state"]');
const hasData = !(await emptyState.isVisible().catch(() => false));
if (hasData) {
const dragHandles = page.locator('[data-testid="drag-handle"]');
const handleCount = await dragHandles.count();
expect(handleCount).toBeGreaterThan(0);
}
});
test('Drag handle имеет правильный курсор', async ({ page }) => {
const emptyState = page.locator('[data-testid="ideas-empty-state"]');
const hasData = !(await emptyState.isVisible().catch(() => false));
test.skip(!hasData, 'Нет данных для тестирования');
const dragHandle = page.locator('[data-testid="drag-handle"]').first();
await expect(dragHandle).toBeVisible();
// Проверяем что элемент имеет cursor: grab
const cursor = await dragHandle.evaluate((el) => getComputedStyle(el).cursor);
expect(cursor).toBe('grab');
});
test('Drag & Drop изменяет порядок строк', async ({ page }) => {
const ideaRows = page.locator('[data-testid^="idea-row-"]');
const rowCount = await ideaRows.count();
test.skip(rowCount < 2, 'Недостаточно данных для тестирования D&D');
// Находим drag handle первой строки
const firstHandle = page.locator('[data-testid="drag-handle"]').first();
const box = await firstHandle.boundingBox();
if (box) {
// Выполняем drag & drop
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
await page.mouse.down();
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2 + 80, { steps: 10 });
await page.waitForTimeout(300);
await page.mouse.up();
// Ждём обновления
await page.waitForTimeout(500);
}
// Тест прошёл без ошибок
expect(true).toBeTruthy();
});
test('DragOverlay появляется при перетаскивании', async ({ page }) => {
const emptyState = page.locator('[data-testid="ideas-empty-state"]');
const hasData = !(await emptyState.isVisible().catch(() => false));
test.skip(!hasData, 'Нет данных для тестирования');
const dragHandle = page.locator('[data-testid="drag-handle"]').first();
const box = await dragHandle.boundingBox();
if (box) {
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
await page.mouse.down();
await page.mouse.move(box.x + box.width / 2, box.y + box.height / 2 + 30, { steps: 5 });
await page.waitForTimeout(200);
await page.mouse.up();
}
expect(true).toBeTruthy();
});
});
test.describe('Фаза 2: Цветовая маркировка', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/');
await page.waitForSelector('[data-testid="ideas-table"]', { timeout: 10000 });
});
test('Фильтр по цвету присутствует', async ({ page }) => {
const colorFilter = page.locator('[data-testid="filter-color"]');
await expect(colorFilter).toBeVisible();
});
test('Color picker trigger присутствует в строке', async ({ page }) => {
const emptyState = page.locator('[data-testid="ideas-empty-state"]');
const hasData = !(await emptyState.isVisible().catch(() => false));
test.skip(!hasData, 'Нет данных для тестирования');
const colorTrigger = page.locator('[data-testid="color-picker-trigger"]').first();
await expect(colorTrigger).toBeVisible();
});
test('Color picker открывается по клику', async ({ page }) => {
const emptyState = page.locator('[data-testid="ideas-empty-state"]');
const hasData = !(await emptyState.isVisible().catch(() => false));
test.skip(!hasData, 'Нет данных для тестирования');
// Кликаем на trigger
const colorTrigger = page.locator('[data-testid="color-picker-trigger"]').first();
await colorTrigger.click();
// Ждём появления Popover
const popover = page.locator('[data-testid="color-picker-popover"]');
await expect(popover).toBeVisible({ timeout: 3000 });
// Закрываем popover
await page.keyboard.press('Escape');
});
test('Color picker содержит цветовые опции', async ({ page }) => {
const emptyState = page.locator('[data-testid="ideas-empty-state"]');
const hasData = !(await emptyState.isVisible().catch(() => false));
test.skip(!hasData, 'Нет данных для тестирования');
const colorTrigger = page.locator('[data-testid="color-picker-trigger"]').first();
await colorTrigger.click();
const popover = page.locator('[data-testid="color-picker-popover"]');
await expect(popover).toBeVisible();
// Проверяем что есть цветные опции
const colorOptions = page.locator('[data-testid^="color-option-"]');
const count = await colorOptions.count();
expect(count).toBeGreaterThanOrEqual(8); // 8 предустановленных цветов
await page.keyboard.press('Escape');
});
test('Выбор цвета изменяет фон строки', async ({ page }) => {
const emptyState = page.locator('[data-testid="ideas-empty-state"]');
const hasData = !(await emptyState.isVisible().catch(() => false));
test.skip(!hasData, 'Нет данных для тестирования');
const colorTrigger = page.locator('[data-testid="color-picker-trigger"]').first();
await colorTrigger.click();
const popover = page.locator('[data-testid="color-picker-popover"]');
await expect(popover).toBeVisible({ timeout: 3000 });
// Выбираем первый цвет
const colorOption = page.locator('[data-testid^="color-option-"]').first();
await expect(colorOption).toBeVisible();
await colorOption.click({ force: true });
// Ждём закрытия popover
await expect(popover).toBeHidden({ timeout: 3000 });
// Проверяем что строка получила цветной фон
await page.waitForTimeout(300);
const firstRow = page.locator('[data-testid^="idea-row-"]').first();
const rowStyle = await firstRow.evaluate((el) => {
const bg = getComputedStyle(el).backgroundColor;
return bg;
});
// Фон не должен быть прозрачным
expect(rowStyle).not.toBe('rgba(0, 0, 0, 0)');
});
test('Фильтр по цвету открывает dropdown с опциями', async ({ page }) => {
const colorFilter = page.locator('[data-testid="filter-color"]');
const select = colorFilter.locator('[role="combobox"]');
await select.click();
// Проверяем что появился dropdown с цветами
const listbox = page.locator('[role="listbox"]');
await expect(listbox).toBeVisible();
// Должны быть опции цветов
const options = listbox.locator('[role="option"]');
const count = await options.count();
expect(count).toBeGreaterThanOrEqual(8); // "Все" + 8 цветов
await page.keyboard.press('Escape');
});
test('Можно сбросить цвет (кнопка Clear)', async ({ page }) => {
const emptyState = page.locator('[data-testid="ideas-empty-state"]');
const hasData = !(await emptyState.isVisible().catch(() => false));
test.skip(!hasData, 'Нет данных для тестирования');
const colorTrigger = page.locator('[data-testid="color-picker-trigger"]').first();
await colorTrigger.click();
const popover = page.locator('[data-testid="color-picker-popover"]');
await expect(popover).toBeVisible();
// Ищем кнопку очистки
const clearButton = page.locator('[data-testid="color-clear-button"]');
await expect(clearButton).toBeVisible();
await page.keyboard.press('Escape');
});
});
test.describe('Фаза 2: Комментарии', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/');
await page.waitForSelector('[data-testid="ideas-table"]', { timeout: 10000 });
});
test('Кнопка комментариев присутствует в строке', async ({ page }) => {
const emptyState = page.locator('[data-testid="ideas-empty-state"]');
const hasData = !(await emptyState.isVisible().catch(() => false));
test.skip(!hasData, 'Нет данных для тестирования');
const commentButtons = page.locator('[data-testid="toggle-comments-button"]');
const count = await commentButtons.count();
expect(count).toBeGreaterThan(0);
});
test('Панель комментариев открывается по клику', async ({ page }) => {
const emptyState = page.locator('[data-testid="ideas-empty-state"]');
const hasData = !(await emptyState.isVisible().catch(() => false));
test.skip(!hasData, 'Нет данных для тестирования');
// Кликаем на кнопку комментариев
const commentButton = page.locator('[data-testid="toggle-comments-button"]').first();
await commentButton.click();
// Ждём появления панели комментариев
await page.waitForTimeout(500);
const commentsPanel = page.locator('[data-testid="comments-panel"]');
await expect(commentsPanel).toBeVisible();
});
test('Панель комментариев показывает пустое состояние или комментарии', async ({ page }) => {
const emptyState = page.locator('[data-testid="ideas-empty-state"]');
const hasData = !(await emptyState.isVisible().catch(() => false));
test.skip(!hasData, 'Нет данных для тестирования');
// Открываем комментарии
const commentButton = page.locator('[data-testid="toggle-comments-button"]').first();
await commentButton.click();
await page.waitForTimeout(500);
// Проверяем что есть либо пустое состояние, либо список комментариев
const commentsEmpty = page.locator('[data-testid="comments-empty"]');
const commentsList = page.locator('[data-testid="comments-list"]');
const hasNoComments = await commentsEmpty.isVisible().catch(() => false);
const hasComments = await commentsList.isVisible().catch(() => false);
expect(hasNoComments || hasComments).toBeTruthy();
});
test('Форма добавления комментария присутствует', async ({ page }) => {
const emptyState = page.locator('[data-testid="ideas-empty-state"]');
const hasData = !(await emptyState.isVisible().catch(() => false));
test.skip(!hasData, 'Нет данных для тестирования');
const commentButton = page.locator('[data-testid="toggle-comments-button"]').first();
await commentButton.click();
await page.waitForTimeout(500);
const commentForm = page.locator('[data-testid="comment-form"]');
await expect(commentForm).toBeVisible();
const commentInput = page.locator('[data-testid="comment-input"]');
await expect(commentInput).toBeVisible();
const submitButton = page.locator('[data-testid="submit-comment-button"]');
await expect(submitButton).toBeVisible();
});
test('Можно добавить комментарий', async ({ page }) => {
const emptyState = page.locator('[data-testid="ideas-empty-state"]');
const hasData = !(await emptyState.isVisible().catch(() => false));
test.skip(!hasData, 'Нет данных для тестирования');
// Открываем комментарии
const commentButton = page.locator('[data-testid="toggle-comments-button"]').first();
await commentButton.click();
await page.waitForTimeout(500);
// Вводим текст комментария
const testComment = `Тестовый комментарий ${Date.now()}`;
const commentInput = page.locator('[data-testid="comment-input"]');
await commentInput.fill(testComment);
// Нажимаем кнопку отправки
const sendButton = page.locator('[data-testid="submit-comment-button"]');
await sendButton.click();
// Ждём сохранения
await page.waitForTimeout(1000);
// Проверяем что комментарий появился
const addedComment = page.locator(`text=${testComment}`);
await expect(addedComment).toBeVisible({ timeout: 5000 });
});
test('Можно удалить комментарий', async ({ page }) => {
const emptyState = page.locator('[data-testid="ideas-empty-state"]');
const hasData = !(await emptyState.isVisible().catch(() => false));
test.skip(!hasData, 'Нет данных для тестирования');
// Открываем комментарии
const commentButton = page.locator('[data-testid="toggle-comments-button"]').first();
await commentButton.click();
await page.waitForTimeout(500);
// Проверяем есть ли комментарии для удаления
const deleteButtons = page.locator('[data-testid="delete-comment-button"]');
const deleteCount = await deleteButtons.count();
test.skip(deleteCount === 0, 'Нет комментариев для удаления');
const initialCount = deleteCount;
// Кликаем на удаление первого комментария
await deleteButtons.first().click();
// Ждём удаления
await page.waitForTimeout(1000);
// Проверяем что количество уменьшилось
const newDeleteCount = await deleteButtons.count();
expect(newDeleteCount).toBeLessThan(initialCount);
});
test('Повторный клик закрывает панель комментариев', async ({ page }) => {
const emptyState = page.locator('[data-testid="ideas-empty-state"]');
const hasData = !(await emptyState.isVisible().catch(() => false));
test.skip(!hasData, 'Нет данных для тестирования');
// Открываем комментарии
const commentButton = page.locator('[data-testid="toggle-comments-button"]').first();
await commentButton.click();
await page.waitForTimeout(500);
// Проверяем что панель открыта
const commentsPanel = page.locator('[data-testid="comments-panel"]');
await expect(commentsPanel).toBeVisible();
// Кликаем ещё раз чтобы закрыть
await commentButton.click();
await page.waitForTimeout(500);
// Панель должна закрыться
await expect(commentsPanel).toBeHidden();
});
});
test.describe('Фаза 2: Управление командой - Навигация', () => {
test('Таб "Команда" присутствует', async ({ page }) => {
await page.goto('/');
await page.waitForSelector('body');
// Ищем таб "Команда"
const teamTab = page.locator('[role="tab"]').filter({ hasText: 'Команда' });
await expect(teamTab).toBeVisible();
});
test('Клик на таб "Команда" переключает контент', async ({ page }) => {
await page.goto('/');
await page.waitForSelector('[data-testid="ideas-table"]', { timeout: 10000 });
// Переходим на вкладку "Команда"
const teamTab = page.locator('[role="tab"]').filter({ hasText: 'Команда' });
await teamTab.click();
// Ждём загрузки
await page.waitForTimeout(500);
// Должна появиться страница команды
const teamPage = page.locator('[data-testid="team-page"]');
await expect(teamPage).toBeVisible({ timeout: 5000 });
});
});
test.describe('Фаза 2: Управление командой - Сводка', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/');
await page.waitForSelector('body');
// Переходим на вкладку "Команда"
const teamTab = page.locator('[role="tab"]').filter({ hasText: 'Команда' });
await teamTab.click();
await page.waitForTimeout(500);
});
test('Страница команды отображается', async ({ page }) => {
const teamPage = page.locator('[data-testid="team-page"]');
await expect(teamPage).toBeVisible();
});
test('Вкладки "Участники" и "Роли" присутствуют', async ({ page }) => {
const membersTab = page.locator('[data-testid="team-tab-members"]');
const rolesTab = page.locator('[data-testid="team-tab-roles"]');
await expect(membersTab).toBeVisible();
await expect(rolesTab).toBeVisible();
});
test('Сводка по ролям отображается', async ({ page }) => {
const summarySection = page.locator('[data-testid="team-summary"]');
await expect(summarySection).toBeVisible();
});
test('Карточки ролей присутствуют', async ({ page }) => {
// Карточки с количеством участников по ролям
const roleCards = page.locator('[data-testid^="role-card-"]');
const count = await roleCards.count();
// Может не быть карточек если нет участников с ролями
expect(count).toBeGreaterThanOrEqual(0);
});
});
test.describe('Фаза 2: Управление командой - Таблица участников', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/');
await page.waitForSelector('body');
// Переходим на вкладку "Команда"
const teamTab = page.locator('[role="tab"]').filter({ hasText: 'Команда' });
await teamTab.click();
await page.waitForTimeout(500);
});
test('Таблица участников присутствует', async ({ page }) => {
const table = page.locator('[data-testid="team-table"]');
await expect(table).toBeVisible();
});
test('Кнопка "Добавить" присутствует', async ({ page }) => {
const addButton = page.locator('[data-testid="add-team-member-button"]');
await expect(addButton).toBeVisible();
});
test('Таблица показывает данные или empty state', async ({ page }) => {
const emptyState = page.locator('[data-testid="team-empty-state"]');
const memberRows = page.locator('[data-testid^="team-member-row-"]');
const hasEmptyState = await emptyState.isVisible().catch(() => false);
const rowCount = await memberRows.count();
expect(hasEmptyState || rowCount > 0).toBeTruthy();
});
});
test.describe('Фаза 2: Управление командой - CRUD участников', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/');
await page.waitForSelector('body');
// Переходим на вкладку "Команда"
const teamTab = page.locator('[role="tab"]').filter({ hasText: 'Команда' });
await teamTab.click();
await page.waitForTimeout(500);
});
test('Модалка добавления открывается по кнопке', async ({ page }) => {
const addButton = page.locator('[data-testid="add-team-member-button"]');
await addButton.click();
// Ждём модалку
const modal = page.locator('[data-testid="team-member-modal"]');
await expect(modal).toBeVisible({ timeout: 3000 });
// Закрываем
await page.keyboard.press('Escape');
});
test('Модалка содержит необходимые поля', async ({ page }) => {
const addButton = page.locator('[data-testid="add-team-member-button"]');
await addButton.click();
const modal = page.locator('[data-testid="team-member-modal"]');
await expect(modal).toBeVisible();
// Проверяем форму
const form = page.locator('[data-testid="team-member-form"]');
await expect(form).toBeVisible();
// Поле "Имя"
const nameInput = page.locator('[data-testid="member-name-input"]');
await expect(nameInput).toBeVisible();
// Select "Роль"
const roleSelect = page.locator('[data-testid="member-role-select"]');
await expect(roleSelect).toBeVisible();
// Кнопки
const cancelButton = page.locator('[data-testid="cancel-member-button"]');
await expect(cancelButton).toBeVisible();
const submitButton = page.locator('[data-testid="submit-member-button"]');
await expect(submitButton).toBeVisible();
await page.keyboard.press('Escape');
});
test('Можно добавить нового участника', async ({ page }) => {
const addButton = page.locator('[data-testid="add-team-member-button"]');
await addButton.click();
const modal = page.locator('[data-testid="team-member-modal"]');
await expect(modal).toBeVisible();
// Заполняем имя
const testName = `Тестовый участник ${Date.now()}`;
const nameInput = page.locator('[data-testid="member-name-input"] input');
await nameInput.fill(testName);
// Кликаем "Добавить"
const submitButton = page.locator('[data-testid="submit-member-button"]');
await submitButton.click();
// Ждём закрытия модалки
await expect(modal).toBeHidden({ timeout: 5000 });
// Проверяем что участник появился в таблице
await page.waitForTimeout(500);
const newMember = page.locator(`text=${testName}`);
await expect(newMember).toBeVisible();
});
test('Кнопка редактирования присутствует в строке', async ({ page }) => {
const memberRows = page.locator('[data-testid^="team-member-row-"]');
const rowCount = await memberRows.count();
test.skip(rowCount === 0, 'Нет участников для редактирования');
const editButtons = page.locator('[data-testid="edit-team-member-button"]');
const editCount = await editButtons.count();
expect(editCount).toBeGreaterThan(0);
});
test('Модалка редактирования открывается с данными', async ({ page }) => {
const editButton = page.locator('[data-testid="edit-team-member-button"]').first();
const isVisible = await editButton.isVisible().catch(() => false);
test.skip(!isVisible, 'Нет участников для редактирования');
await editButton.click();
const modal = page.locator('[data-testid="team-member-modal"]');
await expect(modal).toBeVisible();
// Поле имени должно быть заполнено
const nameInput = page.locator('[data-testid="member-name-input"] input');
const nameValue = await nameInput.inputValue();
expect(nameValue.length).toBeGreaterThan(0);
await page.keyboard.press('Escape');
});
test('Кнопка удаления присутствует в строке', async ({ page }) => {
const memberRows = page.locator('[data-testid^="team-member-row-"]');
const rowCount = await memberRows.count();
test.skip(rowCount === 0, 'Нет участников для удаления');
const deleteButtons = page.locator('[data-testid="delete-team-member-button"]');
const deleteCount = await deleteButtons.count();
expect(deleteCount).toBeGreaterThan(0);
});
});
test.describe('Фаза 2: Управление командой - Вкладка Роли', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/');
await page.waitForSelector('body');
// Переходим на вкладку "Команда"
const teamTab = page.locator('[role="tab"]').filter({ hasText: 'Команда' });
await teamTab.click();
await page.waitForTimeout(500);
});
test('Вкладка "Роли" присутствует на странице команды', async ({ page }) => {
const rolesTab = page.locator('[data-testid="team-tab-roles"]');
await expect(rolesTab).toBeVisible();
});
test('Переключение на вкладку "Роли" работает', async ({ page }) => {
const rolesTab = page.locator('[data-testid="team-tab-roles"]');
await rolesTab.click();
await page.waitForTimeout(500);
// Должен появиться RolesManager
const rolesManager = page.locator('[data-testid="roles-manager"]');
await expect(rolesManager).toBeVisible({ timeout: 5000 });
});
test('Таблица ролей присутствует', async ({ page }) => {
const rolesTab = page.locator('[data-testid="team-tab-roles"]');
await rolesTab.click();
await page.waitForTimeout(500);
const rolesTable = page.locator('[data-testid="roles-table"]');
await expect(rolesTable).toBeVisible();
});
test('Кнопка добавления роли присутствует', async ({ page }) => {
const rolesTab = page.locator('[data-testid="team-tab-roles"]');
await rolesTab.click();
await page.waitForTimeout(500);
const addRoleButton = page.locator('[data-testid="add-role-button"]');
await expect(addRoleButton).toBeVisible();
});
test('Модалка добавления роли открывается', async ({ page }) => {
const rolesTab = page.locator('[data-testid="team-tab-roles"]');
await rolesTab.click();
await page.waitForTimeout(500);
const addRoleButton = page.locator('[data-testid="add-role-button"]');
await addRoleButton.click();
const modal = page.locator('[data-testid="role-modal"]');
await expect(modal).toBeVisible();
// Проверяем поля
const nameInput = page.locator('[data-testid="role-name-input"]');
await expect(nameInput).toBeVisible();
const labelInput = page.locator('[data-testid="role-label-input"]');
await expect(labelInput).toBeVisible();
await page.keyboard.press('Escape');
});
});