import { test, expect } from '@playwright/test'; test.describe('Scenario 12: Mobile-First and Tablet Architecture Overhaul', () => { test.beforeEach(async ({ page }) => { await page.goto('/'); await page.waitForLoadState('domcontentloaded'); }); test('1. Mobile viewport (< 768px) automatically displays bottom navigation', async ({ page, viewport }) => { const isMobileViewport = viewport && viewport.width < 768; if (isMobileViewport) { // Bottom navigation must be visible const bottomNav = page.locator('[data-testid="bottom-nav"]'); await expect(bottomNav).toBeVisible({ timeout: 10000 }); // Sidebar must NOT be displayed const sidebar = page.locator('aside'); await expect(sidebar).not.toBeVisible(); // Topbar must have mobile controls (logo + section title + menu button) await expect(page.locator('[data-testid="mobile-menu-btn"]')).toBeVisible(); await expect(page.locator('[data-testid="mobile-current-title"]')).toBeVisible(); } else { // Desktop / tablet view: sidebar visible, bottom nav not visible const sidebar = page.locator('[data-testid="sidebar"]'); await expect(sidebar).toBeVisible({ timeout: 10000 }); const bottomNav = page.locator('[data-testid="bottom-nav"]'); await expect(bottomNav).not.toBeVisible(); } }); test('2. Mobile bottom nav tabs are functional with >= 44x44px touch targets', async ({ page, viewport }) => { const isMobileViewport = viewport && viewport.width < 768; if (!isMobileViewport) return; // Check touch target heights const catalogTab = page.locator('[data-testid="bottom-nav-catalog"]'); await expect(catalogTab).toBeVisible(); const box = await catalogTab.boundingBox(); expect(box).not.toBeNull(); if (box) { expect(box.height).toBeGreaterThanOrEqual(44); expect(box.width).toBeGreaterThanOrEqual(44); } // Navigate to Tasks via bottom nav const tasksTab = page.locator('[data-testid="bottom-nav-tasks"]'); await tasksTab.click(); await page.waitForURL('**/tasks'); await expect(page.locator('h1, h2').first()).toBeVisible(); // Navigate to Communications via bottom nav const commsTab = page.locator('[data-testid="bottom-nav-communications"]'); await commsTab.click(); await page.waitForURL('**/communications'); await expect(page.locator('h1, h2').first()).toBeVisible(); // Return to Catalog await catalogTab.click(); await page.waitForURL('**/'); await expect(page.locator('[data-testid="modules-catalog-container"]')).toBeVisible(); }); test('3. Mobile menu drawer opens and allows switching themes and profiles', async ({ page, viewport }) => { const isMobileViewport = viewport && viewport.width < 768; if (!isMobileViewport) return; // Open via "Ещё" tab in bottom nav const moreTab = page.locator('[data-testid="bottom-nav-more"]'); await moreTab.click(); // Drawer must be visible const drawer = page.locator('[data-testid="mobile-menu-drawer"]'); await expect(drawer).toBeVisible(); // Check drawer content: profiles, roles, themes, reset await expect(drawer).toContainText('Управление платформой'); await expect(drawer).toContainText('Отрасль и профиль организации'); await expect(drawer).toContainText('Тема оформления'); await expect(page.locator('[data-testid="mobile-reset-db-btn"]')).toBeVisible(); // Switch theme to dark const darkThemeBtn = drawer.getByRole('button', { name: /Тёмная/i }); await darkThemeBtn.click(); const themeAttr = await page.evaluate(() => document.documentElement.getAttribute('data-theme')); expect(themeAttr).toBe('dark'); // Close drawer const closeBtn = page.locator('[data-testid="close-mobile-menu"]'); await closeBtn.click(); await expect(drawer).not.toBeVisible(); }); test('4. Zero horizontal overflow across all viewports', async ({ page }) => { // Check main document scroll width vs client width const isOverflowing = await page.evaluate(() => { return document.documentElement.scrollWidth > document.documentElement.clientWidth; }); expect(isOverflowing).toBe(false); // Verify on /crm await page.goto('/crm'); await page.waitForLoadState('domcontentloaded'); const isCrmOverflowing = await page.evaluate(() => { return document.documentElement.scrollWidth > document.documentElement.clientWidth; }); expect(isCrmOverflowing).toBe(false); // Verify on /settings/entities await page.goto('/settings/entities'); await page.waitForLoadState('domcontentloaded'); const isSettingsOverflowing = await page.evaluate(() => { return document.documentElement.scrollWidth > document.documentElement.clientWidth; }); expect(isSettingsOverflowing).toBe(false); }); test('5. DynamicTable renders responsive card list on mobile screens', async ({ page, viewport }) => { await page.goto('/crm'); await page.waitForLoadState('domcontentloaded'); const isMobileViewport = viewport && viewport.width < 768; if (isMobileViewport) { // Mobile card list must be visible const mobileCards = page.locator('[data-testid="mobile-table-cards"]'); if (await mobileCards.isVisible()) { await expect(mobileCards).toBeVisible(); } } }); test('6. FormBuilder supports touch-friendly up and down field reordering', async ({ page }) => { await page.goto('/settings/entities/contact/form-builder'); await page.waitForLoadState('domcontentloaded'); // Locate field move buttons const moveDownBtn = page.locator('[data-testid^="move-field-down-"]').first(); if (await moveDownBtn.isVisible()) { await expect(moveDownBtn).toBeVisible(); // Verify button has accessible touch target const box = await moveDownBtn.boundingBox(); expect(box).not.toBeNull(); if (box) { expect(box.height).toBeGreaterThanOrEqual(28); } } }); test('7. Full Russian localization check (zero untranslated system labels)', async ({ page }) => { // Check home page headings and buttons const bodyText = await page.locator('body').innerText(); // Forbidden English words in visible platform headers expect(bodyText).not.toContain('Business OS'); expect(bodyText).not.toContain('Role: Owner'); expect(bodyText).not.toContain('Quick Action'); }); test('8. All 3 brand themes (soft-light, light, dark) apply and persist', async ({ page }) => { // Set light theme await page.evaluate(() => { window.localStorage.setItem('krona_theme_mode', 'light'); document.documentElement.setAttribute('data-theme', 'light'); }); await page.reload(); let currentTheme = await page.evaluate(() => document.documentElement.getAttribute('data-theme')); expect(currentTheme).toBe('light'); // Set dark theme await page.evaluate(() => { window.localStorage.setItem('krona_theme_mode', 'dark'); document.documentElement.setAttribute('data-theme', 'dark'); }); await page.reload(); currentTheme = await page.evaluate(() => document.documentElement.getAttribute('data-theme')); expect(currentTheme).toBe('dark'); // Restore soft-light await page.evaluate(() => { window.localStorage.setItem('krona_theme_mode', 'soft-light'); document.documentElement.setAttribute('data-theme', 'soft-light'); }); await page.reload(); currentTheme = await page.evaluate(() => document.documentElement.getAttribute('data-theme')); expect(currentTheme).toBe('soft-light'); }); });