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'); }); });