fix bus phase 3/2
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2026-01-15 12:05:57 +03:00
parent 684e416588
commit 7421f33de8
6 changed files with 400 additions and 58 deletions

314
tests/e2e/phase3.2.spec.ts Normal file
View File

@ -0,0 +1,314 @@
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');
});
});