195 lines
7.8 KiB
TypeScript
195 lines
7.8 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
|
|
|
test.describe('Scenario 12: Mobile-First and Tablet Architecture Overhaul', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await page.goto('/', { waitUntil: '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"]');
|
|
const closeBox = await closeBtn.boundingBox();
|
|
expect(closeBox).not.toBeNull();
|
|
if (closeBox) {
|
|
expect(closeBox.height).toBeGreaterThanOrEqual(44);
|
|
expect(closeBox.width).toBeGreaterThanOrEqual(44);
|
|
}
|
|
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 >= 44x44px
|
|
const box = await moveDownBtn.boundingBox();
|
|
expect(box).not.toBeNull();
|
|
if (box) {
|
|
expect(box.height).toBeGreaterThanOrEqual(44);
|
|
expect(box.width).toBeGreaterThanOrEqual(44);
|
|
}
|
|
}
|
|
});
|
|
|
|
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');
|
|
});
|
|
});
|