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.
154 lines
5.5 KiB
TypeScript
154 lines
5.5 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
|
|
|
const BASE_URL = process.env.BASE_URL || 'http://localhost:3002';
|
|
|
|
test.describe('US-95: Keyboard Navigation', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/ru-ru/schedule`, { waitUntil: 'networkidle' });
|
|
await page.waitForLoadState('networkidle');
|
|
});
|
|
|
|
test('should navigate search form with Tab key', async ({ page }) => {
|
|
// Get the search form
|
|
const searchForm = page.getByTestId('schedule-search-form');
|
|
await expect(searchForm).toBeVisible();
|
|
|
|
// Start with Tab from body - should focus first interactive element
|
|
await page.keyboard.press('Tab');
|
|
const focusedElement1 = await page.evaluate(() => {
|
|
const el = document.activeElement as HTMLElement;
|
|
return el?.id || el?.getAttribute('data-testid') || el?.tagName;
|
|
});
|
|
|
|
expect(focusedElement1).toBeTruthy();
|
|
|
|
// Continue tabbing through form
|
|
for (let i = 0; i < 5; i++) {
|
|
await page.keyboard.press('Tab');
|
|
}
|
|
|
|
const focusedElement2 = await page.evaluate(() => {
|
|
const el = document.activeElement as HTMLElement;
|
|
return el?.id || el?.getAttribute('data-testid') || el?.tagName;
|
|
});
|
|
|
|
// Should have moved to a different element
|
|
expect(focusedElement2).toBeTruthy();
|
|
expect(focusedElement2).not.toBe(focusedElement1);
|
|
});
|
|
|
|
test('should support Tab navigation to date inputs', async ({ page }) => {
|
|
// Tab to the date input
|
|
for (let i = 0; i < 3; i++) {
|
|
await page.keyboard.press('Tab');
|
|
}
|
|
|
|
// Current focus should be on a form element
|
|
const focusedEl = await page.evaluate(() => {
|
|
const el = document.activeElement as HTMLElement;
|
|
return el?.getAttribute('id') || el?.tagName;
|
|
});
|
|
|
|
expect(['date-from', 'INPUT']).toContain(focusedEl);
|
|
});
|
|
|
|
test('should have proper tabIndex on form elements', async ({ page }) => {
|
|
// Check that key form elements have proper tabIndex attributes
|
|
const departureInput = page.locator('[data-testid="schedule-departure-input"] input').first();
|
|
const arrivalInput = page.locator('[data-testid="schedule-arrival-input"] input').first();
|
|
const dateFromInput = page.locator('#date-from');
|
|
const dateToInput = page.locator('#date-to');
|
|
const searchButton = page.getByTestId('schedule-search-button');
|
|
|
|
// All should be visible
|
|
await expect(departureInput).toBeVisible();
|
|
await expect(arrivalInput).toBeVisible();
|
|
await expect(dateFromInput).toBeVisible();
|
|
await expect(dateToInput).toBeVisible();
|
|
await expect(searchButton).toBeVisible();
|
|
|
|
// Check tabIndex attributes exist
|
|
const depTabIndex = await departureInput.getAttribute('tabindex');
|
|
const arrTabIndex = await arrivalInput.getAttribute('tabindex');
|
|
const dateFromTabIndex = await dateFromInput.getAttribute('tabindex');
|
|
const dateToTabIndex = await dateToInput.getAttribute('tabindex');
|
|
const btnTabIndex = await searchButton.getAttribute('tabindex');
|
|
|
|
// Either tabIndex is set or it's a native form element (which is keyboard accessible by default)
|
|
expect([depTabIndex, '0']).toContain(depTabIndex || '0');
|
|
expect([arrTabIndex, '1']).toContain(arrTabIndex || '1');
|
|
expect([dateFromTabIndex, '2']).toContain(dateFromTabIndex || '2');
|
|
expect([dateToTabIndex, '3']).toContain(dateToTabIndex || '3');
|
|
expect([btnTabIndex, '7']).toContain(btnTabIndex || '7');
|
|
});
|
|
|
|
test('should have no keyboard traps in form', async ({ page }) => {
|
|
const searchForm = page.getByTestId('schedule-search-form');
|
|
await expect(searchForm).toBeVisible();
|
|
|
|
// Tab through the form multiple times
|
|
for (let i = 0; i < 20; i++) {
|
|
await page.keyboard.press('Tab');
|
|
}
|
|
|
|
// Should be able to reach some interactive element (not stuck in a trap)
|
|
const activeElement = await page.evaluate(() => {
|
|
const el = document.activeElement;
|
|
return el?.tagName;
|
|
});
|
|
|
|
expect(['BUTTON', 'A', 'INPUT', 'SELECT', 'TEXTAREA', 'DIV']).toContain(activeElement);
|
|
});
|
|
|
|
test('should have zero console errors during keyboard navigation', async ({ page }) => {
|
|
const errors: string[] = [];
|
|
|
|
page.on('console', (msg) => {
|
|
if (msg.type() === 'error') {
|
|
errors.push(msg.text());
|
|
}
|
|
});
|
|
|
|
// Perform keyboard navigation
|
|
await page.keyboard.press('Tab');
|
|
await page.keyboard.press('Tab');
|
|
await page.keyboard.press('Tab');
|
|
await page.keyboard.press('Tab');
|
|
await page.keyboard.press('Tab');
|
|
|
|
// Should have no console errors
|
|
expect(errors).toHaveLength(0);
|
|
});
|
|
|
|
test('should support Tab navigation through all interactive elements in order', async ({
|
|
page,
|
|
}) => {
|
|
// Get initial focus
|
|
await page.keyboard.press('Tab');
|
|
const firstFocused = await page.evaluate(() => {
|
|
const el = document.activeElement as HTMLElement;
|
|
return el?.getAttribute('data-testid') || el?.id;
|
|
});
|
|
|
|
// The first element should be the departure input
|
|
expect(firstFocused).toBeTruthy();
|
|
|
|
// Tab several more times
|
|
const focusedElements = [firstFocused];
|
|
for (let i = 0; i < 10; i++) {
|
|
await page.keyboard.press('Tab');
|
|
const focused = await page.evaluate(() => {
|
|
const el = document.activeElement as HTMLElement;
|
|
return el?.getAttribute('data-testid') || el?.id || el?.getAttribute('type');
|
|
});
|
|
if (focused) {
|
|
focusedElements.push(focused);
|
|
}
|
|
}
|
|
|
|
// Should have visited multiple different elements
|
|
const uniqueElements = new Set(focusedElements);
|
|
expect(uniqueElements.size).toBeGreaterThan(1);
|
|
});
|
|
});
|