20c19d15f4
Modern.js SSR intercepts all routes before any Express middleware, so the API proxy runs as a separate Express server on port 8080. Modern.js runs on 8081. The proxy uses curl subprocesses which go through the system HTTPS proxy (GOST) with a proper TLS fingerprint that the Aeroflot WAF accepts. Usage: node scripts/dev-server.mjs (replaces pnpm dev for full-stack) Also: remove stray e2e-angular test directory, fix env default to same-origin /api.
85 lines
3.0 KiB
TypeScript
85 lines
3.0 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
|
|
|
const BASE_URL = process.env.BASE_URL || 'http://localhost:3002';
|
|
|
|
test.describe('US-96: ARIA Labels & Semantic HTML', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/ru-ru/schedule`, { waitUntil: 'networkidle' });
|
|
await page.waitForLoadState('networkidle');
|
|
});
|
|
|
|
test('should have search section with proper aria-label', async ({ page }) => {
|
|
// Check for search section with role="search"
|
|
const searchSection = page.locator('[role="search"]').first();
|
|
if (await searchSection.isVisible()) {
|
|
const ariaLabel = await searchSection.getAttribute('aria-label');
|
|
expect(ariaLabel).toBeTruthy();
|
|
expect(ariaLabel?.length).toBeGreaterThan(0);
|
|
}
|
|
});
|
|
|
|
test('should have form fields with aria-label or associated labels', async ({ page }) => {
|
|
// Look for search inputs in the form
|
|
const inputs = await page.locator('input[type="text"]').all();
|
|
// At least some inputs should have aria attributes or be associated with labels
|
|
let validatedCount = 0;
|
|
for (const input of inputs) {
|
|
const ariaLabel = await input.getAttribute('aria-label');
|
|
const ariaLabelledby = await input.getAttribute('aria-labelledby');
|
|
|
|
if (ariaLabel || ariaLabelledby) {
|
|
validatedCount++;
|
|
}
|
|
}
|
|
expect(validatedCount).toBeGreaterThan(0);
|
|
});
|
|
|
|
test('should have buttons with aria-label or visible text', async ({ page }) => {
|
|
const buttons = await page.locator('button').all();
|
|
let validatedCount = 0;
|
|
for (const button of buttons) {
|
|
const ariaLabel = await button.getAttribute('aria-label');
|
|
const text = (await button.textContent())?.trim();
|
|
|
|
if (ariaLabel || (text && text.length > 0)) {
|
|
validatedCount++;
|
|
}
|
|
}
|
|
expect(validatedCount).toBeGreaterThan(0);
|
|
});
|
|
|
|
test('should have semantic HTML structure', async ({ page }) => {
|
|
// Check that page has proper semantic structure
|
|
const headings = page.locator('h1, h2, h3, h4, h5, h6');
|
|
const headingCount = await headings.count();
|
|
// Should have at least one heading for proper semantic structure
|
|
expect(headingCount).toBeGreaterThanOrEqual(0);
|
|
});
|
|
|
|
test('should have error messages with aria-live role="alert"', async ({ page }) => {
|
|
// Check if alert role exists (might not if no validation error)
|
|
const alerts = page.locator('[role="alert"]');
|
|
const alertCount = await alerts.count();
|
|
if (alertCount > 0) {
|
|
for (const alert of await alerts.all()) {
|
|
const ariaLive = await alert.getAttribute('aria-live');
|
|
expect(ariaLive).toBeTruthy();
|
|
}
|
|
}
|
|
});
|
|
|
|
test('should have zero console errors', async ({ page }) => {
|
|
const errors: string[] = [];
|
|
page.on('console', (msg) => {
|
|
if (msg.type() === 'error') {
|
|
errors.push(msg.text());
|
|
}
|
|
});
|
|
|
|
await page.goto(`${BASE_URL}/ru-ru/schedule`, { waitUntil: 'networkidle' });
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
expect(errors).toHaveLength(0);
|
|
});
|
|
});
|