315 lines
14 KiB
TypeScript
315 lines
14 KiB
TypeScript
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');
|
||
});
|
||
});
|