test(control-center): add 11 Playwright E2E scenarios across desktop, tablet, and mobile

This commit is contained in:
ochenstarik 2026-09-11 02:34:45 +07:00
parent 47dbadafe9
commit 8e0f640673

View file

@ -0,0 +1,272 @@
import { test, expect } from '@playwright/test';
test.describe('Krona Control Center - Production UX & Operator Scenarios', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/control');
await page.waitForLoadState('domcontentloaded');
await expect(page.locator('text=IndexedDB Synced')).toBeVisible({ timeout: 15000 });
});
test('01. Shell loads with zero horizontal overflow and topbar controls', async ({ page }) => {
await expect(page.locator('text=Krona Control Center').first()).toBeVisible();
await expect(page.locator('button[aria-label="Сессия оператора"]')).toBeVisible();
const isOverflowing = await page.evaluate(() => {
return document.documentElement.scrollWidth > document.documentElement.clientWidth;
});
expect(isOverflowing).toBe(false);
await expect(page.locator('text=Сводная операторская панель')).toBeVisible();
await expect(page.locator('text=Организации').first()).toBeVisible();
await expect(page.locator('text=Пользователи / Точки').first()).toBeVisible();
});
test('02. Scenario 1: Search organization and open 360 inspector drawer', async ({ page }) => {
await page.click('a[href="/control/organizations"]');
await page.waitForURL('**/control/organizations');
await expect(page.locator('h1:has-text("Организации платформы")')).toBeVisible();
// Search input
const searchInput = page.locator('input[placeholder*="Поиск"]');
await expect(searchInput).toBeVisible();
await searchInput.fill('Ритейл');
// Filtered result should show Retail Group
await expect(page.locator('text=Северная Ритейл Группа').first()).toBeVisible();
// Click 360 card button
const inspectBtn = page.locator('button:has-text("360° Карточка")').first();
await inspectBtn.click();
// Contextual Drawer opens
const drawer = page.locator('div[role="complementary"][aria-label="Контекстный инспектор"]');
await expect(drawer).toBeVisible();
await expect(drawer.locator('text=Северная Ритейл Группа').first()).toBeVisible();
await expect(drawer.locator('text=Действия оператора платформы')).toBeVisible();
// Verify 360 tabs
await expect(drawer.locator('button:has-text("Обзор")')).toBeVisible();
await expect(drawer.locator('button:has-text("Модули")').first()).toBeVisible();
// Switch to Modules tab
await drawer.locator('button:has-text("Модули")').first().click();
await expect(drawer.locator('div:has-text("v1."), div:has-text("v2."), div:has-text("v3.")').first()).toBeVisible();
// Close drawer
await drawer.locator('button[aria-label="Закрыть инспектор"]').click();
await expect(drawer).not.toBeVisible();
});
test('03. Scenario 2: Dangerous action requires operator authorization & mandatory reason', async ({ page }) => {
await page.click('a[href="/control/organizations"]');
await page.waitForURL('**/control/organizations');
// Open inspector for first org
await page.locator('button:has-text("360° Карточка")').first().click();
const drawer = page.locator('div[role="complementary"][aria-label="Контекстный инспектор"]');
await expect(drawer).toBeVisible();
// Click trial extension button
const grantTrialBtn = drawer.locator('button:has-text("Выдать Trial")');
await expect(grantTrialBtn).toBeVisible();
await grantTrialBtn.click();
// Confirm modal opens
const confirmDialog = page.locator('div[role="dialog"][aria-labelledby="confirm-modal-title"]');
await expect(confirmDialog).toBeVisible();
await expect(confirmDialog.locator('text=Требуется авторизация оператора')).toBeVisible();
// Attempt submit without reason
const submitBtn = confirmDialog.locator('button:has-text("Подтвердить операцию")');
await submitBtn.click();
await expect(confirmDialog.locator('text=Обоснование операции обязательно')).toBeVisible();
// Fill valid reason (>= 5 chars)
const reasonInput = confirmDialog.locator('textarea[id="action-reason-input"]');
await reasonInput.fill('Согласованное продление пилота по заявке #PILOT-1049');
await submitBtn.click();
// Modal closes
await expect(confirmDialog).not.toBeVisible();
});
test('04. Scenario 3: Filter subscriptions expiring in 7 days', async ({ page }) => {
await page.click('a[href="/control/billing"]');
await page.waitForURL('**/control/billing');
await expect(page.locator('h1:has-text("Подписки, лицензии и Entitlements")')).toBeVisible();
// Click "Истекает 7 дней" filter
const filter7dBtn = page.locator('button:has-text("Истекает 7 дней")');
await expect(filter7dBtn).toBeVisible();
await filter7dBtn.click();
// Verify filter active styling and table displays matching rows
await expect(page.locator('tbody tr')).not.toHaveCount(0);
await expect(page.locator('text=Истекает (< 7 дн.)').first()).toBeVisible();
});
test('05. Scenario 4: Monitor installations and identify outdated Android terminals', async ({ page }) => {
await page.click('a[href="/control/installations"]');
await page.waitForURL('**/control/installations');
await expect(page.locator('h1:has-text("Установки и клиентские приложения")')).toBeVisible();
// Click quick filter button for outdated Android
const outdatedAndroidBtn = page.locator('button:has-text("Показать только устаревшие Android")');
await expect(outdatedAndroidBtn).toBeVisible();
await outdatedAndroidBtn.click();
// Outdated Android device should be visible in table
await expect(page.locator('text=Android').first()).toBeVisible();
await expect(page.locator('text=Устарела').first()).toBeVisible();
});
test('06. Scenario 5: Inspect infrastructure nodes and Server Monitor outage resilience', async ({ page }) => {
await page.click('a[href="/control/infrastructure"]');
await page.waitForURL('**/control/infrastructure');
await expect(page.locator('h1:has-text("Инфраструктурные узлы")')).toBeVisible();
// Check circuit breaker banner simulation button
const toggleBannerBtn = page.locator('button[title*="Server Monitor"]');
await expect(toggleBannerBtn).toBeVisible();
// Verify server node metrics
await expect(page.locator('text=CPU:').first()).toBeVisible();
// Click first server card to open inspector
const firstServerCard = page.locator('div[class*="cursor-pointer"]:has-text("CPU:")').first();
await firstServerCard.click();
// Context drawer opens with server details
const drawer = page.locator('div[role="complementary"][aria-label="Контекстный инспектор"]');
await expect(drawer).toBeVisible();
await expect(drawer.locator('text=Телеметрия узла').first()).toBeVisible();
await drawer.locator('button[aria-label="Закрыть инспектор"]').click();
});
test('07. Scenario 6: Create incident workflow from alert', async ({ page }) => {
await page.click('a[href="/control/infrastructure"]');
await page.waitForURL('**/control/infrastructure');
// Find a problematic server with "Создать инцидент"
const createIncBtn = page.locator('button:has-text("Создать инцидент")').first();
await expect(createIncBtn).toBeVisible();
await createIncBtn.click();
// Confirm modal opens
const confirmModal = page.locator('div[role="dialog"][aria-labelledby="confirm-modal-title"]');
await expect(confirmModal).toBeVisible();
await confirmModal.locator('textarea[id="action-reason-input"]').fill('Эскалация дежурным инженером NOC по алерту узла');
await confirmModal.locator('button:has-text("Подтвердить операцию")').click();
// Navigate to Incidents desk
await page.click('a[href="/control/incidents"]');
await page.waitForURL('**/control/incidents');
// The incident alert is listed
await expect(page.locator('text=Критический сбой узла').first()).toBeVisible();
});
test('08. Scenario 7: Create targeted tenant announcement', async ({ page }) => {
await page.click('a[href="/control/announcements"]');
await page.waitForURL('**/control/announcements');
await expect(page.locator('h1:has-text("Оповещения и системные сообщения")')).toBeVisible();
// Open creation form
await page.locator('button:has-text("Создать объявление")').click();
// Fill title
await page.locator('input[placeholder*="Omnichannel"]').fill('Плановое ночное техобслуживание');
// Fill message body
await page.locator('textarea[placeholder*="текст объявления"]').fill('12 сентября с 03:00 до 04:00 МСК будут проводиться регламентные работы.');
// Submit form
await page.locator('button[type="submit"]:has-text("Опубликовать")').click();
// Confirmation
const confirmModal = page.locator('div[role="dialog"][aria-labelledby="confirm-modal-title"]');
await expect(confirmModal).toBeVisible();
await confirmModal.locator('textarea[id="action-reason-input"]').fill('Согласованный релизный бюллетень #REL-402');
await confirmModal.locator('button:has-text("Подтвердить операцию")').click();
await expect(page.locator('text=Плановое ночное техобслуживание').first()).toBeVisible();
});
test('09. Scenario 8: Manage module rollouts (Pause / Resume)', async ({ page }) => {
await page.click('a[href="/control/modules"]');
await page.waitForURL('**/control/modules');
await expect(page.locator('h1:has-text("Каталог модулей и Rollout конвейер")')).toBeVisible();
// Select module with active rollout (Омниканальные коммуникации)
await page.locator('text=Омниканальные коммуникации').first().click();
// Verify active rollout badge
const activeRolloutBadge = page.locator('text=Пилот (Pilot)').first();
await expect(activeRolloutBadge).toBeVisible();
// Click Pause
const pauseBtn = page.locator('button:has-text("Поставить на паузу")');
if (await pauseBtn.isVisible()) {
await pauseBtn.click();
const confirmModal = page.locator('div[role="dialog"][aria-labelledby="confirm-modal-title"]');
await expect(confirmModal).toBeVisible();
await confirmModal.locator('textarea[id="action-reason-input"]').fill('Временная приостановка раскатки до проверки логов');
await confirmModal.locator('button:has-text("Подтвердить операцию")').click();
// Rollout state should now show "На паузе"
await expect(page.locator('text=На паузе').first()).toBeVisible();
}
});
test('10. Scenario 9: Switch operator role and observe strict RBAC enforcement', async ({ page }) => {
const switcher = page.locator('button[aria-label="Сессия оператора"]');
await switcher.click();
// Switch to security_auditor
const auditorItem = page.locator('button[role="menuitem"]:has-text("Аудитор безопасности")');
await expect(auditorItem).toBeVisible();
await auditorItem.click();
// Topbar now shows "Аудитор безопасности"
await expect(page.locator('text=Аудитор безопасности').first()).toBeVisible();
// Navigate to Modules page: triggering rollouts should be disabled for auditor
await page.click('a[href="/control/modules"]');
await page.waitForURL('**/control/modules');
const rolloutBtn = page.locator('button:has-text("Запустить пилотный Rollout"), button:has-text("Поставить на паузу")').first();
if (await rolloutBtn.isVisible()) {
await expect(rolloutBtn).toBeDisabled();
}
// Switch back to platform_owner
await switcher.click();
const ownerItem = page.locator('button[role="menuitem"]:has-text("Владелец платформы")');
await ownerItem.click();
await expect(page.locator('text=Владелец платформы').first()).toBeVisible();
});
test('11. Scenario 10: State survives browser page reload via IndexedDB', async ({ page }) => {
await page.click('a[href="/control/audit"]');
await page.waitForURL('**/control/audit');
await expect(page.locator('h1:has-text("Неизменяемый журнал аудита")')).toBeVisible();
// Wait for audit log rows to load from IndexedDB
await expect(page.locator('tbody tr')).toHaveCount(3);
const initialRowsCount = await page.locator('tbody tr').count();
expect(initialRowsCount).toBe(3);
// Reload page
await page.reload();
await page.waitForLoadState('domcontentloaded');
await expect(page.locator('text=IndexedDB Synced')).toBeVisible({ timeout: 15000 });
// Audit entries must still be present from IndexedDB
await expect(page.locator('tbody tr')).toHaveCount(initialRowsCount);
});
});