krona-front/e2e/13-communications-visual-and-responsive.spec.ts

248 lines
12 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { test, expect } from '@playwright/test';
import * as fs from 'fs';
import * as path from 'path';
const THEMES = ['parchment-light', 'noble-green', 'graphite-forest-dark'] as const;
test.describe('Scenario 13: Communications Visual & Responsive Design System V3 Suite', () => {
test.beforeEach(async ({ page }) => {
// Navigate directly to Communications module
await page.goto('/communications', { waitUntil: 'domcontentloaded' });
await expect(page.locator('h1', { hasText: 'Коммуникации' })).toBeVisible({ timeout: 15000 });
});
test('1. Visual capture and theme validation across all 3 Design System V3 themes', async ({
page,
}, testInfo) => {
const projectName = testInfo.project.name;
const artifactsDir = path.resolve(process.cwd(), 'artifacts/visual-qa');
if (!fs.existsSync(artifactsDir)) {
fs.mkdirSync(artifactsDir, { recursive: true });
}
for (const theme of THEMES) {
// Apply theme to localStorage and DOM
await page.evaluate((th) => {
window.localStorage.setItem('krona-theme-mode', th);
document.documentElement.setAttribute('data-krona-theme', th);
const legacy = th === 'graphite-forest-dark' ? 'dark' : th === 'noble-green' ? 'soft-light' : 'light';
document.documentElement.setAttribute('data-theme', legacy);
}, theme);
// Verify theme attribute on html
await expect(page.locator('html')).toHaveAttribute('data-krona-theme', theme);
// Brief wait for rendering stability
await page.waitForTimeout(300);
// Capture screenshot for this theme and viewport
const screenshotPath = path.join(artifactsDir, `${theme}-${projectName}.png`);
await page.screenshot({ path: screenshotPath, fullPage: false });
expect(fs.existsSync(screenshotPath)).toBe(true);
}
});
test('2. Header, Slogan Watermark, and Channel Switcher validation', async ({ page }) => {
// Check main header
await expect(page.locator('h1', { hasText: 'Коммуникации' })).toBeVisible();
await expect(
page.locator('text=Все сообщения, почта и каналы компании в одном пространстве')
).toBeVisible();
// Check all 5 tabs
await expect(page.getByRole('tab', { name: /мессенджеры/i })).toBeVisible();
await expect(page.getByRole('tab', { name: /email/i })).toBeVisible();
await expect(page.getByRole('tab', { name: /каналы/i })).toBeVisible();
await expect(page.getByRole('tab', { name: /контакты/i })).toBeVisible();
await expect(page.getByRole('tab', { name: /настройки/i })).toBeVisible();
// Check connector pills row
await expect(page.getByRole('button', { name: /все.*48/i })).toBeVisible();
await expect(page.getByRole('button', { name: /telegram.*12/i })).toBeVisible();
await expect(page.getByRole('button', { name: /whatsapp.*8/i })).toBeVisible();
await expect(page.getByRole('button', { name: /vk.*6/i })).toBeVisible();
await expect(page.getByRole('button', { name: /max.*4/i })).toBeVisible();
await expect(page.getByRole('button', { name: /email.*10/i })).toBeVisible();
await expect(page.getByRole('button', { name: /подключить/i })).toBeVisible();
});
test('3. Responsive layout behavior and drill-down flow', async ({ page, viewport }) => {
const isMobile = viewport && viewport.width < 768;
const isTablet = viewport && viewport.width >= 768 && viewport.width < 1280;
const isDesktop = viewport && viewport.width >= 1280;
if (isMobile) {
// Mobile drilldown check: Level 1 (List) -> Level 2 (Chat) -> Level 3 (Contact)
const chatList = page.locator('section[aria-label="Список диалогов"]');
await expect(chatList).toBeVisible();
// Click first conversation to drill down to Level 2 (Chat)
const firstChat = chatList.locator('button').filter({ hasText: 'Иван Петров' }).first();
await firstChat.click();
// Active chat is now visible
const activeChat = page.locator('section[aria-label="Активный диалог"]');
await expect(activeChat).toBeVisible();
// Check Back to list button
const backToListBtn = activeChat.locator('button[aria-label="Назад к списку чатов"]');
await expect(backToListBtn).toBeVisible();
// Check bottom contact teaser button
const profileTeaserBtn = activeChat.locator('button[aria-label="Открыть карточку контакта"]');
await expect(profileTeaserBtn).toBeVisible();
// Click profile teaser to drill down to Level 3 (Contact)
await profileTeaserBtn.click();
const mobileContactCard = page.locator('section[aria-label="Карточка контакта"]');
await expect(mobileContactCard).toBeVisible();
// Check Back to chat button in Level 3
const backToChatBtn = mobileContactCard.locator('button[aria-label="Назад к диалогу"]');
await expect(backToChatBtn).toBeVisible();
await backToChatBtn.click();
// Back in chat Level 2
await expect(activeChat).toBeVisible();
await backToListBtn.click();
// Back in list Level 1
await expect(chatList).toBeVisible();
// Verify no horizontal overflow
const hasHorizontalScroll = await page.evaluate(() => {
return document.documentElement.scrollWidth > document.documentElement.clientWidth + 1;
});
expect(hasHorizontalScroll).toBe(false);
} else if (isTablet) {
// Tablet (768 vertical and 1024 landscape): 2 columns (Chat list on left, Chat + Horizontal Contact Card on right)
const chatList = page.locator('section[aria-label="Список диалогов"]');
await expect(chatList).toBeVisible();
const activeChat = page.locator('section[aria-label="Активный диалог"]');
await expect(activeChat).toBeVisible();
const horizontalContactCard = page.locator('aside[aria-label="Карточка контакта планшет"]');
await expect(horizontalContactCard).toBeVisible();
await expect(horizontalContactCard.locator('text=VIP')).toBeVisible();
} else if (isDesktop) {
// Desktop: 3 separate elevated columns side-by-side
const chatList = page.locator('section[aria-label="Список диалогов"]');
await expect(chatList).toBeVisible();
const activeChat = page.locator('section[aria-label="Активный диалог"]');
await expect(activeChat).toBeVisible();
const verticalContactCard = page.locator('aside[aria-label="Карточка контакта"]');
await expect(verticalContactCard).toBeVisible();
await expect(verticalContactCard.locator('text=Иван Петров')).toBeVisible();
await expect(verticalContactCard.locator('text=VIP')).toBeVisible();
await expect(verticalContactCard.locator('text=✦ AI-сводка')).toBeVisible();
}
});
test('4. Interactive messaging, quick replies, and tab navigation', async ({ page, viewport }) => {
const isMobile = viewport && viewport.width < 768;
if (isMobile) {
// Select chat first on mobile
const firstChat = page
.locator('section[aria-label="Список диалогов"]')
.locator('button')
.filter({ hasText: 'Иван Петров' })
.first();
await firstChat.click();
}
const activeChat = page.locator('section[aria-label="Активный диалог"]');
await expect(activeChat).toBeVisible();
// Interactive message typing and sending
const input = activeChat.locator('textarea[placeholder="Написать сообщение..."]');
await expect(input).toBeVisible();
await input.fill('Отправлено через Playwright тест E2E');
const sendBtn = activeChat.locator('button[aria-label="Отправить сообщение"]');
await expect(sendBtn).toBeEnabled();
await sendBtn.click();
// Verify outgoing bubble appears in active chat stream
await expect(activeChat.getByText('Отправлено через Playwright тест E2E')).toBeVisible();
// Interactive Quick Reply chip
const quickReplyChip = activeChat.locator('button', { hasText: 'Да, подготовим' }).first();
await expect(quickReplyChip).toBeVisible();
await quickReplyChip.click();
await expect(activeChat.getByText('Да, подготовим').first()).toBeVisible();
// Test tab navigation: Email tab
const emailTab = page.getByRole('tab', { name: /email/i });
await emailTab.click({ force: true });
await expect(page.locator('text=Новое письмо')).toBeVisible();
// Channels tab
const channelsTab = page.getByRole('tab', { name: /каналы/i });
await channelsTab.click({ force: true });
await expect(page.locator('text=Внутренние каналы')).toBeVisible();
// Contacts tab
const contactsTab = page.getByRole('tab', { name: /контакты/i });
await contactsTab.click({ force: true });
await expect(page.locator('input[placeholder="Поиск по контактам..."]')).toBeVisible();
// Settings tab
const settingsTab = page.getByRole('tab', { name: /настройки/i });
await settingsTab.click({ force: true });
await expect(page.locator('text=Подключенные мессенджеры и коннекторы')).toBeVisible();
// Return to Messengers
const messengersTab = page.getByRole('tab', { name: /мессенджеры/i });
await messengersTab.click({ force: true });
await expect(page.getByRole('button', { name: /все.*48/i })).toBeVisible();
});
test.afterAll(async () => {
const artifactsDir = path.resolve(process.cwd(), 'artifacts/visual-qa');
if (!fs.existsSync(artifactsDir)) return;
const files = fs.readdirSync(artifactsDir).filter((f) => f.endsWith('.png'));
const manifest = {
generatedAt: new Date().toISOString(),
module: 'communications',
version: 'v3.0',
referenceMockups: [
'Коммуникация.png (Desktop 1440/1280)',
'Коммуникация планшет горизонт.png (Tablet landscape 1024)',
'Коммуникация планшет вертикальный.png (Tablet vertical 768)',
'Коммуникация телефон.png (Mobile 360/390/430)',
],
themes: ['parchment-light', 'noble-green', 'graphite-forest-dark'],
viewports: [
{ name: 'mobile-360', width: 360, height: 800, type: 'phone' },
{ name: 'mobile-390', width: 390, height: 844, type: 'phone' },
{ name: 'mobile-430', width: 430, height: 932, type: 'phone' },
{ name: 'tablet-768', width: 768, height: 1024, type: 'tablet-vertical' },
{ name: 'tablet-1024', width: 1024, height: 768, type: 'tablet-horizontal' },
{ name: 'desktop-1280', width: 1280, height: 800, type: 'desktop' },
{ name: 'desktop-1440', width: 1440, height: 900, type: 'desktop' },
{ name: 'desktop-1920', width: 1920, height: 1080, type: 'desktop' },
],
screenshots: files.map((file) => ({
filename: file,
theme: file.startsWith('graphite-forest-dark')
? 'graphite-forest-dark'
: file.startsWith('noble-green')
? 'noble-green'
: 'parchment-light',
viewport: file.replace(/^(graphite-forest-dark|noble-green|parchment-light)-/, '').replace('.png', ''),
capturedAt: fs.statSync(path.join(artifactsDir, file)).mtime.toISOString(),
})),
};
fs.writeFileSync(
path.join(artifactsDir, 'manifest.json'),
JSON.stringify(manifest, null, 2),
'utf-8'
);
});
});