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.
327 lines
11 KiB
TypeScript
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(() => {});
|
|
});
|
|
});
|