import { test, expect } from '@playwright/test'; /** * E2E тесты для Фазы 3 Team Planner * - AI-оценка трудозатрат * * Используем data-testid для стабильных селекторов */ test.describe('Фаза 3: AI-оценка', () => { test.beforeEach(async ({ page }) => { await page.goto('/'); await page.waitForSelector('[data-testid="ideas-table"]', { timeout: 10000 }); }); test('Кнопка AI-оценки присутствует в каждой строке', async ({ page }) => { const emptyState = page.locator('[data-testid="ideas-empty-state"]'); const hasData = !(await emptyState.isVisible().catch(() => false)); if (hasData) { const estimateButtons = page.locator('[data-testid="estimate-idea-button"]'); const buttonCount = await estimateButtons.count(); expect(buttonCount).toBeGreaterThan(0); } }); test('Клик на кнопку AI-оценки открывает модалку', async ({ page }) => { const emptyState = page.locator('[data-testid="ideas-empty-state"]'); const hasData = !(await emptyState.isVisible().catch(() => false)); test.skip(!hasData, 'Нет данных для тестирования'); // Кликаем на кнопку AI-оценки первой идеи const estimateButton = page.locator('[data-testid="estimate-idea-button"]').first(); await estimateButton.click(); // Проверяем что модалка открылась const modal = page.locator('[data-testid="ai-estimate-modal"]'); await expect(modal).toBeVisible({ timeout: 5000 }); }); test('Модалка AI-оценки показывает загрузку', async ({ page }) => { const emptyState = page.locator('[data-testid="ideas-empty-state"]'); const hasData = !(await emptyState.isVisible().catch(() => false)); test.skip(!hasData, 'Нет данных для тестирования'); const estimateButton = page.locator('[data-testid="estimate-idea-button"]').first(); await estimateButton.click(); const modal = page.locator('[data-testid="ai-estimate-modal"]'); await expect(modal).toBeVisible({ timeout: 5000 }); // Должен быть либо индикатор загрузки, либо результат, либо ошибка const hasContent = await modal.locator('text=Анализируем').isVisible().catch(() => false) || await modal.locator('text=Общее время').isVisible().catch(() => false) || await modal.locator('text=Не удалось').isVisible().catch(() => false); expect(hasContent).toBeTruthy(); }); test('AI-оценка возвращает результат с часами и сложностью', async ({ page }) => { const emptyState = page.locator('[data-testid="ideas-empty-state"]'); const hasData = !(await emptyState.isVisible().catch(() => false)); test.skip(!hasData, 'Нет данных для тестирования'); const estimateButton = page.locator('[data-testid="estimate-idea-button"]').first(); await estimateButton.click(); const modal = page.locator('[data-testid="ai-estimate-modal"]'); await expect(modal).toBeVisible({ timeout: 5000 }); // Ждём результат (до 30 секунд - AI может отвечать долго) const totalTimeLabel = modal.locator('text=Общее время'); await expect(totalTimeLabel).toBeVisible({ timeout: 30000 }); // Проверяем наличие сложности const complexityLabel = modal.locator('text=Сложность'); await expect(complexityLabel).toBeVisible(); }); test('AI-оценка показывает разбивку по ролям', async ({ page }) => { const emptyState = page.locator('[data-testid="ideas-empty-state"]'); const hasData = !(await emptyState.isVisible().catch(() => false)); test.skip(!hasData, 'Нет данных для тестирования'); const estimateButton = page.locator('[data-testid="estimate-idea-button"]').first(); await estimateButton.click(); const modal = page.locator('[data-testid="ai-estimate-modal"]'); await expect(modal).toBeVisible({ timeout: 5000 }); // Ждём результат await expect(modal.locator('text=Общее время')).toBeVisible({ timeout: 30000 }); // Проверяем наличие таблицы разбивки по ролям const breakdownLabel = modal.locator('text=Разбивка по ролям'); // Разбивка опциональна (может не быть если команда не указана) const hasBreakdown = await breakdownLabel.isVisible().catch(() => false); if (hasBreakdown) { const breakdownRows = modal.locator('[data-testid^="estimate-breakdown-row-"]'); const rowCount = await breakdownRows.count(); expect(rowCount).toBeGreaterThanOrEqual(0); } }); test('Кнопка закрытия модалки работает', async ({ page }) => { const emptyState = page.locator('[data-testid="ideas-empty-state"]'); const hasData = !(await emptyState.isVisible().catch(() => false)); test.skip(!hasData, 'Нет данных для тестирования'); const estimateButton = page.locator('[data-testid="estimate-idea-button"]').first(); await estimateButton.click(); const modal = page.locator('[data-testid="ai-estimate-modal"]'); await expect(modal).toBeVisible({ timeout: 5000 }); // Закрываем модалку const closeButton = page.locator('[data-testid="close-estimate-modal-button"]'); await closeButton.click(); // Модалка должна закрыться await expect(modal).not.toBeVisible({ timeout: 3000 }); }); test('После оценки результат сохраняется в строке таблицы', async ({ page }) => { 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 estimateButton = firstRow.locator('[data-testid="estimate-idea-button"]'); await estimateButton.click(); const modal = page.locator('[data-testid="ai-estimate-modal"]'); await expect(modal).toBeVisible({ timeout: 5000 }); // Ждём результат await expect(modal.locator('text=Общее время')).toBeVisible({ timeout: 30000 }); // Закрываем модалку await page.locator('[data-testid="close-estimate-modal-button"]').click(); await expect(modal).not.toBeVisible({ timeout: 3000 }); // Проверяем что в строке появилась оценка (часы или дни) // Ищем текст типа "8ч" или "2д" в строке await page.waitForTimeout(500); // Колонка "Оценка" должна содержать данные const rowText = await firstRow.textContent(); const hasEstimate = rowText?.match(/\d+[чд]/) !== null; expect(hasEstimate).toBeTruthy(); }); test('Колонка "Оценка" отображается в таблице', async ({ page }) => { const table = page.locator('[data-testid="ideas-table"]'); await expect(table).toBeVisible(); // Проверяем наличие заголовка колонки const header = table.locator('th', { hasText: 'Оценка' }); await expect(header).toBeVisible(); }); test('Клик по оценке открывает модалку с деталями', async ({ page }) => { const emptyState = page.locator('[data-testid="ideas-empty-state"]'); const hasData = !(await emptyState.isVisible().catch(() => false)); test.skip(!hasData, 'Нет данных для тестирования'); // Ищем строку с оценкой (кнопка view-estimate-button появляется только если есть оценка) const viewEstimateButton = page.locator('[data-testid="view-estimate-button"]').first(); const hasEstimate = await viewEstimateButton.isVisible().catch(() => false); test.skip(!hasEstimate, 'Нет идей с оценкой для тестирования'); // Кликаем по оценке await viewEstimateButton.click(); // Модалка должна открыться с деталями const modal = page.locator('[data-testid="ai-estimate-modal"]'); await expect(modal).toBeVisible({ timeout: 5000 }); // Должны быть видны результаты (без загрузки) await expect(modal.locator('text=Общее время')).toBeVisible(); await expect(modal.locator('text=Сложность')).toBeVisible(); }); }); test.describe('Фаза 3: AI-оценка - создание данных для теста', () => { test('Создание идеи и запуск AI-оценки', async ({ page }) => { await page.goto('/'); await page.waitForSelector('[data-testid="ideas-table"]', { timeout: 10000 }); // Проверяем есть ли кнопка создания идеи const createButton = page.locator('[data-testid="create-idea-button"]'); const hasCreateButton = await createButton.isVisible().catch(() => false); if (hasCreateButton) { // Создаём идею await createButton.click(); const modal = page.locator('[data-testid="create-idea-modal"]'); await expect(modal).toBeVisible({ timeout: 5000 }); // Заполняем форму await page.locator('[data-testid="idea-title-input"] input').fill('Тестовая идея для AI-оценки'); await page.locator('[data-testid="idea-description-input"] textarea').first().fill( 'Реализовать систему уведомлений. Нужны email и push-уведомления для важных событий.' ); // Сохраняем await page.locator('[data-testid="submit-create-idea"]').click(); await expect(modal).not.toBeVisible({ timeout: 5000 }); // Ждём появления новой строки await page.waitForTimeout(1000); } // Теперь проверяем AI-оценку const emptyState = page.locator('[data-testid="ideas-empty-state"]'); const hasData = !(await emptyState.isVisible().catch(() => false)); test.skip(!hasData, 'Не удалось создать данные для тестирования'); // Запускаем AI-оценку const estimateButton = page.locator('[data-testid="estimate-idea-button"]').first(); await estimateButton.click(); const estimateModal = page.locator('[data-testid="ai-estimate-modal"]'); await expect(estimateModal).toBeVisible({ timeout: 5000 }); // Ждём результат (до 60 секунд - AI может отвечать долго) // Или ошибку (текст "Не удалось" из компонента) const resultOrError = estimateModal.locator('text=/Общее время|Не удалось/'); await expect(resultOrError).toBeVisible({ timeout: 60000 }); }); }); /** * Тесты для генерации мини-ТЗ (Phase 3.1) */ test.describe('Фаза 3.1: Генерация мини-ТЗ', () => { 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 specButtons = page.locator('[data-testid="specification-button"]'); const buttonCount = await specButtons.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 specButton = page.locator('[data-testid="specification-button"]').first(); await specButton.click(); // Проверяем что модалка открылась const modal = page.locator('[data-testid="specification-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 specButton = page.locator('[data-testid="specification-button"]').first(); await specButton.click(); const modal = page.locator('[data-testid="specification-modal"]'); await expect(modal).toBeVisible({ timeout: 5000 }); // Должен быть либо индикатор загрузки, либо контент, либо ошибка const hasContent = await modal.locator('[data-testid="specification-loading"]').isVisible().catch(() => false) || await modal.locator('[data-testid="specification-content"]').isVisible().catch(() => false) || await modal.locator('[data-testid="specification-error"]').isVisible().catch(() => false); expect(hasContent).toBeTruthy(); }); test('Генерация ТЗ возвращает результат', async ({ page }) => { const emptyState = page.locator('[data-testid="ideas-empty-state"]'); const hasData = !(await emptyState.isVisible().catch(() => false)); test.skip(!hasData, 'Нет данных для тестирования'); const specButton = page.locator('[data-testid="specification-button"]').first(); await specButton.click(); const modal = page.locator('[data-testid="specification-modal"]'); await expect(modal).toBeVisible({ timeout: 5000 }); // Ждём результат (до 60 секунд - AI может отвечать долго) const content = modal.locator('[data-testid="specification-content"]'); const error = modal.locator('[data-testid="specification-error"]'); // Ожидаем либо контент, либо ошибку await expect(content.or(error)).toBeVisible({ timeout: 60000 }); }); test('Кнопка закрытия модалки ТЗ работает', async ({ page }) => { const emptyState = page.locator('[data-testid="ideas-empty-state"]'); const hasData = !(await emptyState.isVisible().catch(() => false)); test.skip(!hasData, 'Нет данных для тестирования'); const specButton = page.locator('[data-testid="specification-button"]').first(); await specButton.click(); const modal = page.locator('[data-testid="specification-modal"]'); await expect(modal).toBeVisible({ timeout: 5000 }); // Ждём пока загрузится контент или ошибка const content = modal.locator('[data-testid="specification-content"]'); const error = modal.locator('[data-testid="specification-error"]'); await expect(content.or(error)).toBeVisible({ timeout: 60000 }); // Закрываем модалку const closeButton = page.locator('[data-testid="specification-close-button"]'); await closeButton.click(); // Модалка должна закрыться await expect(modal).not.toBeVisible({ timeout: 3000 }); }); test('Кнопка редактирования ТЗ появляется после генерации', async ({ page }) => { const emptyState = page.locator('[data-testid="ideas-empty-state"]'); const hasData = !(await emptyState.isVisible().catch(() => false)); test.skip(!hasData, 'Нет данных для тестирования'); const specButton = page.locator('[data-testid="specification-button"]').first(); await specButton.click(); const modal = page.locator('[data-testid="specification-modal"]'); await expect(modal).toBeVisible({ timeout: 5000 }); // Ждём контент const content = modal.locator('[data-testid="specification-content"]'); const hasContent = await content.isVisible({ timeout: 60000 }).catch(() => false); if (hasContent) { // Проверяем наличие кнопки редактирования const editButton = modal.locator('[data-testid="specification-edit-button"]'); await expect(editButton).toBeVisible(); } }); test('Редактирование ТЗ открывает textarea', async ({ page }) => { const emptyState = page.locator('[data-testid="ideas-empty-state"]'); const hasData = !(await emptyState.isVisible().catch(() => false)); test.skip(!hasData, 'Нет данных для тестирования'); const specButton = page.locator('[data-testid="specification-button"]').first(); await specButton.click(); const modal = page.locator('[data-testid="specification-modal"]'); await expect(modal).toBeVisible({ timeout: 5000 }); // Ждём контент const content = modal.locator('[data-testid="specification-content"]'); const hasContent = await content.isVisible({ timeout: 60000 }).catch(() => false); test.skip(!hasContent, 'Не удалось сгенерировать ТЗ'); // Кликаем редактировать const editButton = modal.locator('[data-testid="specification-edit-button"]'); await editButton.click(); // Должен появиться textarea const textarea = modal.locator('[data-testid="specification-textarea"]'); await expect(textarea).toBeVisible(); }); test('Сохранение отредактированного ТЗ', async ({ page }) => { const emptyState = page.locator('[data-testid="ideas-empty-state"]'); const hasData = !(await emptyState.isVisible().catch(() => false)); test.skip(!hasData, 'Нет данных для тестирования'); const specButton = page.locator('[data-testid="specification-button"]').first(); await specButton.click(); const modal = page.locator('[data-testid="specification-modal"]'); await expect(modal).toBeVisible({ timeout: 5000 }); // Ждём контент const content = modal.locator('[data-testid="specification-content"]'); const hasContent = await content.isVisible({ timeout: 60000 }).catch(() => false); test.skip(!hasContent, 'Не удалось сгенерировать ТЗ'); // Кликаем редактировать const editButton = modal.locator('[data-testid="specification-edit-button"]'); await editButton.click(); // Редактируем текст const textarea = modal.locator('[data-testid="specification-textarea"] textarea'); const testText = '\n\n## Дополнительно\nТестовая правка ' + Date.now(); await textarea.fill(await textarea.inputValue() + testText); // Сохраняем const saveButton = modal.locator('[data-testid="specification-save-button"]'); await saveButton.click(); // Должен вернуться режим просмотра await expect(content).toBeVisible({ timeout: 5000 }); // Проверяем что изменения сохранились const contentText = await content.textContent(); expect(contentText).toContain('Дополнительно'); }); test('Повторное открытие показывает сохранённое ТЗ', async ({ page }) => { const emptyState = page.locator('[data-testid="ideas-empty-state"]'); const hasData = !(await emptyState.isVisible().catch(() => false)); test.skip(!hasData, 'Нет данных для тестирования'); // Ищем идею с уже сгенерированным ТЗ (кнопка синяя) const blueSpecButton = page.locator('[data-testid="specification-button"][class*="primary"]').first(); const hasExistingSpec = await blueSpecButton.isVisible().catch(() => false); test.skip(!hasExistingSpec, 'Нет идей с ТЗ для тестирования'); await blueSpecButton.click(); const modal = page.locator('[data-testid="specification-modal"]'); await expect(modal).toBeVisible({ timeout: 5000 }); // Контент должен появиться сразу (без загрузки) const content = modal.locator('[data-testid="specification-content"]'); await expect(content).toBeVisible({ timeout: 3000 }); // Не должно быть индикатора загрузки const loading = modal.locator('[data-testid="specification-loading"]'); await expect(loading).not.toBeVisible(); }); });