import { test, expect } from '@playwright/test'; test.describe('Empty State UI (US-91)', () => { test('should display empty state when flight board search returns no results', async ({ page, }) => { // Navigate to a city that should have search results (MOW) await page.goto('/ru-ru/'); // Wait for page to load await page.waitForLoadState('networkidle'); // Intercept and mock the API to return no flights await page.route('**/api/**', (route) => { const url = route.request().url(); if (url.includes('flights')) { route.abort(); } else { route.continue(); } }); // The empty state should display when no flights are returned // Note: This is a partial implementation - real test would need actual API mock await page.goto('/ru-ru/onlineboard?city=MOW'); // Wait for loading to complete await page.waitForLoadState('networkidle'); // Check if page loaded properly const isVisible = await page.isVisible('body'); expect(isVisible).toBe(true); }); test('should display empty state in schedule search when no flights found', async ({ page }) => { // Navigate to schedule page await page.goto('/ru-ru/schedule'); // Wait for the page to fully load await page.waitForLoadState('networkidle'); // The schedule page should load without errors const titleVisible = await page.isVisible('h1'); expect(titleVisible).toBe(true); }); test('should have proper accessibility features in empty state', async ({ page }) => { // Navigate to a page that displays empty state await page.goto('/ru-ru/onlineboard?city=MOW'); // Wait for loading await page.waitForLoadState('networkidle'); // Check for proper semantic HTML (article role) const emptyStateContainer = page.locator('article[role="article"]').first(); // If empty state is displayed, verify accessibility if (await emptyStateContainer.isVisible()) { // Check that it has aria-label const ariaLabel = await emptyStateContainer.getAttribute('aria-label'); expect(ariaLabel).toBeTruthy(); // Check for proper heading hierarchy const heading = page.locator('article h2').first(); if (await heading.isVisible()) { expect(await heading.textContent()).toBeTruthy(); } } }); test('should not have console errors when empty state is displayed', async ({ page }) => { const errors: string[] = []; const warnings: string[] = []; page.on('console', (msg) => { if (msg.type() === 'error') { errors.push(msg.text()); } if (msg.type() === 'warning') { warnings.push(msg.text()); } }); // Navigate to page await page.goto('/ru-ru/onlineboard?city=MOW'); await page.waitForLoadState('networkidle'); // Critical errors should not occur const criticalErrors = errors.filter( (e) => !e.includes('Unexpected token') && !e.includes('Failed to fetch'), ); expect(criticalErrors.length).toBe(0); }); test('should render empty state with correct locale (ru-ru)', async ({ page }) => { // Navigate to page with Russian locale await page.goto('/ru-ru/onlineboard?city=MOW'); // Wait for loading await page.waitForLoadState('networkidle'); // Check if page is in Russian locale const htmlLang = await page.getAttribute('html', 'lang'); expect(['ru', 'ru-RU', 'ru-ru']).toContain(htmlLang); }); test('should have proper button semantics if actions are available', async ({ page }) => { // Navigate to schedule page await page.goto('/ru-ru/schedule'); // Wait for load await page.waitForLoadState('networkidle'); // If empty state has action buttons, verify they're proper buttons const buttons = page.locator('article button').all(); const allButtons = await buttons; for (const button of allButtons) { // Each button should have proper role const role = await button.getAttribute('role'); const ariaLabel = await button.getAttribute('aria-label'); // Buttons should be semantically correct expect(await button.tagName()).toBe('BUTTON'); } }); test('should display empty state with responsive design on mobile', async ({ page }) => { // Set mobile viewport await page.setViewportSize({ width: 375, height: 667 }); // Navigate to page await page.goto('/ru-ru/onlineboard?city=MOW'); // Wait for load await page.waitForLoadState('networkidle'); // Check if content is visible const isVisible = await page.isVisible('body'); expect(isVisible).toBe(true); // Verify no horizontal scroll is needed (check viewport) const bodyWidth = await page.evaluate(() => document.documentElement.clientWidth); expect(bodyWidth).toBeLessThanOrEqual(375); }); test('should display empty state with responsive design on desktop', async ({ page }) => { // Set desktop viewport await page.setViewportSize({ width: 1920, height: 1080 }); // Navigate to page await page.goto('/ru-ru/onlineboard?city=MOW'); // Wait for load await page.waitForLoadState('networkidle'); // Check if content is visible const isVisible = await page.isVisible('body'); expect(isVisible).toBe(true); }); test('should meet WCAG 2.1 minimum touch target size (44px)', async ({ page }) => { // Navigate to schedule page await page.goto('/ru-ru/schedule'); // Wait for load await page.waitForLoadState('networkidle'); // Get all buttons in empty state const buttons = page.locator('article button'); const count = await buttons.count(); for (let i = 0; i < count; i++) { const button = buttons.nth(i); // Get button dimensions const box = await button.boundingBox(); if (box) { // Check that button meets minimum touch target size (44x44px) expect(box.width).toBeGreaterThanOrEqual(44); expect(box.height).toBeGreaterThanOrEqual(44); } } }); test('should not have layout shifts when empty state is displayed', async ({ page }) => { // Listen for layout shifts (Cumulative Layout Shift metric) let hasLayoutShift = false; page.on('framenavigated', () => { hasLayoutShift = false; }); // Navigate to page await page.goto('/ru-ru/onlineboard?city=MOW'); // Wait for stabilization await page.waitForLoadState('networkidle'); // Give time for layout to settle await page.waitForTimeout(1000); // Verify page is stable const isStable = await page.evaluate(() => { return !document.querySelector('[data-testid*="loading"]'); }); expect(isStable || !hasLayoutShift).toBeTruthy(); }); });