375bcfb0fa
Copies Playwright e2e tests (58 specs, 300+ tests) designed for cross-app testing. Adapts API mocks to match real Aeroflot dictionary format (title objects with multilingual keys), adds board/schedule/days endpoint mocks, and provides Angular-specific Playwright config on port 4203.
212 lines
6.6 KiB
TypeScript
212 lines
6.6 KiB
TypeScript
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();
|
|
});
|
|
});
|