import { test, expect } from '@playwright/test'; /** * E2E тесты для Фазы 3.2 Team Planner * - Детальный просмотр идеи (IdeaDetailModal) * - Управление видимостью колонок * * Используем data-testid для стабильных селекторов */ test.describe('Фаза 3.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)); if (hasData) { const viewButtons = page.locator('[data-testid="view-details-button"]'); const buttonCount = await viewButtons.count(); expect(buttonCount).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 viewButton = page.locator('[data-testid="view-details-button"]').first(); await viewButton.click(); const modal = page.locator('[data-testid="idea-detail-modal"]'); await expect(modal).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 viewButton = page.locator('[data-testid="view-details-button"]').first(); await viewButton.click(); const modal = page.locator('[data-testid="idea-detail-modal"]'); await expect(modal).toBeVisible({ timeout: 5000 }); // Проверяем наличие заголовка const title = modal.locator('[data-testid="idea-detail-title"]'); await expect(title).toBeVisible(); const titleText = await title.textContent(); expect(titleText?.length).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 viewButton = page.locator('[data-testid="view-details-button"]').first(); await viewButton.click(); const modal = page.locator('[data-testid="idea-detail-modal"]'); await expect(modal).toBeVisible({ timeout: 5000 }); // Проверяем наличие основных полей await expect(modal.locator('[data-testid="idea-detail-status"]')).toBeVisible(); await expect(modal.locator('[data-testid="idea-detail-priority"]')).toBeVisible(); await expect(modal.locator('[data-testid="idea-detail-description"]')).toBeVisible(); await expect(modal.locator('[data-testid="idea-detail-specification-status"]')).toBeVisible(); }); test('Кнопка редактирования переводит модалку в режим редактирования', async ({ page }) => { const emptyState = page.locator('[data-testid="ideas-empty-state"]'); const hasData = !(await emptyState.isVisible().catch(() => false)); test.skip(!hasData, 'Нет данных для тестирования'); const viewButton = page.locator('[data-testid="view-details-button"]').first(); await viewButton.click(); const modal = page.locator('[data-testid="idea-detail-modal"]'); await expect(modal).toBeVisible({ timeout: 5000 }); // Нажимаем "Редактировать" const editButton = modal.locator('[data-testid="idea-detail-edit-button"]'); await editButton.click(); // Должны появиться поля ввода await expect(modal.locator('[data-testid="idea-detail-title-input"]')).toBeVisible(); // И кнопки сохранения/отмены await expect(modal.locator('[data-testid="idea-detail-save-button"]')).toBeVisible(); await expect(modal.locator('[data-testid="idea-detail-cancel-button"]')).toBeVisible(); }); test('Кнопка "Отмена" возвращает режим просмотра', async ({ page }) => { const emptyState = page.locator('[data-testid="ideas-empty-state"]'); const hasData = !(await emptyState.isVisible().catch(() => false)); test.skip(!hasData, 'Нет данных для тестирования'); const viewButton = page.locator('[data-testid="view-details-button"]').first(); await viewButton.click(); const modal = page.locator('[data-testid="idea-detail-modal"]'); await expect(modal).toBeVisible({ timeout: 5000 }); // Нажимаем "Редактировать" await modal.locator('[data-testid="idea-detail-edit-button"]').click(); await expect(modal.locator('[data-testid="idea-detail-title-input"]')).toBeVisible(); // Нажимаем "Отмена" await modal.locator('[data-testid="idea-detail-cancel-button"]').click(); // Должен вернуться режим просмотра await expect(modal.locator('[data-testid="idea-detail-title"]')).toBeVisible(); await expect(modal.locator('[data-testid="idea-detail-edit-button"]')).toBeVisible(); }); test('Сохранение изменений работает корректно', async ({ page }) => { const emptyState = page.locator('[data-testid="ideas-empty-state"]'); const hasData = !(await emptyState.isVisible().catch(() => false)); test.skip(!hasData, 'Нет данных для тестирования'); const viewButton = page.locator('[data-testid="view-details-button"]').first(); await viewButton.click(); const modal = page.locator('[data-testid="idea-detail-modal"]'); await expect(modal).toBeVisible({ timeout: 5000 }); // Запоминаем исходный заголовок const originalTitle = await modal.locator('[data-testid="idea-detail-title"]').textContent(); // Нажимаем "Редактировать" await modal.locator('[data-testid="idea-detail-edit-button"]').click(); // Изменяем заголовок const titleInput = modal.locator('[data-testid="idea-detail-title-input"]'); const newTitle = `${originalTitle} (изменено ${Date.now()})`; await titleInput.fill(newTitle); // Сохраняем await modal.locator('[data-testid="idea-detail-save-button"]').click(); // Ждём возврата в режим просмотра await expect(modal.locator('[data-testid="idea-detail-title"]')).toBeVisible({ timeout: 5000 }); // Проверяем что заголовок обновился const updatedTitle = await modal.locator('[data-testid="idea-detail-title"]').textContent(); expect(updatedTitle).toBe(newTitle); }); test('Статус ТЗ сохраняется после редактирования идеи', async ({ page }) => { /** * Регрессионный тест: * 1. Сгенерировать ТЗ для идеи * 2. Открыть модалку детального просмотра * 3. Отредактировать и сохранить * 4. Статус ТЗ должен остаться "Есть" */ const emptyState = page.locator('[data-testid="ideas-empty-state"]'); const hasData = !(await emptyState.isVisible().catch(() => false)); test.skip(!hasData, 'Нет данных для тестирования'); // Сначала генерируем ТЗ для первой идеи const firstRow = page.locator('[data-testid^="idea-row-"]').first(); const specButton = firstRow.locator('[data-testid="specification-button"]'); await specButton.click(); const specModal = page.locator('[data-testid="specification-modal"]'); await expect(specModal).toBeVisible({ timeout: 5000 }); // Ждём пока сгенерируется ТЗ (контент или ошибка) const specContent = specModal.locator('[data-testid="specification-content"]'); const specError = specModal.locator('[data-testid="specification-error"]'); await expect(specContent.or(specError)).toBeVisible({ timeout: 60000 }); const hasSpec = await specContent.isVisible().catch(() => false); test.skip(!hasSpec, 'Не удалось сгенерировать ТЗ'); // Закрываем модалку ТЗ await page.locator('[data-testid="specification-close-button"]').click(); await expect(specModal).not.toBeVisible({ timeout: 3000 }); // Теперь открываем детальный просмотр const viewButton = firstRow.locator('[data-testid="view-details-button"]'); await viewButton.click(); const detailModal = page.locator('[data-testid="idea-detail-modal"]'); await expect(detailModal).toBeVisible({ timeout: 5000 }); // Проверяем что ТЗ есть const specStatus = detailModal.locator('[data-testid="idea-detail-specification-status"]'); await expect(specStatus).toContainText('Есть'); // Нажимаем "Редактировать" await detailModal.locator('[data-testid="idea-detail-edit-button"]').click(); // Меняем описание (не трогаем ТЗ) const descInput = detailModal.locator('[data-testid="idea-detail-description-input"]'); await descInput.fill('Обновлённое описание для теста ' + Date.now()); // Сохраняем await detailModal.locator('[data-testid="idea-detail-save-button"]').click(); // Ждём возврата в режим просмотра await expect(detailModal.locator('[data-testid="idea-detail-title"]')).toBeVisible({ timeout: 5000 }); // БАГ: Статус ТЗ должен остаться "Есть", но показывает "Нет" await expect(specStatus).toContainText('Есть'); }); }); test.describe('Фаза 3.2: Управление видимостью колонок', () => { test.beforeEach(async ({ page }) => { await page.goto('/'); await page.waitForSelector('[data-testid="ideas-table"]', { timeout: 10000 }); }); test('Кнопка настройки колонок присутствует', async ({ page }) => { const visibilityButton = page.locator('[data-testid="column-visibility-button"]'); await expect(visibilityButton).toBeVisible(); }); test('Клик на кнопку открывает меню с колонками', async ({ page }) => { const visibilityButton = page.locator('[data-testid="column-visibility-button"]'); await visibilityButton.click(); const menu = page.locator('[data-testid="column-visibility-menu"]'); await expect(menu).toBeVisible({ timeout: 3000 }); }); test('Меню содержит опции для всех колонок', async ({ page }) => { const visibilityButton = page.locator('[data-testid="column-visibility-button"]'); await visibilityButton.click(); const menu = page.locator('[data-testid="column-visibility-menu"]'); await expect(menu).toBeVisible({ timeout: 3000 }); // Проверяем наличие основных колонок await expect(menu.locator('[data-testid="column-visibility-item-status"]')).toBeVisible(); await expect(menu.locator('[data-testid="column-visibility-item-priority"]')).toBeVisible(); await expect(menu.locator('[data-testid="column-visibility-item-description"]')).toBeVisible(); }); test('Переключение видимости колонки работает', async ({ page }) => { // Проверяем что колонка "Описание" видна const table = page.locator('[data-testid="ideas-table"]'); const descHeader = table.locator('th', { hasText: 'Описание' }); await expect(descHeader).toBeVisible(); // Открываем меню const visibilityButton = page.locator('[data-testid="column-visibility-button"]'); await visibilityButton.click(); // Скрываем колонку "Описание" const descItem = page.locator('[data-testid="column-visibility-item-description"]'); await descItem.click(); // Закрываем меню кликом вне его await page.keyboard.press('Escape'); // Колонка должна скрыться await expect(descHeader).not.toBeVisible(); // Возвращаем обратно await visibilityButton.click(); await descItem.click(); await page.keyboard.press('Escape'); // Колонка должна появиться снова await expect(descHeader).toBeVisible(); }); test('Кнопка "Показать все" возвращает все колонки', async ({ page }) => { // Скрываем несколько колонок const visibilityButton = page.locator('[data-testid="column-visibility-button"]'); await visibilityButton.click(); await page.locator('[data-testid="column-visibility-item-description"]').click(); await page.locator('[data-testid="column-visibility-item-module"]').click(); // Нажимаем "Показать все" await page.locator('[data-testid="column-visibility-show-all"]').click(); await page.keyboard.press('Escape'); // Все колонки должны быть видны const table = page.locator('[data-testid="ideas-table"]'); await expect(table.locator('th', { hasText: 'Описание' })).toBeVisible(); await expect(table.locator('th', { hasText: 'Модуль' })).toBeVisible(); }); test('Название и действия нельзя скрыть', async ({ page }) => { const visibilityButton = page.locator('[data-testid="column-visibility-button"]'); await visibilityButton.click(); // Проверяем что "Название" disabled const titleItem = page.locator('[data-testid="column-visibility-item-title"]'); await expect(titleItem).toHaveAttribute('aria-disabled', 'true'); // Действия тоже disabled const actionsItem = page.locator('[data-testid="column-visibility-item-actions"]'); await expect(actionsItem).toHaveAttribute('aria-disabled', 'true'); }); });