Files
team-planner/tests/e2e/phase3.spec.ts
vigdorov dea0676169
Some checks reported errors
continuous-integration/drone/push Build encountered an error
add ai functions
2026-01-15 01:59:16 +03:00

464 lines
21 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 тесты для Фазы 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();
});
});