Files
flights_web/tests/e2e-angular/error-handling.spec.ts
T
gnezim 375bcfb0fa Add e2e test suite from flights-front with Angular API mocks
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.
2026-04-15 23:07:44 +03:00

327 lines
11 KiB
TypeScript

import { test, expect } from '@playwright/test';
test.describe('Error Handling (US-85, US-86, US-88) - React ru-ru', () => {
// Collect console errors for all tests
let consoleErrors: string[] = [];
test.beforeEach(async ({ page }) => {
// Clear previous errors
consoleErrors = [];
// Set Russian locale
await page.addInitScript(() => {
Object.defineProperty(navigator, 'language', {
get: () => 'ru-RU',
});
Object.defineProperty(navigator, 'languages', {
get: () => ['ru-RU', 'ru'],
});
});
// Collect console errors throughout test
page.on('console', (msg) => {
if (msg.type() === 'error') {
consoleErrors.push(msg.text());
}
});
});
test.afterEach(async ({ page }) => {
// Clean up all routes
await page.unroute('**/*');
// Close any dialogs
await page.keyboard.press('Escape').catch(() => {});
// Verify no unexpected console errors occurred
const unexpectedErrors = consoleErrors.filter(
(msg) =>
!msg.includes('Failed to fetch') &&
!msg.includes('Network error') &&
!msg.includes('404') &&
!msg.includes('500'),
);
if (unexpectedErrors.length > 0) {
console.warn('Unexpected console errors:', unexpectedErrors);
}
});
test('US-85: 404 Not Found - navigate to invalid route and verify Russian error page', async ({
page,
}) => {
// Navigate to non-existent route
await page.goto('http://localhost:3001/ru-ru/invalid-route-xyz');
// Verify NotFoundPage renders with 404 heading visible
const notFoundHeading = page.locator('h1:has-text("404")');
await expect(notFoundHeading).toBeVisible();
// Verify Russian title is displayed
const rusTitle = page.locator('text=Страница не найдена');
await expect(rusTitle).toBeVisible({ timeout: 5000 });
// Verify Russian description is displayed
const rusDescription = page.locator('text=/Извините|не существует/');
await expect(rusDescription).toBeVisible();
// Verify home link exists and is clickable
const homeLink = page.getByRole('link', { name: /На главную|Home/i });
await expect(homeLink).toBeVisible();
// Verify no unexpected console errors
expect(
consoleErrors.filter((e) => !e.includes('404') && !e.includes('Failed to fetch')),
).toHaveLength(0);
});
test('US-85: 404 Not Found - home link navigates back to onlineboard', async ({ page }) => {
// Navigate to invalid route
await page.goto('http://localhost:3001/ru-ru/not-found-test');
// Verify 404 page appears with Russian text
await expect(page.locator('h1:has-text("404")')).toBeVisible();
await expect(page.locator('text=Страница не найдена')).toBeVisible();
// Click home link to navigate back
const homeLink = page.getByRole('link', { name: /На главную|Home/i });
await homeLink.click();
// Verify navigation returns to home page
await page.waitForURL(/\/ru-ru\/(onlineboard|flights)?/);
// Wait for page to fully load
await page.waitForLoadState('networkidle');
// Verify page content loaded (should show main flight board)
const mainContent = page.locator('main[role="main"]');
await expect(mainContent).toBeVisible({ timeout: 5000 });
});
test('US-86: 500 Server Error - HTTP 500 response renders error page with Russian text', async ({
page,
}) => {
// Set up route to respond with actual HTTP 500 status code
let requestCount = 0;
await page.route('**/api/**', (route) => {
requestCount++;
if (requestCount === 1) {
// Respond with actual HTTP 500
route.respond({
status: 500,
contentType: 'application/json',
body: JSON.stringify({ error: 'Server Error' }),
});
} else {
route.continue();
}
});
// Navigate to page that requires API
await page.goto('http://localhost:3001/ru-ru/onlineboard', {
waitUntil: 'domcontentloaded',
});
// Wait for error handling to occur
await page.waitForTimeout(2000);
// Verify ServerErrorPage renders with 500 heading visible
const serverErrorHeading = page.locator('h1:has-text("500")');
await expect(serverErrorHeading).toBeVisible({ timeout: 5000 });
// Verify Russian error title is displayed
const rusTitle = page.locator('text=Ошибка сервера');
await expect(rusTitle).toBeVisible();
// Verify Russian description is displayed
const rusDescription = page.locator('text=/К сожалению|произошла ошибка/');
await expect(rusDescription).toBeVisible();
// Verify error page has role="alert" for accessibility
const alertMain = page.locator('main[role="alert"]');
await expect(alertMain).toBeVisible();
});
test('US-86: 500 Server Error - "Try Again" button reloads and retries API', async ({ page }) => {
let requestCount = 0;
let secondRequestMade = false;
// Set up route to fail first request, succeed on second
await page.route('**/api/**', (route) => {
requestCount++;
if (requestCount === 1) {
// First request: respond with HTTP 500
route.respond({
status: 500,
contentType: 'application/json',
body: JSON.stringify({ error: 'Server Error' }),
});
} else {
// Second request: succeed
secondRequestMade = true;
route.continue();
}
});
// Navigate to page
await page.goto('http://localhost:3001/ru-ru/onlineboard', {
waitUntil: 'domcontentloaded',
});
// Wait for error page to render
await page.waitForTimeout(1500);
// Verify 500 error page is visible
await expect(page.locator('h1:has-text("500")')).toBeVisible();
await expect(page.locator('text=Ошибка сервера')).toBeVisible();
// Find and click "Try Again" button (should trigger page reload)
const tryAgainButton = page.getByRole('button', { name: /Try Again|Перезагрузить/i });
await expect(tryAgainButton).toBeVisible();
// Track if new request is made after button click
const requestsBeforeClick = requestCount;
await tryAgainButton.click();
// Wait for new request to be made
await page.waitForTimeout(1500);
// Verify error page is hidden after retry
await expect(page.locator('h1:has-text("500")')).toBeHidden({ timeout: 5000 });
});
test('US-86: 500 Server Error - "Go Home" link navigates away from error', async ({ page }) => {
// Set up route to respond with HTTP 500
await page.route('**/api/**', (route) => {
route.respond({
status: 500,
contentType: 'application/json',
body: JSON.stringify({ error: 'Server Error' }),
});
});
// Navigate to page
await page.goto('http://localhost:3001/ru-ru/onlineboard', {
waitUntil: 'domcontentloaded',
});
// Wait for error page to render
await page.waitForTimeout(1500);
// Verify 500 error page is visible
await expect(page.locator('h1:has-text("500")')).toBeVisible();
// Find and click "Go Home" link
const goHomeLink = page.getByRole('link', { name: /Go Home|На главную/i });
await expect(goHomeLink).toBeVisible();
await goHomeLink.click();
// Verify navigation happens (should go to home route)
await page.waitForURL(/\/ru-ru\/(|flights|onlineboard)/);
});
test('US-88: Timeout Detection - indicator appears after 30 seconds of waiting', async ({
page,
}) => {
// Set up route to delay response beyond 30 second timeout
await page.route('**/api/**', async (route) => {
await new Promise((resolve) => setTimeout(resolve, 32000));
await route.continue().catch(() => {});
});
// Track when timeout indicator appears
const startTime = Date.now();
let timeoutAppearedAt: number | null = null;
// Navigate and allow slow load
const navigationPromise = page.goto('http://localhost:3001/ru-ru/onlineboard', {
waitUntil: 'domcontentloaded',
timeout: 35000,
});
// Wait for timeout indicator to appear (should be around 30 seconds)
// Start checking at 25 seconds to catch it
await page.waitForTimeout(25000);
// Poll for timeout indicator appearance every 500ms for up to 10 seconds
for (let i = 0; i < 20; i++) {
const indicator = page.locator('[role="alert"]').filter({
hasText: /timeout|истекло|time|истек/i,
});
if (await indicator.isVisible().catch(() => false)) {
timeoutAppearedAt = Date.now() - startTime;
break;
}
await page.waitForTimeout(500);
}
// Verify timeout indicator appeared within expected window (28-32 seconds = 30±2s tolerance)
expect(timeoutAppearedAt).not.toBeNull();
if (timeoutAppearedAt !== null) {
expect(timeoutAppearedAt).toBeGreaterThanOrEqual(28000); // 28 seconds
expect(timeoutAppearedAt).toBeLessThanOrEqual(32000); // 32 seconds
}
// Verify Russian timeout text is visible
const rusTimeout = page.locator('text=/Истекло время|Запрос занял/');
await expect(rusTimeout).toBeVisible({ timeout: 5000 });
// Allow navigation to complete
await navigationPromise.catch(() => {});
});
test('US-88: Timeout Detection - retry button exists and re-executes search', async ({
page,
}) => {
let firstRequestCompleted = false;
let retryRequestMade = false;
// Set up route to delay first request
await page.route('**/api/**', async (route) => {
if (!firstRequestCompleted) {
firstRequestCompleted = true;
// Delay first request beyond timeout
await new Promise((resolve) => setTimeout(resolve, 32000));
await route.continue().catch(() => {});
} else {
// Subsequent requests succeed immediately
retryRequestMade = true;
await route.continue();
}
});
// Navigate to page
const navigationPromise = page.goto('http://localhost:3001/ru-ru/onlineboard', {
waitUntil: 'domcontentloaded',
timeout: 35000,
});
// Wait for timeout to appear and retry button to be available
await page.waitForTimeout(31000);
// Find retry button in timeout indicator
const retryButton = page
.locator('[role="alert"]')
.filter({ hasText: /Повторить|retry/i })
.locator('button');
const retryVisible = await retryButton.isVisible().catch(() => false);
if (retryVisible) {
// Click retry button
await retryButton.click({ timeout: 5000 });
// Wait briefly for new request to be made
await page.waitForTimeout(1500);
// Verify timeout indicator is hidden after retry
await expect(
page.locator('[role="alert"]').filter({ hasText: /timeout|истекло|time/i }),
).toBeHidden({ timeout: 5000 });
}
// Allow navigation to complete
await navigationPromise.catch(() => {});
});
});