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); }); });