Remove React-specific test files from Angular e2e suite

Removed 40 files that were written for the flights-front React project:
- 15 root spec files with hardcoded React ports (3000/3001/3002/5173)
- 8 ru-ru/ tests with React URLs or React Query-specific features
- 8 integration/ tests with React templates
- 3 visual/ regression tests with React screenshots
- 6 integration templates

Kept: 18 cross-app tests (properly use localePath/urlPattern fixtures),
7 clean ru-ru tests, support files, and fixtures.

Result: 230 passed, 173 skipped, 0 failed.
This commit is contained in:
2026-04-16 00:01:03 +03:00
parent 94da763f22
commit c6b865b324
41 changed files with 0 additions and 17418 deletions
-182
View File
@@ -1,182 +0,0 @@
import { test, expect } from '@playwright/test';
test.describe('Console Error-Free Audit (US-11)', () => {
let consoleMessages: Array<{ type: string; text: string }> = [];
test.beforeEach(async ({ page }) => {
consoleMessages = [];
// Capture console messages
page.on('console', (msg) => {
consoleMessages.push({
type: msg.type(),
text: msg.text(),
});
});
// Capture page errors
page.on('pageerror', (error) => {
consoleMessages.push({
type: 'error',
text: error.toString(),
});
});
});
test('online board page should be error-free', async ({ page }) => {
await page.goto('http://localhost:3002/ru-ru/onlineboard');
await page.waitForLoadState('networkidle');
// Perform interactions
const flightTab = page.locator('[data-testid="search-tab-flight"]');
if ((await flightTab.count()) > 0) {
await flightTab.click();
await page.waitForTimeout(500);
}
const routeTab = page.locator('[data-testid="search-tab-route"]');
if ((await routeTab.count()) > 0) {
await routeTab.click();
await page.waitForTimeout(500);
}
// Check for errors
const errors = consoleMessages.filter((m) => m.type === 'error');
expect(errors).toEqual([]);
});
test('schedule page should be error-free', async ({ page }) => {
await page.goto('http://localhost:3002/ru-ru/schedule');
await page.waitForLoadState('networkidle');
// Check for errors
const errors = consoleMessages.filter((m) => m.type === 'error');
expect(errors).toEqual([]);
});
test('flights map page should be error-free', async ({ page }) => {
await page.goto('http://localhost:3002/ru-ru/flights-map');
await page.waitForLoadState('networkidle');
// Check for errors
const errors = consoleMessages.filter((m) => m.type === 'error');
expect(errors).toEqual([]);
});
test('language switching should not cause errors', async ({ page }) => {
await page.goto('http://localhost:3002/ru-ru/onlineboard');
await page.waitForLoadState('networkidle');
// Try to switch languages
const localeEn = page.locator('[data-testid="locale-en-us"]');
if ((await localeEn.count()) > 0) {
await localeEn.click();
await page.waitForLoadState('networkidle');
}
const localeRu = page.locator('[data-testid="locale-ru-ru"]');
if ((await localeRu.count()) > 0) {
await localeRu.click();
await page.waitForLoadState('networkidle');
}
// Check for errors
const errors = consoleMessages.filter((m) => m.type === 'error');
expect(errors).toEqual([]);
});
test('tab navigation should not cause errors', async ({ page }) => {
await page.goto('http://localhost:3002/ru-ru/onlineboard');
await page.waitForLoadState('networkidle');
const tabs = [
page.locator('[data-testid="tab-onlineboard"]'),
page.locator('[data-testid="tab-schedule"]'),
page.locator('[data-testid="tab-map"]'),
];
for (const tab of tabs) {
if ((await tab.count()) > 0) {
await tab.click();
await page.waitForLoadState('networkidle');
}
}
// Check for errors
const errors = consoleMessages.filter((m) => m.type === 'error');
expect(errors).toEqual([]);
});
test('scroll interactions should not cause errors', async ({ page }) => {
await page.goto('http://localhost:3002/ru-ru/onlineboard');
await page.waitForLoadState('networkidle');
// Scroll down
await page.evaluate(() => window.scrollBy(0, 500));
await page.waitForTimeout(300);
// Scroll up
await page.evaluate(() => window.scrollTo(0, 0));
await page.waitForTimeout(300);
// Check for errors
const errors = consoleMessages.filter((m) => m.type === 'error');
expect(errors).toEqual([]);
});
test('should have no JavaScript errors during full user flow', async ({ page }) => {
const errors: string[] = [];
page.on('console', (msg) => {
if (msg.type() === 'error') {
errors.push(msg.text());
}
});
page.on('pageerror', (error) => {
errors.push(error.toString());
});
// Online Board flow
await page.goto('http://localhost:3002/ru-ru/onlineboard');
await page.waitForLoadState('networkidle');
const flightInput = page.locator('[data-testid="search-flight-number"]');
if ((await flightInput.count()) > 0) {
await flightInput.fill('SU1402');
await page.waitForTimeout(500);
}
// Schedule flow
const scheduleTab = page.locator('[data-testid="tab-schedule"]');
if ((await scheduleTab.count()) > 0) {
await scheduleTab.click();
await page.waitForLoadState('networkidle');
await page.waitForTimeout(500);
}
// Map flow (if available)
const mapTab = page.locator('[data-testid="tab-map"]');
if ((await mapTab.count()) > 0) {
await mapTab.click();
await page.waitForLoadState('networkidle');
}
// Language switch
const localeEn = page.locator('[data-testid="locale-en-us"]');
if ((await localeEn.count()) > 0) {
await localeEn.click();
await page.waitForLoadState('networkidle');
}
// Back to Russian
const localeRu = page.locator('[data-testid="locale-ru-ru"]');
if ((await localeRu.count()) > 0) {
await localeRu.click();
await page.waitForLoadState('networkidle');
}
// Final check
expect(errors).toEqual([]);
});
});
-326
View File
@@ -1,326 +0,0 @@
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(() => {});
});
});
File diff suppressed because it is too large Load Diff
-338
View File
@@ -1,338 +0,0 @@
import { test, expect } from '@playwright/test';
test.describe('Flight Results - Document 2 (US-18, US-19, US-20, US-22)', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/ru-ru/onlineboard');
await page.waitForLoadState('networkidle');
});
test.describe('US-18: Time Range Filter', () => {
test('should display time range slider in route search', async ({ page }) => {
// Navigate to route search tab (if available via search panel)
// Time range slider should be present in SearchByRoute
const timeSlider = page.locator('[data-testid="filter-route-time-selector"]');
// Note: Slider is in search panel which may be on different page
expect(page.locator('body')).toBeTruthy();
});
test('should support time range from 00:00 to 24:00', async ({ page }) => {
// When searching, time range should be available
// Default should be 00:00 to 24:00
const searchPage = page.locator('[data-testid="landing-section"]');
expect(searchPage).toBeTruthy();
});
test('should display time range values in HH:MM format', async ({ page }) => {
// Time values should be displayed as HH:MM
// e.g., "08:00 — 14:30"
const timeDisplay = page.locator('text=/\\d{2}:\\d{2}\\s*—\\s*\\d{2}:\\d{2}/');
// May or may not be visible depending on search state
expect(page.locator('body')).toBeTruthy();
});
test('should allow filtering flights by departure time range', async ({ page }) => {
// User should be able to adjust time range slider
// Results should filter based on time range
const timeSlider = page.locator('[data-testid="filter-route-time-selector"]');
// Note: Tested on search panel component
expect(page.locator('body')).toBeTruthy();
});
test('should allow filtering flights by arrival time range', async ({ page }) => {
// Similar to departure, arrival time range should work
expect(page.locator('body')).toBeTruthy();
});
});
test.describe('US-19: Flight Details View', () => {
test('should render flight list with clickable items', async ({ page }) => {
// Search for a flight to get results
const flightInput = page.locator('[data-testid="filter-flight-number-input"]');
if ((await flightInput.count()) > 0) {
await flightInput.fill('1402');
const searchBtn = page
.locator('button:has-text("Найти")')
.or(page.locator('button:has-text("Find")'));
if ((await searchBtn.count()) > 0) {
await searchBtn.click();
await page.waitForLoadState('networkidle');
// Flight results should be displayed
const flightList = page.locator('[data-testid="board-search-result"]');
expect(flightList).toBeTruthy();
}
}
});
test('should display flight information (number, times, status)', async ({ page }) => {
// When results are shown, each flight item should display:
// - Flight number
// - Departure/arrival times
// - Status
const flightCards = page.locator('[data-testid="flight-item"]');
// May not have results immediately
expect(page.locator('body')).toBeTruthy();
});
test('should make flight items clickable', async ({ page }) => {
// Flight items should be clickable buttons
const flightItems = page.locator('button:has-text(/SU\\d+/)');
if ((await flightItems.count()) > 0) {
// Should be able to click
const firstItem = flightItems.first();
expect(firstItem).toBeTruthy();
}
});
test('should show flight details when clicking flight', async ({ page }) => {
// Click on a flight should navigate to details page or show modal
const flightButton = page.locator('button:has-text(/SU\\d+/)').first();
if ((await flightButton.count()) > 0) {
await flightButton.click();
// Should navigate to flight details or show modal
await page.waitForLoadState('networkidle');
expect(page.url()).toBeTruthy();
}
});
test('should display all flight data fields', async ({ page }) => {
// Flight details should include all relevant information
expect(page.locator('body')).toBeTruthy();
});
});
test.describe('US-20: Empty Results Handling', () => {
test('should show empty state when no results found', async ({ page }) => {
// Search for non-existent flight
const flightInput = page.locator('[data-testid="filter-flight-number-input"]');
if ((await flightInput.count()) > 0) {
await flightInput.fill('XX9999');
const searchBtn = page
.locator('button:has-text("Найти")')
.or(page.locator('button:has-text("Find")'));
if ((await searchBtn.count()) > 0) {
await searchBtn.click();
await page.waitForLoadState('networkidle');
// Should show empty state message
const emptyState = page.locator('[data-testid="board-empty-list"]');
if ((await emptyState.count()) > 0) {
expect(emptyState).toBeTruthy();
} else {
// Or show no results message
const noResults = page.locator('text=/no results|не найдено|нет результатов/i');
expect(noResults.count()).toBeGreaterThanOrEqual(0);
}
}
}
});
test('should provide helpful empty state message', async ({ page }) => {
// Empty state should have clear message
const emptyState = page.locator('[data-testid="board-empty-list"]');
if ((await emptyState.count()) > 0) {
const text = await emptyState.textContent();
expect(text).toBeTruthy();
}
});
test('should not show flight list when empty', async ({ page }) => {
// Flight list should not be present in empty state
const flightInput = page.locator('[data-testid="filter-flight-number-input"]');
if ((await flightInput.count()) > 0) {
await flightInput.fill('XX9999');
const searchBtn = page
.locator('button:has-text("Найти")')
.or(page.locator('button:has-text("Find")'));
if ((await searchBtn.count()) > 0) {
await searchBtn.click();
await page.waitForLoadState('networkidle');
const flightList = page.locator('[data-testid="board-search-result"]');
expect(await flightList.count()).toBe(0);
}
}
});
test('should show flight list when results exist', async ({ page }) => {
// Flight list should be shown with results
const flightInput = page.locator('[data-testid="filter-flight-number-input"]');
if ((await flightInput.count()) > 0) {
await flightInput.fill('1402');
const searchBtn = page
.locator('button:has-text("Найти")')
.or(page.locator('button:has-text("Find")'));
if ((await searchBtn.count()) > 0) {
await searchBtn.click();
await page.waitForLoadState('networkidle');
const flightList = page.locator('[data-testid="board-search-result"]');
// List may be empty but should exist if search happened
expect(page.locator('body')).toBeTruthy();
}
}
});
test('should allow refining search after empty results', async ({ page }) => {
// User should be able to try new search
const flightInput = page.locator('[data-testid="filter-flight-number-input"]');
if ((await flightInput.count()) > 0) {
// First search
await flightInput.fill('XX9999');
const searchBtn = page
.locator('button:has-text("Найти")')
.or(page.locator('button:has-text("Find")'));
if ((await searchBtn.count()) > 0) {
await searchBtn.click();
await page.waitForLoadState('networkidle');
// Clear and search again
await flightInput.fill('1402');
await searchBtn.click();
await page.waitForLoadState('networkidle');
expect(flightInput).toHaveValue('1402');
}
}
});
});
test.describe('US-22: Loading Indicator', () => {
test('should show loading indicator during search', async ({ page }) => {
// When searching, loading should appear briefly
const flightInput = page.locator('[data-testid="filter-flight-number-input"]');
if ((await flightInput.count()) > 0) {
await flightInput.fill('1402');
const searchBtn = page
.locator('button:has-text("Найти")')
.or(page.locator('button:has-text("Find")'));
if ((await searchBtn.count()) > 0) {
await searchBtn.click();
// Loading indicator should appear (may be brief)
const loading = page.locator('[data-testid="board-loader"]');
if ((await loading.count()) > 0) {
expect(loading).toBeTruthy();
}
}
}
});
test('should hide loading after results load', async ({ page }) => {
// After loading completes, indicator should disappear
const flightInput = page.locator('[data-testid="filter-flight-number-input"]');
if ((await flightInput.count()) > 0) {
await flightInput.fill('1402');
const searchBtn = page
.locator('button:has-text("Найти")')
.or(page.locator('button:has-text("Find")'));
if ((await searchBtn.count()) > 0) {
await searchBtn.click();
await page.waitForLoadState('networkidle');
// Loading should be gone
const loading = page.locator('[data-testid="board-loader"]');
expect(await loading.count()).toBe(0);
}
}
});
test('should show loading on page load with results', async ({ page }) => {
// When page loads with flight data, loading should appear
const loading = page.locator('[data-testid="board-loader"]');
// May or may not be visible depending on load speed
expect(page.locator('body')).toBeTruthy();
});
test('should show loading during transition between searches', async ({ page }) => {
// Switching between different searches should show loading
const flightInput = page.locator('[data-testid="filter-flight-number-input"]');
if ((await flightInput.count()) > 0) {
// First search
await flightInput.fill('1402');
const searchBtn = page
.locator('button:has-text("Найти")')
.or(page.locator('button:has-text("Find")'));
if ((await searchBtn.count()) > 0) {
await searchBtn.click();
await page.waitForLoadState('networkidle');
// Second search
await flightInput.fill('1403');
await searchBtn.click();
// Loading should show during transition
expect(page.locator('body')).toBeTruthy();
}
}
});
});
test.describe('Integration: Complete Search + Results Flow', () => {
test('should handle complete search workflow', async ({ page }) => {
// Full workflow:
// 1. User enters flight number
// 2. Clicks search
// 3. Loading shows
// 4. Results display
const flightInput = page.locator('[data-testid="filter-flight-number-input"]');
if ((await flightInput.count()) > 0) {
await flightInput.fill('1402');
const searchBtn = page
.locator('button:has-text("Найти")')
.or(page.locator('button:has-text("Find")'));
if ((await searchBtn.count()) > 0) {
await searchBtn.click();
await page.waitForLoadState('networkidle');
// Should have page with content
expect(page.locator('body')).toBeTruthy();
}
}
});
test('should support different search types', async ({ page }) => {
// Support flight number, route, and arrival searches
const tabs = page.locator('[data-testid^="search-tab-"]');
expect(tabs.count()).toBeGreaterThanOrEqual(0);
});
test('should handle fast successive searches', async ({ page }) => {
// User should be able to search multiple times quickly
const flightInput = page.locator('[data-testid="filter-flight-number-input"]');
if ((await flightInput.count()) > 0) {
for (let i = 0; i < 3; i++) {
await flightInput.fill(`140${i}`);
const searchBtn = page
.locator('button:has-text("Найти")')
.or(page.locator('button:has-text("Find")'));
if ((await searchBtn.count()) > 0) {
await searchBtn.click();
}
}
expect(page.locator('body')).toBeTruthy();
}
});
test('should preserve results during refetch', async ({ page }) => {
// When page refreshes or refetches, results should be maintained
const flightInput = page.locator('[data-testid="filter-flight-number-input"]');
if ((await flightInput.count()) > 0) {
await flightInput.fill('1402');
const searchBtn = page
.locator('button:has-text("Найти")')
.or(page.locator('button:has-text("Find")'));
if ((await searchBtn.count()) > 0) {
await searchBtn.click();
await page.waitForLoadState('networkidle');
// Reload page
await page.reload();
await page.waitForLoadState('networkidle');
expect(page.locator('body')).toBeTruthy();
}
}
});
});
});
-908
View File
@@ -1,908 +0,0 @@
import { test, expect } from '@playwright/test';
const BASE_URL = process.env.BASE_URL || 'http://localhost:5173';
test.describe('Flights Map (US-65 to US-69)', () => {
test.describe('US-65: Flights Map Tab Navigation', () => {
test('should navigate to flights map page from main board', async ({ page }) => {
// Navigate to main board
await page.goto(`${BASE_URL}/ru-ru/onlineboard`);
await page.waitForLoadState('networkidle');
// Look for flights map tab (third tab)
const flightsMapTab = page.locator('[data-testid="flights-map-tab"]');
if (await flightsMapTab.isVisible()) {
await flightsMapTab.click();
await page.waitForLoadState('networkidle');
// Verify URL changed to flights map
expect(page.url()).toContain('flights-map');
// Verify page title
const title = page.locator('h1');
await expect(title).toContainText(/map|карт/i);
}
});
test('should display map container on flights map page', async ({ page }) => {
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
await page.waitForLoadState('networkidle');
// Check for map container
const mapContainer = page.locator('[data-testid="map-container"]');
await expect(mapContainer).toBeVisible();
});
test('should display filter panel', async ({ page }) => {
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
await page.waitForLoadState('networkidle');
// Check for filter panel
const filterPanel = page.locator('[data-testid="flights-map-filter"]');
await expect(filterPanel).toBeVisible();
});
test('should show loading state initially', async ({ page }) => {
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
// Page should load and display map
const mapContainer = page.locator('[data-testid="map-container"]');
await expect(mapContainer).toBeVisible({ timeout: 10000 });
});
test('should have tab for flights map in navigation', async ({ page }) => {
await page.goto(`${BASE_URL}/ru-ru/onlineboard`);
await page.waitForLoadState('networkidle');
// Check if flights map tab exists in navigation
const tabNavigation = page.locator('[role="tablist"]');
if (await tabNavigation.isVisible()) {
const tabs = tabNavigation.locator('[role="tab"]');
const tabCount = await tabs.count();
expect(tabCount).toBeGreaterThanOrEqual(2); // At least Online Board and Schedule
}
});
test('should render page without 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/flights-map`);
await page.waitForLoadState('networkidle');
expect(errors.filter((e) => !e.includes('sourcemap'))).toEqual([]);
});
});
test.describe('US-66: Route Display on Map', () => {
test('should display routes after selecting departure city', async ({ page }) => {
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
await page.waitForLoadState('networkidle');
// Get departure input
const departureInput = page.locator('[data-testid="map-departure-input"]');
if (await departureInput.isVisible()) {
// Type departure city
await departureInput.fill('Moscow');
await page.waitForLoadState('networkidle');
// Select first suggestion
const suggestions = page.locator('[data-testid="city-suggestion"]');
if ((await suggestions.count()) > 0) {
await suggestions.first().click();
await page.waitForLoadState('networkidle');
// Verify map container still visible
const mapContainer = page.locator('[data-testid="map-container"]');
await expect(mapContainer).toBeVisible();
}
}
});
test('should render polylines for routes', async ({ page }) => {
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
await page.waitForLoadState('networkidle');
// Check for map svg (Leaflet renders routes as SVG)
const svgElements = page.locator('svg');
const svgCount = await svgElements.count();
expect(svgCount).toBeGreaterThan(0);
});
test('should apply color to routes', async ({ page }) => {
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
await page.waitForLoadState('networkidle');
// Check for colored elements (polylines)
const svgPaths = page.locator('svg path');
const pathCount = await svgPaths.count();
// If any paths exist, they should be rendered
if (pathCount > 0) {
const firstPath = svgPaths.first();
const stroke = await firstPath.evaluate((el) => window.getComputedStyle(el).stroke);
expect(stroke).toBeTruthy();
}
});
test('should handle multiple routes', async ({ page }) => {
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
await page.waitForLoadState('networkidle');
const departureInput = page.locator('[data-testid="map-departure-input"]');
if (await departureInput.isVisible()) {
await departureInput.fill('Moscow');
await page.waitForLoadState('networkidle');
// Wait for suggestions
const suggestions = page.locator('[data-testid="city-suggestion"]');
if ((await suggestions.count()) > 0) {
await suggestions.first().click();
await page.waitForLoadState('networkidle');
// Map should display multiple routes
const mapContainer = page.locator('[data-testid="map-container"]');
await expect(mapContainer).toBeVisible();
}
}
});
test('should update routes when filters change', async ({ page }) => {
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
await page.waitForLoadState('networkidle');
const mapContainer = page.locator('[data-testid="map-container"]');
await expect(mapContainer).toBeVisible();
// Change filter and verify map updates
const domesticToggle = page.locator('[data-testid="map-domestic-toggle"]');
if (await domesticToggle.isVisible()) {
await domesticToggle.click();
await page.waitForLoadState('networkidle');
// Map should still be visible after filter change
await expect(mapContainer).toBeVisible();
}
});
});
test.describe('US-67: Departure City Selection', () => {
test('should render departure city input', async ({ page }) => {
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
await page.waitForLoadState('networkidle');
const departureInput = page.locator('[data-testid="map-departure-input"]');
await expect(departureInput).toBeVisible();
});
test('should show suggestions when typing in departure input', async ({ page }) => {
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
await page.waitForLoadState('networkidle');
const departureInput = page.locator('[data-testid="map-departure-input"]');
await departureInput.fill('Mos');
await page.waitForTimeout(700); // Wait for debounce
// Check for suggestions dropdown
const suggestions = page.locator('[data-testid="city-suggestion"]');
const count = await suggestions.count();
expect(count).toBeGreaterThan(0);
});
test('should filter suggestions by city name', async ({ page }) => {
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
await page.waitForLoadState('networkidle');
const departureInput = page.locator('[data-testid="map-departure-input"]');
await departureInput.fill('Moscow');
await page.waitForTimeout(700);
const suggestions = page.locator('[data-testid="city-suggestion"]');
const firstSuggestion = suggestions.first();
const text = await firstSuggestion.textContent();
expect(text?.toLowerCase()).toContain('moscow');
});
test('should support city code input', async ({ page }) => {
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
await page.waitForLoadState('networkidle');
const departureInput = page.locator('[data-testid="map-departure-input"]');
await departureInput.fill('MOW');
await page.waitForTimeout(700);
const suggestions = page.locator('[data-testid="city-suggestion"]');
const count = await suggestions.count();
expect(count).toBeGreaterThan(0);
});
test('should select departure city from suggestions', async ({ page }) => {
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
await page.waitForLoadState('networkidle');
const departureInput = page.locator('[data-testid="map-departure-input"]');
await departureInput.fill('Moscow');
await page.waitForTimeout(700);
const suggestions = page.locator('[data-testid="city-suggestion"]');
if ((await suggestions.count()) > 0) {
await suggestions.first().click();
await page.waitForLoadState('networkidle');
// Input should contain selected city
const inputValue = await departureInput.inputValue();
expect(inputValue.length).toBeGreaterThan(0);
}
});
test('should require departure city', async ({ page }) => {
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
await page.waitForLoadState('networkidle');
const departureInput = page.locator('[data-testid="map-departure-input"]');
const required = await departureInput.evaluate((el: HTMLInputElement) => el.required);
expect(required).toBe(true);
});
});
test.describe('US-68: Arrival City Selection', () => {
test('should render arrival city input', async ({ page }) => {
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
await page.waitForLoadState('networkidle');
const arrivalInput = page.locator('[data-testid="map-arrival-input"]');
await expect(arrivalInput).toBeVisible();
});
test('should show suggestions when typing in arrival input', async ({ page }) => {
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
await page.waitForLoadState('networkidle');
// First select departure city
const departureInput = page.locator('[data-testid="map-departure-input"]');
await departureInput.fill('Moscow');
await page.waitForTimeout(700);
const depSuggestions = page.locator('[data-testid="city-suggestion"]');
if ((await depSuggestions.count()) > 0) {
await depSuggestions.first().click();
await page.waitForLoadState('networkidle');
// Now type in arrival input
const arrivalInput = page.locator('[data-testid="map-arrival-input"]');
await arrivalInput.fill('Par');
await page.waitForTimeout(700);
const arrivalSuggestions = page.locator('[data-testid="city-suggestion"]');
const count = await arrivalSuggestions.count();
expect(count).toBeGreaterThan(0);
}
});
test('should filter suggestions by arrival city name', async ({ page }) => {
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
await page.waitForLoadState('networkidle');
const departureInput = page.locator('[data-testid="map-departure-input"]');
await departureInput.fill('Moscow');
await page.waitForTimeout(700);
const suggestions = page.locator('[data-testid="city-suggestion"]');
if ((await suggestions.count()) > 0) {
await suggestions.first().click();
const arrivalInput = page.locator('[data-testid="map-arrival-input"]');
await arrivalInput.fill('Paris');
await page.waitForTimeout(700);
const arrivalSuggestions = page.locator('[data-testid="city-suggestion"]');
if ((await arrivalSuggestions.count()) > 0) {
const text = await arrivalSuggestions.first().textContent();
expect(text?.toLowerCase()).toContain('par');
}
}
});
test('should support arrival city code', async ({ page }) => {
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
await page.waitForLoadState('networkidle');
const departureInput = page.locator('[data-testid="map-departure-input"]');
await departureInput.fill('Moscow');
await page.waitForTimeout(700);
const suggestions = page.locator('[data-testid="city-suggestion"]');
if ((await suggestions.count()) > 0) {
await suggestions.first().click();
const arrivalInput = page.locator('[data-testid="map-arrival-input"]');
await arrivalInput.fill('CDG');
await page.waitForTimeout(700);
const arrivalSuggestions = page.locator('[data-testid="city-suggestion"]');
expect(await arrivalSuggestions.count()).toBeGreaterThan(0);
}
});
test('should select arrival city and display route', async ({ page }) => {
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
await page.waitForLoadState('networkidle');
const departureInput = page.locator('[data-testid="map-departure-input"]');
await departureInput.fill('Moscow');
await page.waitForTimeout(700);
const depSuggestions = page.locator('[data-testid="city-suggestion"]');
if ((await depSuggestions.count()) > 0) {
await depSuggestions.first().click();
const arrivalInput = page.locator('[data-testid="map-arrival-input"]');
await arrivalInput.fill('Paris');
await page.waitForTimeout(700);
const arrivalSuggestions = page.locator('[data-testid="city-suggestion"]');
if ((await arrivalSuggestions.count()) > 0) {
await arrivalSuggestions.first().click();
await page.waitForLoadState('networkidle');
const mapContainer = page.locator('[data-testid="map-container"]');
await expect(mapContainer).toBeVisible();
}
}
});
});
test.describe('US-69: Swap Cities Button', () => {
test('should display swap button between city inputs', async ({ page }) => {
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
await page.waitForLoadState('networkidle');
const swapButton = page.locator('[data-testid="swap-button"]');
await expect(swapButton).toBeVisible();
});
test('should swap cities when button is clicked', async ({ page }) => {
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
await page.waitForLoadState('networkidle');
const departureInput = page.locator('[data-testid="map-departure-input"]');
const arrivalInput = page.locator('[data-testid="map-arrival-input"]');
// Set initial cities
await departureInput.fill('Moscow');
await page.waitForTimeout(700);
const depSuggestions = page.locator('[data-testid="city-suggestion"]');
if ((await depSuggestions.count()) > 0) {
await depSuggestions.first().click();
await arrivalInput.fill('Paris');
await page.waitForTimeout(700);
const arrivalSuggestions = page.locator('[data-testid="city-suggestion"]');
if ((await arrivalSuggestions.count()) > 0) {
await arrivalSuggestions.first().click();
// Click swap button
const swapButton = page.locator('[data-testid="swap-button"]');
await swapButton.click();
await page.waitForLoadState('networkidle');
const depAfter = await departureInput.inputValue();
const arrAfter = await arrivalInput.inputValue();
// Cities should be swapped (approximately)
expect(depAfter.length).toBeGreaterThan(0);
expect(arrAfter.length).toBeGreaterThan(0);
}
}
});
test('should be keyboard accessible', async ({ page }) => {
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
await page.waitForLoadState('networkidle');
const swapButton = page.locator('[data-testid="swap-button"]');
// Focus on button
await swapButton.focus();
// Press Enter
await page.keyboard.press('Enter');
await page.waitForLoadState('networkidle');
// Page should still be functional
const mapContainer = page.locator('[data-testid="map-container"]');
await expect(mapContainer).toBeVisible();
});
test('should be mobile-friendly size', async ({ page }) => {
await page.setViewportSize({ width: 375, height: 667 });
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
await page.waitForLoadState('networkidle');
const swapButton = page.locator('[data-testid="swap-button"]');
await expect(swapButton).toBeVisible();
// Get button size
const box = await swapButton.boundingBox();
if (box) {
// Should be at least 44x44px for touch targets
expect(box.width).toBeGreaterThanOrEqual(40);
expect(box.height).toBeGreaterThanOrEqual(40);
}
});
test('should have accessible label', async ({ page }) => {
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
await page.waitForLoadState('networkidle');
const swapButton = page.locator('[data-testid="swap-button"]');
const ariaLabel = await swapButton.getAttribute('aria-label');
expect(ariaLabel).toBeTruthy();
});
});
test.describe('US-65-69: Responsive Design', () => {
test('should be responsive on mobile (375px)', async ({ page }) => {
await page.setViewportSize({ width: 375, height: 667 });
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
await page.waitForLoadState('networkidle');
const mapContainer = page.locator('[data-testid="map-container"]');
await expect(mapContainer).toBeVisible();
const filterPanel = page.locator('[data-testid="flights-map-filter"]');
await expect(filterPanel).toBeVisible();
});
test('should be responsive on tablet (768px)', async ({ page }) => {
await page.setViewportSize({ width: 768, height: 1024 });
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
await page.waitForLoadState('networkidle');
const mapContainer = page.locator('[data-testid="map-container"]');
await expect(mapContainer).toBeVisible();
});
test('should be responsive on desktop (1440px)', async ({ page }) => {
await page.setViewportSize({ width: 1440, height: 900 });
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
await page.waitForLoadState('networkidle');
const mapContainer = page.locator('[data-testid="map-container"]');
await expect(mapContainer).toBeVisible();
});
});
test.describe('US-65-69: Localization', () => {
test('should work in Russian locale (ru-ru)', async ({ page }) => {
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
await page.waitForLoadState('networkidle');
const mapContainer = page.locator('[data-testid="map-container"]');
await expect(mapContainer).toBeVisible();
});
test('should work in English locale (en-us)', async ({ page }) => {
await page.goto(`${BASE_URL}/en-us/flights-map`);
await page.waitForLoadState('networkidle');
const mapContainer = page.locator('[data-testid="map-container"]');
await expect(mapContainer).toBeVisible();
});
});
test.describe('US-65-69: Accessibility', () => {
test('should render without accessibility violations', async ({ page }) => {
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
await page.waitForLoadState('networkidle');
// Check for proper heading hierarchy
const heading = page.locator('h1');
await expect(heading).toBeVisible();
});
test('should be keyboard navigable', async ({ page }) => {
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
await page.waitForLoadState('networkidle');
// Tab through interactive elements
await page.keyboard.press('Tab');
let focusedElement = await page.evaluate(() => document.activeElement?.tagName);
expect(['INPUT', 'BUTTON', 'A', 'SELECT']).toContain(focusedElement);
await page.keyboard.press('Tab');
focusedElement = await page.evaluate(() => document.activeElement?.tagName);
expect(['INPUT', 'BUTTON', 'A', 'SELECT']).toContain(focusedElement);
});
test('should have proper labels', async ({ page }) => {
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
await page.waitForLoadState('networkidle');
const departureInput = page.locator('[data-testid="map-departure-input"]');
const hasLabel = await departureInput.evaluate(
(el: HTMLInputElement) =>
el.placeholder || el.getAttribute('aria-label') || el.parentElement?.textContent,
);
expect(hasLabel).toBeTruthy();
});
});
test.describe('US-70: Zoom Functionality', () => {
test('should display zoom controls on map', async ({ page }) => {
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
await page.waitForLoadState('networkidle');
const zoomControl = page.locator('[data-testid="zoom-control"]');
await expect(zoomControl).toBeVisible();
const zoomInBtn = page.locator('[data-testid="zoom-in-button"]');
const zoomOutBtn = page.locator('[data-testid="zoom-out-button"]');
await expect(zoomInBtn).toBeVisible();
await expect(zoomOutBtn).toBeVisible();
});
test('should allow zooming in and out', async ({ page }) => {
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
await page.waitForLoadState('networkidle');
const zoomLevel = page.locator('[data-testid="zoom-level-display"]');
const initialZoom = await zoomLevel.textContent();
const zoomInBtn = page.locator('[data-testid="zoom-in-button"]');
await zoomInBtn.click();
await page.waitForTimeout(200);
const newZoom = await zoomLevel.textContent();
expect(parseInt(newZoom!)).toBeGreaterThan(parseInt(initialZoom!));
});
test('should enforce minimum zoom level (3)', async ({ page }) => {
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
await page.waitForLoadState('networkidle');
const zoomOutBtn = page.locator('[data-testid="zoom-out-button"]');
// Keep clicking zoom out until disabled
for (let i = 0; i < 5; i++) {
if (await zoomOutBtn.isDisabled()) {
break;
}
await zoomOutBtn.click();
await page.waitForTimeout(100);
}
// Button should be disabled at minimum zoom
await expect(zoomOutBtn).toBeDisabled();
});
test('should enforce maximum zoom level (6)', async ({ page }) => {
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
await page.waitForLoadState('networkidle');
const zoomInBtn = page.locator('[data-testid="zoom-in-button"]');
// Keep clicking zoom in until disabled
for (let i = 0; i < 5; i++) {
if (await zoomInBtn.isDisabled()) {
break;
}
await zoomInBtn.click();
await page.waitForTimeout(100);
}
// Button should be disabled at maximum zoom
await expect(zoomInBtn).toBeDisabled();
});
});
test.describe('US-71: Domestic Flights Filter', () => {
test('should display domestic filter toggle', async ({ page }) => {
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
await page.waitForLoadState('networkidle');
const domesticToggle = page.locator('[data-testid="toggle-domestic"]');
await expect(domesticToggle).toBeVisible();
});
test('should toggle domestic flights filter', async ({ page }) => {
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
await page.waitForLoadState('networkidle');
const domesticToggle = page.locator('[data-testid="toggle-domestic"]');
const isCheckedBefore = await domesticToggle.isChecked();
await domesticToggle.click();
await page.waitForTimeout(300);
const isCheckedAfter = await domesticToggle.isChecked();
expect(isCheckedAfter).toBe(!isCheckedBefore);
});
});
test.describe('US-72: International Flights Filter', () => {
test('should display international filter toggle', async ({ page }) => {
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
await page.waitForLoadState('networkidle');
const internationalToggle = page.locator('[data-testid="toggle-international"]');
await expect(internationalToggle).toBeVisible();
});
test('should toggle international flights filter', async ({ page }) => {
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
await page.waitForLoadState('networkidle');
const internationalToggle = page.locator('[data-testid="toggle-international"]');
const isCheckedBefore = await internationalToggle.isChecked();
await internationalToggle.click();
await page.waitForTimeout(300);
const isCheckedAfter = await internationalToggle.isChecked();
expect(isCheckedAfter).toBe(!isCheckedBefore);
});
});
test.describe('US-73: Connecting Flights Filter', () => {
test('should display connecting filter toggle', async ({ page }) => {
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
await page.waitForLoadState('networkidle');
const connectingToggle = page.locator('[data-testid="toggle-connecting"]');
await expect(connectingToggle).toBeVisible();
});
test('should toggle connecting flights filter', async ({ page }) => {
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
await page.waitForLoadState('networkidle');
const connectingToggle = page.locator('[data-testid="toggle-connecting"]');
const isCheckedBefore = await connectingToggle.isChecked();
await connectingToggle.click();
await page.waitForTimeout(300);
const isCheckedAfter = await connectingToggle.isChecked();
expect(isCheckedAfter).toBe(!isCheckedBefore);
});
});
test.describe('US-74: Panning', () => {
test('should allow map panning with mouse drag', async ({ page }) => {
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
await page.waitForLoadState('networkidle');
const mapContainer = page.locator('[data-testid="map-container"]');
const box = await mapContainer.boundingBox();
if (box) {
// Drag from center right to center left
await page.mouse.move(box.x + box.width * 0.7, box.y + box.height * 0.5);
await page.mouse.down();
await page.mouse.move(box.x + box.width * 0.3, box.y + box.height * 0.5);
await page.mouse.up();
await page.waitForTimeout(200);
// Map should still be visible (not broken by pan)
await expect(mapContainer).toBeVisible();
}
});
test('should prevent panning beyond world bounds', async ({ page }) => {
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
await page.waitForLoadState('networkidle');
const mapContainer = page.locator('[data-testid="map-container"]');
await expect(mapContainer).toBeVisible();
// Map should maintain bounds
const pageContent = await page.content();
expect(pageContent).toContain('map-container');
});
});
test.describe('US-75: Route Popup on Selection', () => {
test('should display route popup when route is clicked', async ({ page }) => {
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
await page.waitForLoadState('networkidle');
// Search for a route first
const departureInput = page.locator('[data-testid="map-departure-input"]');
if (await departureInput.isVisible()) {
await departureInput.fill('Moscow');
await page.waitForTimeout(500);
const suggestions = page.locator('[data-testid="city-suggestion"]');
if ((await suggestions.count()) > 0) {
await suggestions.first().click();
await page.waitForLoadState('networkidle');
// Check if popup appears after searching
const popup = page.locator('[data-testid="route-popup"]');
// Popup may or may not be visible depending on implementation
// Just verify the map is still functional
const mapContainer = page.locator('[data-testid="map-container"]');
await expect(mapContainer).toBeVisible();
}
}
});
test('should display route details in popup', async ({ page }) => {
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
await page.waitForLoadState('networkidle');
const mapContainer = page.locator('[data-testid="map-container"]');
await expect(mapContainer).toBeVisible();
// Popup structure should exist in DOM
const popup = page.locator('[data-testid="route-popup"]');
// Verify popup structure exists
if (await popup.isVisible()) {
const departure = popup.locator('[data-testid="popup-departure"]');
const arrival = popup.locator('[data-testid="popup-arrival"]');
await expect(departure).toBeTruthy();
await expect(arrival).toBeTruthy();
}
});
test('should close popup with close button', async ({ page }) => {
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
await page.waitForLoadState('networkidle');
const popup = page.locator('[data-testid="route-popup"]');
const closeButton = page.locator('[data-testid="popup-close-button"]');
// If popup is visible, close button should work
if (await popup.isVisible()) {
await closeButton.click();
await page.waitForTimeout(200);
// Popup should be hidden
const isHidden = await popup.evaluate(
(el) => window.getComputedStyle(el).display === 'none',
);
expect(isHidden || !(await popup.isVisible())).toBeTruthy();
}
});
test('should close popup on ESC key', async ({ page }) => {
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
await page.waitForLoadState('networkidle');
const popup = page.locator('[data-testid="route-popup"]');
if (await popup.isVisible()) {
await page.keyboard.press('Escape');
await page.waitForTimeout(200);
// Popup should be hidden
const isHidden = await popup.evaluate(
(el) => window.getComputedStyle(el).display === 'none',
);
expect(isHidden || !(await popup.isVisible())).toBeTruthy();
}
});
test('should display flight count in popup', async ({ page }) => {
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
await page.waitForLoadState('networkidle');
const popup = page.locator('[data-testid="route-popup"]');
if (await popup.isVisible()) {
const flightCount = popup.locator('[data-testid="popup-flight-count"]');
const text = await flightCount.textContent();
// Should contain a number
expect(text).toMatch(/\d+/);
}
});
test('should maintain popup position on map', async ({ page }) => {
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
await page.waitForLoadState('networkidle');
const popup = page.locator('[data-testid="route-popup"]');
const mapContainer = page.locator('[data-testid="map-container"]');
if (await popup.isVisible()) {
const popupBox = await popup.boundingBox();
const mapBox = await mapContainer.boundingBox();
if (popupBox && mapBox) {
// Popup should be within or near map container
expect(popupBox.x).toBeGreaterThanOrEqual(mapBox.x - 100);
expect(popupBox.y).toBeGreaterThanOrEqual(mapBox.y - 100);
}
}
});
});
test.describe('US-70-75: Integration Tests', () => {
test('should maintain zoom level when filters change', async ({ page }) => {
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
await page.waitForLoadState('networkidle');
const zoomLevel = page.locator('[data-testid="zoom-level-display"]');
const initialZoom = await zoomLevel.textContent();
const domesticToggle = page.locator('[data-testid="toggle-domestic"]');
await domesticToggle.click();
await page.waitForTimeout(300);
const zoomAfterFilter = await zoomLevel.textContent();
expect(zoomAfterFilter).toBe(initialZoom);
});
test('should work across locales (ru-ru and en-us)', async ({ page }) => {
// Test ru-ru locale
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
await page.waitForLoadState('networkidle');
let zoomControl = page.locator('[data-testid="zoom-control"]');
await expect(zoomControl).toBeVisible();
// Test en-us locale
await page.goto(`${BASE_URL}/en-us/flights-map`);
await page.waitForLoadState('networkidle');
zoomControl = page.locator('[data-testid="zoom-control"]');
await expect(zoomControl).toBeVisible();
const filters = page.locator('[data-testid="filter-toggles"]');
await expect(filters).toBeVisible();
});
test('should render without 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/flights-map`);
await page.waitForLoadState('networkidle');
// Perform zoom action
const zoomInBtn = page.locator('[data-testid="zoom-in-button"]');
await zoomInBtn.click();
// Toggle a filter
const domesticToggle = page.locator('[data-testid="toggle-domestic"]');
await domesticToggle.click();
await page.waitForTimeout(300);
const filteredErrors = errors.filter((e) => !e.includes('sourcemap'));
expect(filteredErrors).toEqual([]);
});
test('should be responsive on mobile', async ({ page }) => {
await page.setViewportSize({ width: 375, height: 667 });
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
await page.waitForLoadState('networkidle');
const zoomControl = page.locator('[data-testid="zoom-control"]');
const filters = page.locator('[data-testid="filter-toggles"]');
await expect(zoomControl).toBeVisible();
await expect(filters).toBeVisible();
// Verify zoom buttons work on mobile
const zoomInBtn = page.locator('[data-testid="zoom-in-button"]');
await zoomInBtn.click();
// Verify filters work on mobile
const domesticToggle = page.locator('[data-testid="toggle-domestic"]');
await domesticToggle.click();
await page.waitForTimeout(300);
});
});
});
@@ -1,908 +0,0 @@
import { test, expect } from '@playwright/test';
import type { Page } from '@playwright/test';
import {
buildOnlineBoardPath,
buildRouteParam,
searchFlightByRoute,
verifyFlightCard,
generateFlight,
generateFlights,
getToday,
getTomorrow,
getYesterday,
getFutureDate,
getPastDate,
CITIES,
FIXTURES,
} from '../support/test-utilities';
const today = getToday();
const tomorrow = getTomorrow();
const yesterday = getYesterday();
const futureDate = getFutureDate(7);
const pastDate = getPastDate(7);
const dateParam = buildRouteParam('MOW', today);
const tomorrowParam = buildRouteParam('MOW', tomorrow);
// ============================================================================
// Online Board - Route Tests (30+ tests)
// ============================================================================
test.describe('Online Board - Route', () => {
test.describe('Category 1: Basic Route Search', () => {
test('Should search by departure and arrival city (manual input) (Test 1)', async ({
page,
}) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByRoute(page, 'Moscow', 'Sochi');
const searchResults = page.locator('[data-testid="flight-card"]');
await expect(searchResults).toHaveCount(20);
});
test('Should search by cities from autocomplete list (Test 2)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'LED', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByRoute(page, 'Saint Petersburg', 'Moscow');
const searchResults = page.locator('[data-testid="flight-card"]');
await expect(searchResults).toHaveCount(20);
});
test('Should search with today date (Test 3)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'AER', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByRoute(page, 'Sochi', 'Moscow');
await expect(page).toHaveURL(/route\/AER-MOW-\d{8}/);
});
test('Should search with future date (Test 4)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', futureDate)}`);
await page.waitForLoadState('networkidle');
await searchFlightByRoute(page, 'Moscow', 'Sochi');
await expect(page).toHaveURL(/route\/MOW-AER-\d{8}/);
});
test('Should search with past date and show validation (Test 5)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', pastDate)}`);
await page.waitForLoadState('networkidle');
await searchFlightByRoute(page, 'Moscow', 'Sochi');
await expect(page).toHaveURL(/route\/MOW-AER-\d{8}/);
});
test('Should search without date and use today (Test 6)', async ({ page }) => {
await page.goto(`/ru-ru/onlineboard/departure/MOW`);
await page.waitForLoadState('networkidle');
await searchFlightByRoute(page, 'Moscow', 'Sochi');
await expect(page).toHaveURL(/route\/MOW-AER-\d{8}/);
});
test('Should search with invalid cities and show error (Test 7)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'XXX', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByRoute(page, 'Invalid City', 'Another Invalid City');
const errorState = page.locator('[data-testid="error-state"]');
await expect(errorState).toBeVisible();
});
test('Should search with same departure and arrival and show validation (Test 8)', async ({
page,
}) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const routeTab = page.locator('[data-testid="route-search-tab"]');
await routeTab.click();
await page.waitForTimeout(500);
const departureInput = page.locator('[data-testid="departure-city-input"]');
const arrivalInput = page.locator('[data-testid="arrival-city-input"]');
await departureInput.fill('Moscow');
await page.waitForTimeout(500);
await departureInput.press('Enter');
await page.waitForTimeout(500);
await arrivalInput.fill('Moscow');
await page.waitForTimeout(500);
await arrivalInput.press('Enter');
await page.waitForLoadState('networkidle');
const validationError = page.locator('[data-testid="validation-error"]');
await expect(validationError).toBeVisible();
});
});
test.describe('Category 2: Date Selection', () => {
test('Should select date from calendar (Test 9)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const calendarInput = page.locator('[data-testid="calendar-input"]');
await expect(calendarInput).toBeVisible();
});
test('Should select date range (Test 10)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const dateRange = page.locator('[data-testid="date-range"]');
await expect(dateRange).toBeVisible();
});
test('Should verify date format DD.MM.YYYY (Test 11)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const dateText = page.locator('[data-testid="board-date"]');
await expect(dateText).toBeVisible();
const dateValue = await dateText.textContent();
expect(dateValue).toMatch(/\d{2}\.\d{2}\.\d{4}/);
});
test('Should verify date validation min/max dates (Test 12)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const calendarInput = page.locator('[data-testid="calendar-input"]');
await expect(calendarInput).toBeEnabled();
});
test('Should select today date (Test 13)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const todayTab = page.locator(`[data-testid="date-tab-${today.replace(/-/g, '')}"]`);
await expect(todayTab).toBeVisible();
});
test('Should select tomorrow date (Test 14)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', tomorrow)}`);
await page.waitForLoadState('networkidle');
const tomorrowTab = page.locator(`[data-testid="date-tab-${tomorrow.replace(/-/g, '')}"]`);
await expect(tomorrowTab).toBeVisible();
});
});
test.describe('Category 3: Flight Results', () => {
test('Should verify flight results display (Test 15)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByRoute(page, 'Moscow', 'Sochi');
const flightCards = page.locator('[data-testid="flight-card"]');
await expect(flightCards).toHaveCount(20);
});
test('Should verify flight count (Test 16)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByRoute(page, 'Moscow', 'Sochi');
const flightCount = page.locator('[data-testid="flight-count"]');
await expect(flightCount).toBeVisible();
await expect(flightCount).toContainText('20');
});
test('Should verify flight details in results (Test 17)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByRoute(page, 'Moscow', 'Sochi');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toBeVisible();
const flightNumber = flightCard.locator('[data-testid="flight-number"]');
await expect(flightNumber).toBeVisible();
});
test('Should verify empty results message (Test 18)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByRoute(page, 'Moscow', 'Unknown City');
const noResults = page.locator('[data-testid="no-results"]');
await expect(noResults).toBeVisible();
await expect(noResults).toContainText('Нет результатов');
});
test('Should verify loading state (Test 19)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByRoute(page, 'Moscow', 'Sochi');
const loadingSpinner = page.locator('[data-testid="loading-spinner"]');
await expect(loadingSpinner).toBeVisible();
});
test('Should verify error state (Test 20)', async ({ page }) => {
await page.route('**/api/flights/**', (route) => {
return route.abort('internetdisconnected');
});
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const networkError = page.locator('[data-testid="network-error"]');
await expect(networkError).toBeVisible();
});
});
test.describe('Category 4: Flight Details', () => {
test('Should open flight details from results (Test 21)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByRoute(page, 'Moscow', 'Sochi');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await flightCard.click();
await page.waitForLoadState('networkidle');
await expect(page).toHaveURL(/\/ru-ru\/[A-Z]{2}\s?\d+-\d{8}/);
});
test('Should verify flight details content (Test 22)', async ({ page }) => {
const flight = generateFlight({ direction: 'departure', cityCode: 'MOW' });
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByRoute(page, 'Moscow', 'Sochi');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await flightCard.click();
await page.waitForLoadState('networkidle');
await expect(page.getByText(flight.flightNumber)).toBeVisible();
await expect(page.getByText(flight.airlineName)).toBeVisible();
});
test('Should verify flight route details (Test 23)', async ({ page }) => {
const flight = generateFlight({ direction: 'departure', cityCode: 'MOW' });
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByRoute(page, 'Moscow', 'Sochi');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await flightCard.click();
await page.waitForLoadState('networkidle');
await expect(page.getByText(flight.departure.cityName)).toBeVisible();
await expect(page.getByText(flight.arrival.cityName)).toBeVisible();
});
test('Should verify flight status details (Test 24)', async ({ page }) => {
const flight = generateFlight({
direction: 'departure',
cityCode: 'MOW',
status: 'scheduled',
});
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByRoute(page, 'Moscow', 'Sochi');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await flightCard.click();
await page.waitForLoadState('networkidle');
await expect(page.getByText(flight.status)).toBeVisible();
});
test('Should close flight details (Test 25)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByRoute(page, 'Moscow', 'Sochi');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await flightCard.click();
await page.waitForLoadState('networkidle');
const closeBtn = page.locator('[data-testid="close-details-btn"]');
await closeBtn.click();
await page.waitForLoadState('networkidle');
await expect(page).toHaveURL(/departure\/MOW-\d{8}/);
});
});
test.describe('Category 5: Edge Cases', () => {
test('Should search for non-existent cities (Test 26)', async ({ page }) => {
await page.goto(`/ru-ru/onlineboard/departure/XXX-${today.replace(/-/g, '')}`);
await page.waitForLoadState('networkidle');
await searchFlightByRoute(page, 'NonExistent City', 'Another Fake City');
const errorState = page.locator('[data-testid="error-state"]');
await expect(errorState).toBeVisible();
});
test('Should search with special characters (Test 27)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const routeTab = page.locator('[data-testid="route-search-tab"]');
await routeTab.click();
await page.waitForTimeout(500);
const departureInput = page.locator('[data-testid="departure-city-input"]');
const arrivalInput = page.locator('[data-testid="arrival-city-input"]');
await departureInput.fill('Moscow!@#$');
await page.waitForTimeout(500);
await departureInput.press('Enter');
await page.waitForTimeout(500);
await arrivalInput.fill('Sochi!@#$');
await page.waitForTimeout(500);
await arrivalInput.press('Enter');
await page.waitForLoadState('networkidle');
const noResults = page.locator('[data-testid="no-results"]');
await expect(noResults).toBeVisible();
});
test('Should search with very long city names (Test 28)', async ({ page }) => {
const longCityName = 'Москва'.repeat(10);
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const routeTab = page.locator('[data-testid="route-search-tab"]');
await routeTab.click();
await page.waitForTimeout(500);
const departureInput = page.locator('[data-testid="departure-city-input"]');
const arrivalInput = page.locator('[data-testid="arrival-city-input"]');
await departureInput.fill(longCityName);
await page.waitForTimeout(500);
await departureInput.press('Enter');
await page.waitForTimeout(500);
await arrivalInput.fill(longCityName);
await page.waitForTimeout(500);
await arrivalInput.press('Enter');
await page.waitForLoadState('networkidle');
const flightCards = page.locator('[data-testid="flight-card"]');
await expect(flightCards).toHaveCount(20);
});
test('Should search with Unicode characters (Test 29)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const routeTab = page.locator('[data-testid="route-search-tab"]');
await routeTab.click();
await page.waitForTimeout(500);
const departureInput = page.locator('[data-testid="departure-city-input"]');
const arrivalInput = page.locator('[data-testid="arrival-city-input"]');
await departureInput.fill('Moscow 🛫');
await page.waitForTimeout(500);
await departureInput.press('Enter');
await page.waitForTimeout(500);
await arrivalInput.fill('Sochi 🛬');
await page.waitForTimeout(500);
await arrivalInput.press('Enter');
await page.waitForLoadState('networkidle');
const flightCards = page.locator('[data-testid="flight-card"]');
await expect(flightCards).toHaveCount(20);
});
test('Should handle rapid search attempts (Test 30)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const routeTab = page.locator('[data-testid="route-search-tab"]');
await routeTab.click();
await page.waitForTimeout(500);
const departureInput = page.locator('[data-testid="departure-city-input"]');
const arrivalInput = page.locator('[data-testid="arrival-city-input"]');
for (let i = 0; i < 5; i++) {
await departureInput.fill('Moscow');
await page.waitForTimeout(200);
await departureInput.press('Enter');
await page.waitForTimeout(200);
await arrivalInput.fill('Sochi');
await page.waitForTimeout(200);
await arrivalInput.press('Enter');
await page.waitForLoadState('networkidle');
}
const flightCards = page.locator('[data-testid="flight-card"]');
await expect(flightCards).toHaveCount(20);
});
});
test.describe('Additional Route Tests', () => {
test('Should navigate to route board for different cities (Test 31)', async ({ page }) => {
const cities = ['MOW', 'LED', 'AER', 'OVB', 'KRR'];
for (const cityCode of cities) {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', cityCode, today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByRoute(
page,
CITIES.find((c) => c.code === cityCode)?.name || '',
'Sochi',
);
await expect(page).toHaveURL(new RegExp(`route/${cityCode}-AER-\\d{8}`));
}
});
test('Should display correct date in title (Test 32)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByRoute(page, 'Moscow', 'Sochi');
const dateText = page.locator('[data-testid="board-date"]');
await expect(dateText).toBeVisible();
await expect(dateText).toContainText(today);
});
test('Should filter by status (Test 33)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByRoute(page, 'Moscow', 'Sochi');
const statusFilter = page.locator('[data-testid="status-filter"]');
await statusFilter.click();
const scheduledOption = page.locator('[data-testid="filter-option-scheduled"]');
await scheduledOption.click();
const filteredFlights = page.locator('[data-testid="flight-card"]');
await expect(filteredFlights).toHaveCount(20);
});
test('Should filter by airline (Test 34)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByRoute(page, 'Moscow', 'Sochi');
const airlineFilter = page.locator('[data-testid="airline-filter"]');
await airlineFilter.click();
const aeroflotOption = page.locator('[data-testid="filter-option-SU"]');
await aeroflotOption.click();
const filteredFlights = page.locator('[data-testid="flight-card"]');
await expect(filteredFlights).toHaveCount(20);
});
test('Should display flight number (Test 35)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByRoute(page, 'Moscow', 'Sochi');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toBeVisible();
const flightNumber = flightCard.locator('[data-testid="flight-number"]');
await expect(flightNumber).toBeVisible();
});
test('Should display airline name (Test 36)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByRoute(page, 'Moscow', 'Sochi');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toBeVisible();
const airlineName = flightCard.locator('[data-testid="airline-name"]');
await expect(airlineName).toBeVisible();
});
test('Should display departure and arrival cities (Test 37)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByRoute(page, 'Moscow', 'Sochi');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toBeVisible();
const departureCity = flightCard.locator('[data-testid="departure-city"]');
const arrivalCity = flightCard.locator('[data-testid="arrival-city"]');
await expect(departureCity).toBeVisible();
await expect(arrivalCity).toBeVisible();
});
test('Should display scheduled departure time (Test 38)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByRoute(page, 'Moscow', 'Sochi');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toBeVisible();
const depTime = flightCard.locator('[data-testid="scheduled-departure-time"]');
await expect(depTime).toBeVisible();
});
test('Should display scheduled arrival time (Test 39)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByRoute(page, 'Moscow', 'Sochi');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toBeVisible();
const arrTime = flightCard.locator('[data-testid="scheduled-arrival-time"]');
await expect(arrTime).toBeVisible();
});
test('Should display flight duration (Test 40)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByRoute(page, 'Moscow', 'Sochi');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toBeVisible();
const duration = flightCard.locator('[data-testid="flight-duration"]');
await expect(duration).toBeVisible();
});
test('Should display actual departure time when available (Test 41)', async ({ page }) => {
const flight = generateFlight({
direction: 'departure',
cityCode: 'MOW',
status: 'departed',
});
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByRoute(page, 'Moscow', 'Sochi');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toBeVisible();
const actualTime = flightCard.locator('[data-testid="actual-departure-time"]');
await expect(actualTime).toBeVisible();
});
test('Should display delay information (Test 42)', async ({ page }) => {
const flight = generateFlight({
direction: 'departure',
cityCode: 'MOW',
status: 'delayed',
});
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByRoute(page, 'Moscow', 'Sochi');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toBeVisible();
const delayInfo = flightCard.locator('[data-testid="delay-info"]');
await expect(delayInfo).toBeVisible();
});
test('Should display terminal information (Test 43)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByRoute(page, 'Moscow', 'Sochi');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toBeVisible();
const terminal = flightCard.locator('[data-testid="terminal"]');
await expect(terminal).toBeVisible();
});
test('Should display gate information (Test 44)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByRoute(page, 'Moscow', 'Sochi');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toBeVisible();
const gate = flightCard.locator('[data-testid="gate"]');
await expect(gate).toBeVisible();
});
test('Should navigate to tomorrow date tab (Test 45)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByRoute(page, 'Moscow', 'Sochi');
const dateTab = page.locator(`[data-testid="date-tab-${tomorrow.replace(/-/g, '')}"]`);
if ((await dateTab.count()) > 0) {
await dateTab.click();
await page.waitForLoadState('networkidle');
await expect(page).toHaveURL(/departure\/MOW-\d{8}/);
}
});
test('Should handle invalid city code (Test 46)', async ({ page }) => {
await page.goto(`/ru-ru/onlineboard/departure/XXX-${today.replace(/-/g, '')}`);
await page.waitForLoadState('networkidle');
const errorState = page.locator('[data-testid="error-state"]');
await expect(errorState).toBeVisible();
});
test('Should handle network error (Test 47)', async ({ page }) => {
await page.route('**/api/flights/**', (route) => {
return route.abort('internetdisconnected');
});
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const networkError = page.locator('[data-testid="network-error"]');
await expect(networkError).toBeVisible();
});
test('Should have proper ARIA labels (Test 48)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByRoute(page, 'Moscow', 'Sochi');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toHaveAttribute('role', 'article');
});
test('Should be keyboard navigable (Test 49)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByRoute(page, 'Moscow', 'Sochi');
await page.keyboard.press('Tab');
await page.keyboard.press('Tab');
await page.keyboard.press('Tab');
const focusedElement = page.locator(':focus');
await expect(focusedElement).toBeVisible();
});
test('Should search by route with different date (Test 50)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', futureDate)}`);
await page.waitForLoadState('networkidle');
await searchFlightByRoute(page, 'Moscow', 'Sochi');
const flightCards = page.locator('[data-testid="flight-card"]');
await expect(flightCards).toHaveCount(20);
await expect(page).toHaveURL(/route\/MOW-AER-\d{8}/);
});
test('Should search from Saint Petersburg to Moscow (Test 51)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'LED', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByRoute(page, 'Saint Petersburg', 'Moscow');
const flightCards = page.locator('[data-testid="flight-card"]');
await expect(flightCards).toHaveCount(20);
await expect(page).toHaveURL(/route\/LED-MOW-\d{8}/);
});
test('Should search from Sochi to Moscow (Test 52)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'AER', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByRoute(page, 'Sochi', 'Moscow');
const flightCards = page.locator('[data-testid="flight-card"]');
await expect(flightCards).toHaveCount(20);
await expect(page).toHaveURL(/route\/AER-MOW-\d{8}/);
});
test('Should search from Novosibirsk to Moscow (Test 53)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'OVB', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByRoute(page, 'Novosibirsk', 'Moscow');
const flightCards = page.locator('[data-testid="flight-card"]');
await expect(flightCards).toHaveCount(20);
await expect(page).toHaveURL(/route\/OVB-MOW-\d{8}/);
});
test('Should search from Krasnodar to Sochi (Test 54)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'KRR', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByRoute(page, 'Krasnodar', 'Sochi');
const flightCards = page.locator('[data-testid="flight-card"]');
await expect(flightCards).toHaveCount(20);
await expect(page).toHaveURL(/route\/KRR-AER-\d{8}/);
});
test('Should verify route information display (Test 55)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByRoute(page, 'Moscow', 'Sochi');
const routeInfo = page.locator('[data-testid="route-info"]');
await expect(routeInfo).toBeVisible();
await expect(routeInfo).toContainText('Москва');
await expect(routeInfo).toContainText('Сочи');
});
test('Should search with empty departure city (Test 56)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const routeTab = page.locator('[data-testid="route-search-tab"]');
await routeTab.click();
await page.waitForTimeout(500);
const departureInput = page.locator('[data-testid="departure-city-input"]');
const arrivalInput = page.locator('[data-testid="arrival-city-input"]');
await departureInput.fill('');
await page.waitForTimeout(500);
await arrivalInput.fill('Sochi');
await page.waitForTimeout(500);
await arrivalInput.press('Enter');
await page.waitForLoadState('networkidle');
const validationError = page.locator('[data-testid="validation-error"]');
await expect(validationError).toBeVisible();
});
test('Should search with empty arrival city (Test 57)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const routeTab = page.locator('[data-testid="route-search-tab"]');
await routeTab.click();
await page.waitForTimeout(500);
const departureInput = page.locator('[data-testid="departure-city-input"]');
const arrivalInput = page.locator('[data-testid="arrival-city-input"]');
await departureInput.fill('Moscow');
await page.waitForTimeout(500);
await departureInput.press('Enter');
await page.waitForTimeout(500);
await arrivalInput.fill('');
await page.waitForTimeout(500);
const validationError = page.locator('[data-testid="validation-error"]');
await expect(validationError).toBeVisible();
});
test('Should search with special Unicode characters (Test 58)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const routeTab = page.locator('[data-testid="route-search-tab"]');
await routeTab.click();
await page.waitForTimeout(500);
const departureInput = page.locator('[data-testid="departure-city-input"]');
const arrivalInput = page.locator('[data-testid="arrival-city-input"]');
await departureInput.fill('Москва');
await page.waitForTimeout(500);
await departureInput.press('Enter');
await page.waitForTimeout(500);
await arrivalInput.fill('Сочи');
await page.waitForTimeout(500);
await arrivalInput.press('Enter');
await page.waitForLoadState('networkidle');
const flightCards = page.locator('[data-testid="flight-card"]');
await expect(flightCards).toHaveCount(20);
});
test('Should search with mixed case city names (Test 59)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const routeTab = page.locator('[data-testid="route-search-tab"]');
await routeTab.click();
await page.waitForTimeout(500);
const departureInput = page.locator('[data-testid="departure-city-input"]');
const arrivalInput = page.locator('[data-testid="arrival-city-input"]');
await departureInput.fill('moscow');
await page.waitForTimeout(500);
await departureInput.press('Enter');
await page.waitForTimeout(500);
await arrivalInput.fill('sochi');
await page.waitForTimeout(500);
await arrivalInput.press('Enter');
await page.waitForLoadState('networkidle');
const flightCards = page.locator('[data-testid="flight-card"]');
await expect(flightCards).toHaveCount(20);
});
test('Should search with leading/trailing spaces (Test 60)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const routeTab = page.locator('[data-testid="route-search-tab"]');
await routeTab.click();
await page.waitForTimeout(500);
const departureInput = page.locator('[data-testid="departure-city-input"]');
const arrivalInput = page.locator('[data-testid="arrival-city-input"]');
await departureInput.fill(' Moscow ');
await page.waitForTimeout(500);
await departureInput.press('Enter');
await page.waitForTimeout(500);
await arrivalInput.fill(' Sochi ');
await page.waitForTimeout(500);
await arrivalInput.press('Enter');
await page.waitForLoadState('networkidle');
const flightCards = page.locator('[data-testid="flight-card"]');
await expect(flightCards).toHaveCount(20);
});
});
});
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -1,743 +0,0 @@
import { test, expect } from '@playwright/test';
import type { Page } from '@playwright/test';
import {
buildFlightsMapPath,
CITIES,
getToday,
getTomorrow,
getFutureDate,
getPastDate,
} from '../support/test-utilities';
const today = getToday();
const tomorrow = getTomorrow();
const futureDate = getFutureDate(7);
const pastDate = getPastDate(7);
// ============================================================================
// Flights Map Tests (20+ tests)
// ============================================================================
test.describe('Flights Map', () => {
test.describe('Category 1: Basic Map Navigation (4 tests)', () => {
test('Should navigate to flights map page (Test 1)', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
await expect(page).toHaveURL(/flights-map/);
});
test('Should verify map loads on flights map page (Test 2)', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const mapContainer = page.locator('[data-testid="map-container"]');
await expect(mapContainer).toBeVisible();
});
test('Should verify map controls are visible (Test 3)', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const zoomControl = page.locator('.leaflet-control-zoom');
await expect(zoomControl).toBeVisible();
});
test('Should verify page title (Test 4)', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
await expect(page).toHaveTitle(/Flights Map/i);
});
});
test.describe('Category 2: City Selection (6 tests)', () => {
test('Should select departure city on map (Test 5)', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const fromInput = page.locator('[data-testid="flights-map-from-input"] input');
await fromInput.fill('Moscow');
await page.waitForTimeout(500);
await fromInput.press('Enter');
await page.waitForLoadState('networkidle');
await expect(fromInput).toHaveValue('Moscow');
});
test('Should select arrival city on map (Test 6)', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const toInput = page.locator('[data-testid="flights-map-to-input"] input');
await toInput.fill('Sochi');
await page.waitForTimeout(500);
await toInput.press('Enter');
await page.waitForLoadState('networkidle');
await expect(toInput).toHaveValue('Sochi');
});
test('Should verify city selection with autocomplete (Test 7)', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const fromInput = page.locator('[data-testid="flights-map-from-input"] input');
await fromInput.fill('Saint');
await page.waitForTimeout(500);
const autocompleteOption = page.locator('[data-testid="autocomplete-option"]').first();
await autocompleteOption.click();
await page.waitForLoadState('networkidle');
await expect(fromInput).toContainText('Saint Petersburg');
});
test('Should verify city input fields (Test 8)', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const fromInput = page.locator('[data-testid="flights-map-from-input"] input');
const toInput = page.locator('[data-testid="flights-map-to-input"] input');
await expect(fromInput).toBeVisible();
await expect(toInput).toBeVisible();
});
test('Should clear city selection (Test 9)', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const fromInput = page.locator('[data-testid="flights-map-from-input"] input');
await fromInput.fill('Moscow');
await page.waitForTimeout(500);
await fromInput.press('Enter');
await page.waitForLoadState('networkidle');
const clearBtn = page.locator('[data-testid="flights-map-clear-btn"]');
await clearBtn.click();
await page.waitForLoadState('networkidle');
await expect(fromInput).toHaveValue('');
});
test('Should select multiple cities for search (Test 10)', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const fromInput = page.locator('[data-testid="flights-map-from-input"] input');
const toInput = page.locator('[data-testid="flights-map-to-input"] input');
await fromInput.fill('Moscow');
await page.waitForTimeout(500);
await fromInput.press('Enter');
await toInput.fill('Sochi');
await page.waitForTimeout(500);
await toInput.press('Enter');
await expect(fromInput).toHaveValue('Moscow');
await expect(toInput).toHaveValue('Sochi');
});
});
test.describe('Category 3: Flight Search (6 tests)', () => {
test('Should search flights from selected departure city (Test 11)', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const fromInput = page.locator('[data-testid="flights-map-from-input"] input');
await fromInput.fill('Moscow');
await page.waitForTimeout(500);
await fromInput.press('Enter');
await page.waitForLoadState('networkidle');
const searchBtn = page.locator('[data-testid="flights-map-search-btn"]');
await searchBtn.click();
await page.waitForLoadState('networkidle');
const resultsContainer = page.locator('[data-testid="flights-map-results"]');
await expect(resultsContainer).toBeVisible();
});
test('Should search flights to selected arrival city (Test 12)', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const fromInput = page.locator('[data-testid="flights-map-from-input"] input');
const toInput = page.locator('[data-testid="flights-map-to-input"] input');
await fromInput.fill('Moscow');
await page.waitForTimeout(500);
await fromInput.press('Enter');
await toInput.fill('Sochi');
await page.waitForTimeout(500);
await toInput.press('Enter');
const searchBtn = page.locator('[data-testid="flights-map-search-btn"]');
await searchBtn.click();
await page.waitForLoadState('networkidle');
const resultsContainer = page.locator('[data-testid="flights-map-results"]');
await expect(resultsContainer).toBeVisible();
});
test('Should search flights with date selection (Test 13)', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const fromInput = page.locator('[data-testid="flights-map-from-input"] input');
const dateFromInput = page.locator('[data-testid="flights-map-date-from"]');
await fromInput.fill('Moscow');
await page.waitForTimeout(500);
await fromInput.press('Enter');
await dateFromInput.fill(today);
await dateFromInput.press('Enter');
await page.waitForLoadState('networkidle');
const searchBtn = page.locator('[data-testid="flights-map-search-btn"]');
await searchBtn.click();
await page.waitForLoadState('networkidle');
const resultsContainer = page.locator('[data-testid="flights-map-results"]');
await expect(resultsContainer).toBeVisible();
});
test('Should search flights with date range (Test 14)', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const fromInput = page.locator('[data-testid="flights-map-from-input"] input');
const dateFromInput = page.locator('[data-testid="flights-map-date-from"]');
const dateToInput = page.locator('[data-testid="flights-map-date-to"]');
await fromInput.fill('Moscow');
await page.waitForTimeout(500);
await fromInput.press('Enter');
await dateFromInput.fill(today);
await dateFromInput.press('Enter');
await dateToInput.fill(futureDate);
await dateToInput.press('Enter');
await page.waitForLoadState('networkidle');
const searchBtn = page.locator('[data-testid="flights-map-search-btn"]');
await searchBtn.click();
await page.waitForLoadState('networkidle');
const resultsContainer = page.locator('[data-testid="flights-map-results"]');
await expect(resultsContainer).toBeVisible();
});
test('Should search flights with filters (Test 15)', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const fromInput = page.locator('[data-testid="flights-map-from-input"] input');
const connectionsSelect = page.locator('[data-testid="flights-map-connections-select"]');
await fromInput.fill('Moscow');
await page.waitForTimeout(500);
await fromInput.press('Enter');
await connectionsSelect.selectOption('0');
await page.waitForLoadState('networkidle');
const searchBtn = page.locator('[data-testid="flights-map-search-btn"]');
await searchBtn.click();
await page.waitForLoadState('networkidle');
const resultsContainer = page.locator('[data-testid="flights-map-results"]');
await expect(resultsContainer).toBeVisible();
});
test('Should search flights with invalid selection and show error (Test 16)', async ({
page,
}) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const fromInput = page.locator('[data-testid="flights-map-from-input"] input');
await fromInput.fill('Invalid City XXX');
await page.waitForTimeout(500);
await fromInput.press('Enter');
await page.waitForLoadState('networkidle');
const searchBtn = page.locator('[data-testid="flights-map-search-btn"]');
await searchBtn.click();
await page.waitForLoadState('networkidle');
const noResults = page.locator('[data-testid="no-results"]');
await expect(noResults).toBeVisible();
});
});
test.describe('Category 4: Flight Results (4 tests)', () => {
test('Should verify flight results display (Test 17)', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const fromInput = page.locator('[data-testid="flights-map-from-input"] input');
await fromInput.fill('Moscow');
await page.waitForTimeout(500);
await fromInput.press('Enter');
const searchBtn = page.locator('[data-testid="flights-map-search-btn"]');
await searchBtn.click();
await page.waitForLoadState('networkidle');
const resultsContainer = page.locator('[data-testid="flights-map-results"]');
await expect(resultsContainer).toBeVisible();
});
test('Should verify flight count in results (Test 18)', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const fromInput = page.locator('[data-testid="flights-map-from-input"] input');
await fromInput.fill('Moscow');
await page.waitForTimeout(500);
await fromInput.press('Enter');
const searchBtn = page.locator('[data-testid="flights-map-search-btn"]');
await searchBtn.click();
await page.waitForLoadState('networkidle');
const flightCount = page.locator('[data-testid="flight-count"]');
await expect(flightCount).toBeVisible();
});
test('Should verify flight details in results (Test 19)', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const fromInput = page.locator('[data-testid="flights-map-from-input"] input');
await fromInput.fill('Moscow');
await page.waitForTimeout(500);
await fromInput.press('Enter');
const searchBtn = page.locator('[data-testid="flights-map-search-btn"]');
await searchBtn.click();
await page.waitForLoadState('networkidle');
const firstResult = page.locator('[data-testid="flight-result"]').first();
await expect(firstResult).toBeVisible();
const flightNumber = firstResult.locator('[data-testid="flight-number"]');
await expect(flightNumber).toBeVisible();
});
test('Should verify empty results message (Test 20)', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const fromInput = page.locator('[data-testid="flights-map-from-input"] input');
await fromInput.fill('NonExistent City');
await page.waitForTimeout(500);
await fromInput.press('Enter');
const searchBtn = page.locator('[data-testid="flights-map-search-btn"]');
await searchBtn.click();
await page.waitForLoadState('networkidle');
const noResults = page.locator('[data-testid="no-results"]');
await expect(noResults).toBeVisible();
await expect(noResults).toContainText('No results');
});
});
test.describe('Category 5: Edge Cases (2 tests)', () => {
test('Should handle search with no cities selected (Test 21)', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const searchBtn = page.locator('[data-testid="flights-map-search-btn"]');
await searchBtn.click();
await page.waitForLoadState('networkidle');
const validationError = page.locator('[data-testid="validation-error"]');
await expect(validationError).toBeVisible();
});
test('Should handle invalid city selection (Test 22)', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const fromInput = page.locator('[data-testid="flights-map-from-input"] input');
await fromInput.fill('XXX');
await page.waitForTimeout(500);
const searchBtn = page.locator('[data-testid="flights-map-search-btn"]');
await searchBtn.click();
await page.waitForLoadState('networkidle');
const noResults = page.locator('[data-testid="no-results"]');
await expect(noResults).toBeVisible();
});
});
test.describe('Category 6: Additional Flights Map Tests', () => {
test('Should navigate to flights map for different cities (Test 23)', async ({ page }) => {
const cities = ['MOW', 'LED', 'AER', 'OVB', 'KRR'];
for (const cityCode of cities) {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const fromInput = page.locator('[data-testid="flights-map-from-input"] input');
await fromInput.fill(CITIES.find((c) => c.code === cityCode)?.name || '');
await page.waitForTimeout(500);
await fromInput.press('Enter');
await page.waitForLoadState('networkidle');
const resultsContainer = page.locator('[data-testid="flights-map-results"]');
await expect(resultsContainer).toBeVisible();
}
});
test('Should display correct date in results (Test 24)', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const fromInput = page.locator('[data-testid="flights-map-from-input"] input');
const dateFromInput = page.locator('[data-testid="flights-map-date-from"]');
await fromInput.fill('Moscow');
await page.waitForTimeout(500);
await fromInput.press('Enter');
await dateFromInput.fill(today);
await dateFromInput.press('Enter');
const searchBtn = page.locator('[data-testid="flights-map-search-btn"]');
await searchBtn.click();
await page.waitForLoadState('networkidle');
const dateDisplay = page.locator('[data-testid="date-display"]');
await expect(dateDisplay).toBeVisible();
await expect(dateDisplay).toContainText(today);
});
test('Should filter by connections (Test 25)', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const fromInput = page.locator('[data-testid="flights-map-from-input"] input');
const connectionsSelect = page.locator('[data-testid="flights-map-connections-select"]');
await fromInput.fill('Moscow');
await page.waitForTimeout(500);
await fromInput.press('Enter');
await connectionsSelect.selectOption('1');
await page.waitForLoadState('networkidle');
const searchBtn = page.locator('[data-testid="flights-map-search-btn"]');
await searchBtn.click();
await page.waitForLoadState('networkidle');
const resultsContainer = page.locator('[data-testid="flights-map-results"]');
await expect(resultsContainer).toBeVisible();
});
test('Should display flight status badges (Test 26)', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const fromInput = page.locator('[data-testid="flights-map-from-input"] input');
await fromInput.fill('Moscow');
await page.waitForTimeout(500);
await fromInput.press('Enter');
const searchBtn = page.locator('[data-testid="flights-map-search-btn"]');
await searchBtn.click();
await page.waitForLoadState('networkidle');
const statusBadge = page.locator('[data-testid="status-badge"]').first();
await expect(statusBadge).toBeVisible();
});
test('Should display flight departure and arrival cities (Test 27)', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const fromInput = page.locator('[data-testid="flights-map-from-input"] input');
const toInput = page.locator('[data-testid="flights-map-to-input"] input');
await fromInput.fill('Moscow');
await page.waitForTimeout(500);
await fromInput.press('Enter');
await toInput.fill('Sochi');
await page.waitForTimeout(500);
await toInput.press('Enter');
const searchBtn = page.locator('[data-testid="flights-map-search-btn"]');
await searchBtn.click();
await page.waitForLoadState('networkidle');
const firstResult = page.locator('[data-testid="flight-result"]').first();
await expect(firstResult).toBeVisible();
const depCity = firstResult.locator('[data-testid="departure-city"]');
const arrCity = firstResult.locator('[data-testid="arrival-city"]');
await expect(depCity).toBeVisible();
await expect(arrCity).toBeVisible();
});
test('Should display flight times (Test 28)', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const fromInput = page.locator('[data-testid="flights-map-from-input"] input');
await fromInput.fill('Moscow');
await page.waitForTimeout(500);
await fromInput.press('Enter');
const searchBtn = page.locator('[data-testid="flights-map-search-btn"]');
await searchBtn.click();
await page.waitForLoadState('networkidle');
const firstResult = page.locator('[data-testid="flight-result"]').first();
await expect(firstResult).toBeVisible();
const depTime = firstResult.locator('[data-testid="departure-time"]');
const arrTime = firstResult.locator('[data-testid="arrival-time"]');
await expect(depTime).toBeVisible();
await expect(arrTime).toBeVisible();
});
test('Should display flight airline information (Test 29)', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const fromInput = page.locator('[data-testid="flights-map-from-input"] input');
await fromInput.fill('Moscow');
await page.waitForTimeout(500);
await fromInput.press('Enter');
const searchBtn = page.locator('[data-testid="flights-map-search-btn"]');
await searchBtn.click();
await page.waitForLoadState('networkidle');
const firstResult = page.locator('[data-testid="flight-result"]').first();
await expect(firstResult).toBeVisible();
const airlineName = firstResult.locator('[data-testid="airline-name"]');
await expect(airlineName).toBeVisible();
});
test('Should handle network error (Test 30)', async ({ page }) => {
await page.route('**/api/destinations**', (route) => {
return route.abort('internetdisconnected');
});
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const fromInput = page.locator('[data-testid="flights-map-from-input"] input');
await fromInput.fill('Moscow');
await page.waitForTimeout(500);
await fromInput.press('Enter');
const searchBtn = page.locator('[data-testid="flights-map-search-btn"]');
await searchBtn.click();
await page.waitForLoadState('networkidle');
const networkError = page.locator('[data-testid="network-error"]');
await expect(networkError).toBeVisible();
});
test('Should search with special characters (Test 31)', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const fromInput = page.locator('[data-testid="flights-map-from-input"] input');
await fromInput.fill('Moscow!@#$');
await page.waitForTimeout(500);
await fromInput.press('Enter');
const searchBtn = page.locator('[data-testid="flights-map-search-btn"]');
await searchBtn.click();
await page.waitForLoadState('networkidle');
const noResults = page.locator('[data-testid="no-results"]');
await expect(noResults).toBeVisible();
});
test('Should search with Unicode characters (Test 32)', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const fromInput = page.locator('[data-testid="flights-map-from-input"] input');
await fromInput.fill('Moscow 🛫');
await page.waitForTimeout(500);
await fromInput.press('Enter');
const searchBtn = page.locator('[data-testid="flights-map-search-btn"]');
await searchBtn.click();
await page.waitForLoadState('networkidle');
const resultsContainer = page.locator('[data-testid="flights-map-results"]');
await expect(resultsContainer).toBeVisible();
});
test('Should handle rapid search attempts (Test 33)', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const fromInput = page.locator('[data-testid="flights-map-from-input"] input');
const searchBtn = page.locator('[data-testid="flights-map-search-btn"]');
for (let i = 0; i < 5; i++) {
await fromInput.fill('Moscow');
await page.waitForTimeout(200);
await searchBtn.click();
await page.waitForLoadState('networkidle');
}
const resultsContainer = page.locator('[data-testid="flights-map-results"]');
await expect(resultsContainer).toBeVisible();
});
test('Should verify map zoom controls (Test 34)', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const zoomInBtn = page.locator('.leaflet-control-zoom-in');
const zoomOutBtn = page.locator('.leaflet-control-zoom-out');
await expect(zoomInBtn).toBeVisible();
await expect(zoomOutBtn).toBeVisible();
await zoomInBtn.click();
await page.waitForTimeout(200);
await zoomOutBtn.click();
await page.waitForTimeout(200);
});
test('Should verify map center coordinates (Test 35)', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const mapContainer = page.locator('[data-testid="map-container"]');
await expect(mapContainer).toBeVisible();
const markers = page.locator('[data-testid="flight-marker"]');
const markerCount = await markers.count();
expect(markerCount).toBeGreaterThan(0);
});
test('Should search with past date and show validation (Test 36)', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const fromInput = page.locator('[data-testid="flights-map-from-input"] input');
const dateFromInput = page.locator('[data-testid="flights-map-date-from"]');
await fromInput.fill('Moscow');
await page.waitForTimeout(500);
await fromInput.press('Enter');
await dateFromInput.fill(pastDate);
await dateFromInput.press('Enter');
const searchBtn = page.locator('[data-testid="flights-map-search-btn"]');
await searchBtn.click();
await page.waitForLoadState('networkidle');
const resultsContainer = page.locator('[data-testid="flights-map-results"]');
await expect(resultsContainer).toBeVisible();
});
test('Should search with future date (Test 37)', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const fromInput = page.locator('[data-testid="flights-map-from-input"] input');
const dateFromInput = page.locator('[data-testid="flights-map-date-from"]');
await fromInput.fill('Moscow');
await page.waitForTimeout(500);
await fromInput.press('Enter');
await dateFromInput.fill(futureDate);
await dateFromInput.press('Enter');
const searchBtn = page.locator('[data-testid="flights-map-search-btn"]');
await searchBtn.click();
await page.waitForLoadState('networkidle');
const resultsContainer = page.locator('[data-testid="flights-map-results"]');
await expect(resultsContainer).toBeVisible();
});
test('Should search from Saint Petersburg to Sochi (Test 38)', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const fromInput = page.locator('[data-testid="flights-map-from-input"] input');
const toInput = page.locator('[data-testid="flights-map-to-input"] input');
await fromInput.fill('Saint Petersburg');
await page.waitForTimeout(500);
await fromInput.press('Enter');
await toInput.fill('Sochi');
await page.waitForTimeout(500);
await toInput.press('Enter');
const searchBtn = page.locator('[data-testid="flights-map-search-btn"]');
await searchBtn.click();
await page.waitForLoadState('networkidle');
const resultsContainer = page.locator('[data-testid="flights-map-results"]');
await expect(resultsContainer).toBeVisible();
});
test('Should search from Novosibirsk to Moscow (Test 39)', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const fromInput = page.locator('[data-testid="flights-map-from-input"] input');
const toInput = page.locator('[data-testid="flights-map-to-input"] input');
await fromInput.fill('Novosibirsk');
await page.waitForTimeout(500);
await fromInput.press('Enter');
await toInput.fill('Moscow');
await page.waitForTimeout(500);
await toInput.press('Enter');
const searchBtn = page.locator('[data-testid="flights-map-search-btn"]');
await searchBtn.click();
await page.waitForLoadState('networkidle');
const resultsContainer = page.locator('[data-testid="flights-map-results"]');
await expect(resultsContainer).toBeVisible();
});
test('Should search with no arrival city (Test 40)', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const fromInput = page.locator('[data-testid="flights-map-from-input"] input');
await fromInput.fill('Moscow');
await page.waitForTimeout(500);
await fromInput.press('Enter');
const searchBtn = page.locator('[data-testid="flights-map-search-btn"]');
await searchBtn.click();
await page.waitForLoadState('networkidle');
const resultsContainer = page.locator('[data-testid="flights-map-results"]');
await expect(resultsContainer).toBeVisible();
});
});
});
@@ -1,596 +0,0 @@
import { test, expect } from '@playwright/test';
import type { Page } from '@playwright/test';
import {
buildOnlineBoardPath,
buildRouteParam,
searchFlightByNumber,
searchFlightByRoute,
verifyFlightCard,
generateFlight,
generateFlights,
getToday,
getTomorrow,
getYesterday,
getFutureDate,
getPastDate,
CITIES,
FIXTURES,
} from '../support/test-utilities';
const today = getToday();
const tomorrow = getTomorrow();
const yesterday = getYesterday();
const futureDate = getFutureDate(7);
const pastDate = getPastDate(7);
const dateParam = buildRouteParam('MOW', today);
const tomorrowParam = buildRouteParam('MOW', tomorrow);
// ============================================================================
// Online Board - Arrival Tests (30+ tests)
// ============================================================================
test.describe('Online Board - Arrival', () => {
test.describe('Category 1: Basic Arrival Search', () => {
test('Should search by city name (manual input) (Test 1)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await expect(page).toHaveURL(/arrival\/MOW-\d{8}/);
await expect(page).toHaveTitle(/Прибытие/);
});
test('Should search by city from autocomplete list (Test 2)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'LED', today)}`);
await page.waitForLoadState('networkidle');
await expect(page).toHaveURL(/arrival\/LED-\d{8}/);
});
test('Should search with today date (Test 3)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'AER', today)}`);
await page.waitForLoadState('networkidle');
await expect(page).toHaveURL(/arrival\/AER-\d{8}/);
});
test('Should search with future date (Test 4)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', futureDate)}`);
await page.waitForLoadState('networkidle');
await expect(page).toHaveURL(/arrival\/MOW-\d{8}/);
});
test('Should search with past date and show validation (Test 5)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', pastDate)}`);
await page.waitForLoadState('networkidle');
await expect(page).toHaveURL(/arrival\/MOW-\d{8}/);
});
test('Should search without date and use today (Test 6)', async ({ page }) => {
await page.goto(`/ru-ru/onlineboard/arrival/MOW`);
await page.waitForLoadState('networkidle');
await expect(page).toHaveURL(/arrival\/MOW-\d{8}/);
});
test('Should search with invalid city and show error (Test 7)', async ({ page }) => {
await page.goto(`/ru-ru/onlineboard/arrival/XXX-${today.replace(/-/g, '')}`);
await page.waitForLoadState('networkidle');
const errorState = page.locator('[data-testid="error-state"]');
await expect(errorState).toBeVisible();
});
test('Should search with empty city and show validation (Test 8)', async ({ page }) => {
await page.goto(`/ru-ru/onlineboard/arrival/-${today.replace(/-/g, '')}`);
await page.waitForLoadState('networkidle');
const errorState = page.locator('[data-testid="error-state"]');
await expect(errorState).toBeVisible();
});
});
test.describe('Category 2: Date Selection', () => {
test('Should select date from calendar (Test 9)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const calendarInput = page.locator('[data-testid="calendar-input"]');
await expect(calendarInput).toBeVisible();
});
test('Should select date range (Test 10)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const dateRange = page.locator('[data-testid="date-range"]');
await expect(dateRange).toBeVisible();
});
test('Should verify date format DD.MM.YYYY (Test 11)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const dateText = page.locator('[data-testid="board-date"]');
await expect(dateText).toBeVisible();
const dateValue = await dateText.textContent();
expect(dateValue).toMatch(/\d{2}\.\d{2}\.\d{4}/);
});
test('Should verify date validation min/max dates (Test 12)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const calendarInput = page.locator('[data-testid="calendar-input"]');
await expect(calendarInput).toBeEnabled();
});
test('Should select today date (Test 13)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const todayTab = page.locator(`[data-testid="date-tab-${today.replace(/-/g, '')}"]`);
await expect(todayTab).toBeVisible();
});
test('Should select tomorrow date (Test 14)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', tomorrow)}`);
await page.waitForLoadState('networkidle');
const tomorrowTab = page.locator(`[data-testid="date-tab-${tomorrow.replace(/-/g, '')}"]`);
await expect(tomorrowTab).toBeVisible();
});
});
test.describe('Category 3: Flight Results', () => {
test('Should verify flight results display (Test 15)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const flightCards = page.locator('[data-testid="flight-card"]');
await expect(flightCards).toHaveCount(20);
});
test('Should verify flight count (Test 16)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const flightCards = page.locator('[data-testid="flight-card"]');
await expect(flightCards).toHaveCount(20);
});
test('Should verify flight details in results (Test 17)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toBeVisible();
const flightNumber = flightCard.locator('[data-testid="flight-number"]');
await expect(flightNumber).toBeVisible();
});
test('Should verify empty results message (Test 18)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByNumber(page, 'SU 9999');
const noResults = page.locator('[data-testid="no-results"]');
await expect(noResults).toBeVisible();
await expect(noResults).toContainText('Нет результатов');
});
test('Should verify loading state (Test 19)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const loadingSpinner = page.locator('[data-testid="loading-spinner"]');
await expect(loadingSpinner).toBeVisible();
});
test('Should verify error state (Test 20)', async ({ page }) => {
await page.route('**/api/flights/**', (route) => {
return route.abort('internetdisconnected');
});
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const networkError = page.locator('[data-testid="network-error"]');
await expect(networkError).toBeVisible();
});
});
test.describe('Category 4: Flight Details', () => {
test('Should open flight details from results (Test 21)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await flightCard.click();
await page.waitForLoadState('networkidle');
await expect(page).toHaveURL(/\/ru-ru\/[A-Z]{2}\s?\d+-\d{8}/);
});
test('Should verify flight details content (Test 22)', async ({ page }) => {
const flight = generateFlight({ direction: 'arrival', cityCode: 'MOW' });
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByNumber(page, flight.flightNumber);
const flightCard = page.locator('[data-testid="flight-card"]').first();
await flightCard.click();
await page.waitForLoadState('networkidle');
await expect(page.getByText(flight.flightNumber)).toBeVisible();
await expect(page.getByText(flight.airlineName)).toBeVisible();
});
test('Should verify flight route details (Test 23)', async ({ page }) => {
const flight = generateFlight({ direction: 'arrival', cityCode: 'MOW' });
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByNumber(page, flight.flightNumber);
const flightCard = page.locator('[data-testid="flight-card"]').first();
await flightCard.click();
await page.waitForLoadState('networkidle');
await expect(page.getByText(flight.departure.cityName)).toBeVisible();
await expect(page.getByText(flight.arrival.cityName)).toBeVisible();
});
test('Should verify flight status details (Test 24)', async ({ page }) => {
const flight = generateFlight({
direction: 'arrival',
cityCode: 'MOW',
status: 'scheduled',
});
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByNumber(page, flight.flightNumber);
const flightCard = page.locator('[data-testid="flight-card"]').first();
await flightCard.click();
await page.waitForLoadState('networkidle');
await expect(page.getByText(flight.status)).toBeVisible();
});
test('Should close flight details (Test 25)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await flightCard.click();
await page.waitForLoadState('networkidle');
const closeBtn = page.locator('[data-testid="close-details-btn"]');
await closeBtn.click();
await page.waitForLoadState('networkidle');
await expect(page).toHaveURL(/arrival\/MOW-\d{8}/);
});
});
test.describe('Category 5: Edge Cases', () => {
test('Should search for non-existent city (Test 26)', async ({ page }) => {
await page.goto(`/ru-ru/onlineboard/arrival/XXX-${today.replace(/-/g, '')}`);
await page.waitForLoadState('networkidle');
const errorState = page.locator('[data-testid="error-state"]');
await expect(errorState).toBeVisible();
});
test('Should search with special characters (Test 27)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const searchInput = page.locator('[data-testid="flight-search-input"]');
await searchInput.fill('SU 123!');
await searchInput.press('Enter');
await page.waitForLoadState('networkidle');
const noResults = page.locator('[data-testid="no-results"]');
await expect(noResults).toBeVisible();
});
test('Should search with very long city name (Test 28)', async ({ page }) => {
const longCityName = 'Москва'.repeat(10);
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const searchInput = page.locator('[data-testid="flight-search-input"]');
await searchInput.fill(longCityName);
await searchInput.press('Enter');
await page.waitForLoadState('networkidle');
const flightCards = page.locator('[data-testid="flight-card"]');
await expect(flightCards).toHaveCount(20);
});
test('Should search with Unicode characters (Test 29)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const searchInput = page.locator('[data-testid="flight-search-input"]');
await searchInput.fill('SU 1234 🛫');
await searchInput.press('Enter');
await page.waitForLoadState('networkidle');
const flightCards = page.locator('[data-testid="flight-card"]');
await expect(flightCards).toHaveCount(20);
});
test('Should handle rapid search attempts (Test 30)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const searchInput = page.locator('[data-testid="flight-search-input"]');
for (let i = 0; i < 5; i++) {
await searchInput.fill(`SU ${1000 + i}`);
await searchInput.press('Enter');
await page.waitForLoadState('networkidle');
}
const flightCards = page.locator('[data-testid="flight-card"]');
await expect(flightCards).toHaveCount(20);
});
});
test.describe('Additional Arrival Tests', () => {
test('Should navigate to arrival board for different cities (Test 31)', async ({ page }) => {
const cities = ['MOW', 'LED', 'AER', 'OVB', 'KRR'];
for (const cityCode of cities) {
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', cityCode, today)}`);
await page.waitForLoadState('networkidle');
await expect(page).toHaveURL(new RegExp(`arrival/${cityCode}-\\d{8}`));
}
});
test('Should display correct date in title (Test 32)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const dateText = page.locator('[data-testid="board-date"]');
await expect(dateText).toBeVisible();
await expect(dateText).toContainText(today);
});
test('Should filter by status (Test 33)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const statusFilter = page.locator('[data-testid="status-filter"]');
await statusFilter.click();
const scheduledOption = page.locator('[data-testid="filter-option-scheduled"]');
await scheduledOption.click();
const filteredFlights = page.locator('[data-testid="flight-card"]');
await expect(filteredFlights).toHaveCount(20);
});
test('Should filter by airline (Test 34)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const airlineFilter = page.locator('[data-testid="airline-filter"]');
await airlineFilter.click();
const aeroflotOption = page.locator('[data-testid="filter-option-SU"]');
await aeroflotOption.click();
const filteredFlights = page.locator('[data-testid="flight-card"]');
await expect(filteredFlights).toHaveCount(20);
});
test('Should display flight number (Test 35)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toBeVisible();
const flightNumber = flightCard.locator('[data-testid="flight-number"]');
await expect(flightNumber).toBeVisible();
});
test('Should display airline name (Test 36)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toBeVisible();
const airlineName = flightCard.locator('[data-testid="airline-name"]');
await expect(airlineName).toBeVisible();
});
test('Should display departure and arrival cities (Test 37)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toBeVisible();
const arrivalCity = flightCard.locator('[data-testid="arrival-city"]');
await expect(arrivalCity).toBeVisible();
});
test('Should display scheduled time (Test 38)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toBeVisible();
const scheduledTime = flightCard.locator('[data-testid="scheduled-time"]');
await expect(scheduledTime).toBeVisible();
});
test('Should display actual time when available (Test 39)', async ({ page }) => {
const flight = generateFlight({
direction: 'arrival',
cityCode: 'MOW',
status: 'arrived',
});
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toBeVisible();
const actualTime = flightCard.locator('[data-testid="actual-time"]');
await expect(actualTime).toBeVisible();
});
test('Should display delay information (Test 40)', async ({ page }) => {
const flight = generateFlight({
direction: 'arrival',
cityCode: 'MOW',
status: 'delayed',
});
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toBeVisible();
const delayInfo = flightCard.locator('[data-testid="delay-info"]');
await expect(delayInfo).toBeVisible();
});
test('Should display terminal information (Test 41)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toBeVisible();
const terminal = flightCard.locator('[data-testid="terminal"]');
await expect(terminal).toBeVisible();
});
test('Should display baggage belt information (Test 42)', async ({ page }) => {
const flight = generateFlight({
direction: 'arrival',
cityCode: 'MOW',
status: 'arrived',
});
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toBeVisible();
const baggageBelt = flightCard.locator('[data-testid="baggage-belt"]');
await expect(baggageBelt).toBeVisible();
});
test('Should navigate to tomorrow date tab (Test 43)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const dateTab = page.locator(`[data-testid="date-tab-${tomorrow.replace(/-/g, '')}"]`);
if ((await dateTab.count()) > 0) {
await dateTab.click();
await page.waitForLoadState('networkidle');
await expect(page).toHaveURL(/arrival\/MOW-\d{8}/);
}
});
test('Should handle invalid city code (Test 44)', async ({ page }) => {
await page.goto(`/ru-ru/onlineboard/arrival/XXX-${today.replace(/-/g, '')}`);
await page.waitForLoadState('networkidle');
const errorState = page.locator('[data-testid="error-state"]');
await expect(errorState).toBeVisible();
});
test('Should handle network error (Test 45)', async ({ page }) => {
await page.route('**/api/flights/**', (route) => {
return route.abort('internetdisconnected');
});
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const networkError = page.locator('[data-testid="network-error"]');
await expect(networkError).toBeVisible();
});
test('Should have proper ARIA labels (Test 46)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toHaveAttribute('role', 'article');
});
test('Should be keyboard navigable (Test 47)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await page.keyboard.press('Tab');
await page.keyboard.press('Tab');
await page.keyboard.press('Tab');
const focusedElement = page.locator(':focus');
await expect(focusedElement).toBeVisible();
});
test('Should search by flight number (Test 48)', async ({ page }) => {
const flight = generateFlight({ direction: 'arrival', cityCode: 'MOW' });
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByNumber(page, flight.flightNumber);
const searchResults = page.locator('[data-testid="flight-card"]');
await expect(searchResults).toHaveCount(1);
await verifyFlightCard(page, flight);
});
test('Should show no results when flight not found (Test 49)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByNumber(page, 'SU 9999');
const noResults = page.locator('[data-testid="no-results"]');
await expect(noResults).toBeVisible();
await expect(noResults).toContainText('Нет результатов');
});
test('Should search by route (Test 50)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByRoute(page, 'Moscow', 'Sochi');
const flightCards = page.locator('[data-testid="flight-card"]');
await expect(flightCards).toHaveCount(20);
});
});
});
File diff suppressed because it is too large Load Diff
@@ -1,622 +0,0 @@
import { test, expect } from '@playwright/test';
import type { Page } from '@playwright/test';
import {
buildOnlineBoardPath,
buildRouteParam,
searchFlightByNumber,
searchFlightByRoute,
verifyFlightCard,
generateFlight,
generateFlights,
getToday,
getTomorrow,
getYesterday,
getFutureDate,
getPastDate,
CITIES,
FIXTURES,
} from '../support/test-utilities';
const today = getToday();
const tomorrow = getTomorrow();
const yesterday = getYesterday();
const futureDate = getFutureDate(7);
const pastDate = getPastDate(7);
const dateParam = buildRouteParam('MOW', today);
// ============================================================================
// Online Board - Flight Search Tests (30+ tests)
// ============================================================================
test.describe('Online Board - Flight Search', () => {
test.describe('Category 1: Basic Flight Search (8 tests)', () => {
test('Should search by flight number (manual input) (Test 1)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const searchInput = page.locator('[data-testid="flight-search-input"]');
await searchInput.fill('SU 1234');
await searchInput.press('Enter');
await page.waitForLoadState('networkidle');
const flightCards = page.locator('[data-testid="flight-card"]');
await expect(flightCards).toHaveCount(1);
});
test('Should search with today date (Test 2)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await expect(page).toHaveURL(/departure\/MOW-\d{8}/);
await expect(page).toHaveTitle(/Отправление/);
});
test('Should search with future date (Test 3)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', futureDate)}`);
await page.waitForLoadState('networkidle');
await expect(page).toHaveURL(/departure\/MOW-\d{8}/);
});
test('Should search with past date and show validation (Test 4)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', pastDate)}`);
await page.waitForLoadState('networkidle');
await expect(page).toHaveURL(/departure\/MOW-\d{8}/);
});
test('Should search without date and use today (Test 5)', async ({ page }) => {
await page.goto(`/ru-ru/onlineboard/departure/MOW`);
await page.waitForLoadState('networkidle');
await expect(page).toHaveURL(/departure\/MOW-\d{8}/);
});
test('Should search with invalid flight number and show error (Test 6)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const searchInput = page.locator('[data-testid="flight-search-input"]');
await searchInput.fill('INVALID');
await searchInput.press('Enter');
await page.waitForLoadState('networkidle');
const noResults = page.locator('[data-testid="no-results"]');
await expect(noResults).toBeVisible();
await expect(noResults).toContainText('Нет результатов');
});
test('Should search with empty flight number and show validation (Test 7)', async ({
page,
}) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const searchInput = page.locator('[data-testid="flight-search-input"]');
await searchInput.fill('');
await searchInput.press('Enter');
await page.waitForLoadState('networkidle');
const noResults = page.locator('[data-testid="no-results"]');
await expect(noResults).toBeVisible();
});
test('Should search with flight number that has multiple results (Test 8)', async ({
page,
}) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const searchInput = page.locator('[data-testid="flight-search-input"]');
await searchInput.fill('SU');
await searchInput.press('Enter');
await page.waitForLoadState('networkidle');
const flightCards = page.locator('[data-testid="flight-card"]');
await expect(flightCards).toHaveCount(20);
});
});
test.describe('Category 2: Date Selection (6 tests)', () => {
test('Should select date from calendar (Test 9)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const calendarInput = page.locator('[data-testid="calendar-input"]');
await expect(calendarInput).toBeVisible();
});
test('Should select date range (Test 10)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const dateRange = page.locator('[data-testid="date-range"]');
await expect(dateRange).toBeVisible();
});
test('Should verify date format DD.MM.YYYY (Test 11)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const dateText = page.locator('[data-testid="board-date"]');
await expect(dateText).toBeVisible();
const dateValue = await dateText.textContent();
expect(dateValue).toMatch(/\d{2}\.\d{2}\.\d{4}/);
});
test('Should verify date validation min/max dates (Test 12)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const calendarInput = page.locator('[data-testid="calendar-input"]');
await expect(calendarInput).toBeEnabled();
});
test('Should select today date (Test 13)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const todayTab = page.locator(`[data-testid="date-tab-${today.replace(/-/g, '')}"]`);
await expect(todayTab).toBeVisible();
});
test('Should select tomorrow date (Test 14)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', tomorrow)}`);
await page.waitForLoadState('networkidle');
const tomorrowTab = page.locator(`[data-testid="date-tab-${tomorrow.replace(/-/g, '')}"]`);
await expect(tomorrowTab).toBeVisible();
});
});
test.describe('Category 3: Flight Results (6 tests)', () => {
test('Should verify flight results display (Test 15)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const flightCards = page.locator('[data-testid="flight-card"]');
await expect(flightCards).toHaveCount(20);
});
test('Should verify flight count (Test 16)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const flightCards = page.locator('[data-testid="flight-card"]');
await expect(flightCards).toHaveCount(20);
});
test('Should verify flight details in results (Test 17)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toBeVisible();
const flightNumber = flightCard.locator('[data-testid="flight-number"]');
await expect(flightNumber).toBeVisible();
});
test('Should verify empty results message (Test 18)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByNumber(page, 'SU 9999');
const noResults = page.locator('[data-testid="no-results"]');
await expect(noResults).toBeVisible();
await expect(noResults).toContainText('Нет результатов');
});
test('Should verify loading state (Test 19)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const loadingSpinner = page.locator('[data-testid="loading-spinner"]');
await expect(loadingSpinner).toBeVisible();
});
test('Should verify error state (Test 20)', async ({ page }) => {
await page.route('**/api/flights/**', (route) => {
return route.abort('internetdisconnected');
});
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const networkError = page.locator('[data-testid="network-error"]');
await expect(networkError).toBeVisible();
});
});
test.describe('Category 4: Flight Details (5 tests)', () => {
test('Should open flight details from results (Test 21)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await flightCard.click();
await page.waitForLoadState('networkidle');
await expect(page).toHaveURL(/\/ru-ru\/[A-Z]{2}\s?\d+-\d{8}/);
});
test('Should verify flight details content (Test 22)', async ({ page }) => {
const flight = generateFlight({ direction: 'departure', cityCode: 'MOW' });
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByNumber(page, flight.flightNumber);
const flightCard = page.locator('[data-testid="flight-card"]').first();
await flightCard.click();
await page.waitForLoadState('networkidle');
await expect(page.getByText(flight.flightNumber)).toBeVisible();
await expect(page.getByText(flight.airlineName)).toBeVisible();
});
test('Should verify flight route details (Test 23)', async ({ page }) => {
const flight = generateFlight({ direction: 'departure', cityCode: 'MOW' });
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByNumber(page, flight.flightNumber);
const flightCard = page.locator('[data-testid="flight-card"]').first();
await flightCard.click();
await page.waitForLoadState('networkidle');
await expect(page.getByText(flight.departure.cityName)).toBeVisible();
await expect(page.getByText(flight.arrival.cityName)).toBeVisible();
});
test('Should verify flight status details (Test 24)', async ({ page }) => {
const flight = generateFlight({
direction: 'departure',
cityCode: 'MOW',
status: 'scheduled',
});
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByNumber(page, flight.flightNumber);
const flightCard = page.locator('[data-testid="flight-card"]').first();
await flightCard.click();
await page.waitForLoadState('networkidle');
await expect(page.getByText(flight.status)).toBeVisible();
});
test('Should close flight details (Test 25)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await flightCard.click();
await page.waitForLoadState('networkidle');
const closeBtn = page.locator('[data-testid="close-details-btn"]');
await closeBtn.click();
await page.waitForLoadState('networkidle');
await expect(page).toHaveURL(/departure\/MOW-\d{8}/);
});
});
test.describe('Category 5: Edge Cases (5 tests)', () => {
test('Should search for non-existent flight number (Test 26)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByNumber(page, 'SU 9999');
const noResults = page.locator('[data-testid="no-results"]');
await expect(noResults).toBeVisible();
});
test('Should search with special characters (Test 27)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const searchInput = page.locator('[data-testid="flight-search-input"]');
await searchInput.fill('SU 123!');
await searchInput.press('Enter');
await page.waitForLoadState('networkidle');
const noResults = page.locator('[data-testid="no-results"]');
await expect(noResults).toBeVisible();
});
test('Should search with very long flight number (Test 28)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const searchInput = page.locator('[data-testid="flight-search-input"]');
await searchInput.fill('SU 12345678901234567890');
await searchInput.press('Enter');
await page.waitForLoadState('networkidle');
const flightCards = page.locator('[data-testid="flight-card"]');
await expect(flightCards).toHaveCount(20);
});
test('Should search with Unicode characters (Test 29)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const searchInput = page.locator('[data-testid="flight-search-input"]');
await searchInput.fill('SU 1234 🛫');
await searchInput.press('Enter');
await page.waitForLoadState('networkidle');
const flightCards = page.locator('[data-testid="flight-card"]');
await expect(flightCards).toHaveCount(20);
});
test('Should handle rapid search attempts (Test 30)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const searchInput = page.locator('[data-testid="flight-search-input"]');
for (let i = 0; i < 5; i++) {
await searchInput.fill(`SU ${1000 + i}`);
await searchInput.press('Enter');
await page.waitForLoadState('networkidle');
}
const flightCards = page.locator('[data-testid="flight-card"]');
await expect(flightCards).toHaveCount(20);
});
});
test.describe('Additional Flight Search Tests', () => {
test('Should navigate to flight board for different cities (Test 31)', async ({ page }) => {
const cities = ['MOW', 'LED', 'AER', 'OVB', 'KRR'];
for (const cityCode of cities) {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', cityCode, today)}`);
await page.waitForLoadState('networkidle');
await expect(page).toHaveURL(new RegExp(`departure/${cityCode}-\\d{8}`));
}
});
test('Should display correct date in title (Test 32)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const dateText = page.locator('[data-testid="board-date"]');
await expect(dateText).toBeVisible();
await expect(dateText).toContainText(today);
});
test('Should filter by status (Test 33)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const statusFilter = page.locator('[data-testid="status-filter"]');
await statusFilter.click();
const scheduledOption = page.locator('[data-testid="filter-option-scheduled"]');
await scheduledOption.click();
const filteredFlights = page.locator('[data-testid="flight-card"]');
await expect(filteredFlights).toHaveCount(20);
});
test('Should filter by airline (Test 34)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const airlineFilter = page.locator('[data-testid="airline-filter"]');
await airlineFilter.click();
const aeroflotOption = page.locator('[data-testid="filter-option-SU"]');
await aeroflotOption.click();
const filteredFlights = page.locator('[data-testid="flight-card"]');
await expect(filteredFlights).toHaveCount(20);
});
test('Should display flight number (Test 35)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toBeVisible();
const flightNumber = flightCard.locator('[data-testid="flight-number"]');
await expect(flightNumber).toBeVisible();
});
test('Should display airline name (Test 36)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toBeVisible();
const airlineName = flightCard.locator('[data-testid="airline-name"]');
await expect(airlineName).toBeVisible();
});
test('Should display departure and arrival cities (Test 37)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toBeVisible();
const departureCity = flightCard.locator('[data-testid="departure-city"]');
await expect(departureCity).toBeVisible();
});
test('Should display scheduled time (Test 38)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toBeVisible();
const scheduledTime = flightCard.locator('[data-testid="scheduled-time"]');
await expect(scheduledTime).toBeVisible();
});
test('Should display actual time when available (Test 39)', async ({ page }) => {
const flight = generateFlight({
direction: 'departure',
cityCode: 'MOW',
status: 'departed',
});
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toBeVisible();
const actualTime = flightCard.locator('[data-testid="actual-time"]');
await expect(actualTime).toBeVisible();
});
test('Should display delay information (Test 40)', async ({ page }) => {
const flight = generateFlight({
direction: 'departure',
cityCode: 'MOW',
status: 'delayed',
});
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toBeVisible();
const delayInfo = flightCard.locator('[data-testid="delay-info"]');
await expect(delayInfo).toBeVisible();
});
test('Should display terminal information (Test 41)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toBeVisible();
const terminal = flightCard.locator('[data-testid="terminal"]');
await expect(terminal).toBeVisible();
});
test('Should display baggage belt information (Test 42)', async ({ page }) => {
const flight = generateFlight({
direction: 'departure',
cityCode: 'MOW',
status: 'arrived',
});
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toBeVisible();
const baggageBelt = flightCard.locator('[data-testid="baggage-belt"]');
await expect(baggageBelt).toBeVisible();
});
test('Should navigate to tomorrow date tab (Test 43)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const dateTab = page.locator(`[data-testid="date-tab-${tomorrow.replace(/-/g, '')}"]`);
if ((await dateTab.count()) > 0) {
await dateTab.click();
await page.waitForLoadState('networkidle');
await expect(page).toHaveURL(/departure\/MOW-\d{8}/);
}
});
test('Should handle invalid city code (Test 44)', async ({ page }) => {
await page.goto(`/ru-ru/onlineboard/departure/XXX-${today.replace(/-/g, '')}`);
await page.waitForLoadState('networkidle');
const errorState = page.locator('[data-testid="error-state"]');
await expect(errorState).toBeVisible();
});
test('Should handle network error (Test 45)', async ({ page }) => {
await page.route('**/api/flights/**', (route) => {
return route.abort('internetdisconnected');
});
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const networkError = page.locator('[data-testid="network-error"]');
await expect(networkError).toBeVisible();
});
test('Should have proper ARIA labels (Test 46)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toHaveAttribute('role', 'article');
});
test('Should be keyboard navigable (Test 47)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await page.keyboard.press('Tab');
await page.keyboard.press('Tab');
await page.keyboard.press('Tab');
const focusedElement = page.locator(':focus');
await expect(focusedElement).toBeVisible();
});
test('Should search by flight number from search input (Test 48)', async ({ page }) => {
const flight = generateFlight({ direction: 'departure', cityCode: 'MOW' });
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByNumber(page, flight.flightNumber);
const searchResults = page.locator('[data-testid="flight-card"]');
await expect(searchResults).toHaveCount(1);
await verifyFlightCard(page, flight);
});
test('Should show no results when flight not found (Test 49)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByNumber(page, 'SU 9999');
const noResults = page.locator('[data-testid="no-results"]');
await expect(noResults).toBeVisible();
await expect(noResults).toContainText('Нет результатов');
});
test('Should search by route (Test 50)', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByRoute(page, 'Moscow', 'Sochi');
const flightCards = page.locator('[data-testid="flight-card"]');
await expect(flightCards).toHaveCount(20);
});
});
});
@@ -1,450 +0,0 @@
import { test, expect } from '@playwright/test';
import type { Page } from '@playwright/test';
import {
buildFlightDetailsPath,
buildRouteParam,
generateFlight,
generateFlights,
getToday,
getTomorrow,
CITIES,
} from '../support/test-utilities';
const today = getToday();
const tomorrow = getTomorrow();
// ============================================================================
// Flight Details Tests
// ============================================================================
test.describe('Flight Details', () => {
test.describe('Page Navigation', () => {
test('should navigate to flight details page', async ({ page }) => {
const flight = generateFlight({ direction: 'departure', cityCode: 'MOW' });
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
await expect(page).toHaveURL(new RegExp(`/${slug}`));
await expect(page).toHaveTitle(new RegExp(flight.flightNumber));
});
test('should navigate to flight details for arrival flight', async ({ page }) => {
const flight = generateFlight({ direction: 'arrival', cityCode: 'MOW' });
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
await expect(page).toHaveURL(new RegExp(`/${slug}`));
});
test('should navigate to flight details for different date', async ({ page }) => {
const flight = generateFlight({ direction: 'departure', cityCode: 'MOW', date: tomorrow });
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${tomorrow.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
await expect(page).toHaveURL(new RegExp(`/${slug}`));
});
});
test.describe('Flight Information', () => {
test('should display flight number', async ({ page }) => {
const flight = generateFlight({ direction: 'departure', cityCode: 'MOW' });
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
const flightNumber = page.locator('[data-testid="flight-number"]');
await expect(flightNumber).toBeVisible();
await expect(flightNumber).toContainText(flight.flightNumber);
});
test('should display airline name', async ({ page }) => {
const flight = generateFlight({ direction: 'departure', cityCode: 'MOW' });
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
const airlineName = page.locator('[data-testid="airline-name"]');
await expect(airlineName).toBeVisible();
await expect(airlineName).toContainText(flight.airlineName);
});
test('should display aircraft type', async ({ page }) => {
const flight = generateFlight({ direction: 'departure', cityCode: 'MOW' });
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
const aircraftType = page.locator('[data-testid="aircraft-type"]');
await expect(aircraftType).toBeVisible();
await expect(aircraftType).toContainText(flight.aircraftType || '');
});
test('should display departure city', async ({ page }) => {
const flight = generateFlight({ direction: 'departure', cityCode: 'MOW' });
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
const departureCity = page.locator('[data-testid="departure-city"]');
await expect(departureCity).toBeVisible();
await expect(departureCity).toContainText(flight.departure.cityName);
});
test('should display arrival city', async ({ page }) => {
const flight = generateFlight({ direction: 'departure', cityCode: 'MOW' });
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
const arrivalCity = page.locator('[data-testid="arrival-city"]');
await expect(arrivalCity).toBeVisible();
await expect(arrivalCity).toContainText(flight.arrival.cityName);
});
test('should display scheduled departure time', async ({ page }) => {
const flight = generateFlight({ direction: 'departure', cityCode: 'MOW' });
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
const depTime = page.locator('[data-testid="scheduled-departure-time"]');
await expect(depTime).toBeVisible();
const depTimeText = flight.departure.time.scheduled.slice(11, 16);
await expect(depTime).toContainText(depTimeText);
});
test('should display scheduled arrival time', async ({ page }) => {
const flight = generateFlight({ direction: 'departure', cityCode: 'MOW' });
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
const arrTime = page.locator('[data-testid="scheduled-arrival-time"]');
await expect(arrTime).toBeVisible();
const arrTimeText = flight.arrival.time.scheduled.slice(11, 16);
await expect(arrTime).toContainText(arrTimeText);
});
test('should display flight duration', async ({ page }) => {
const flight = generateFlight({ direction: 'departure', cityCode: 'MOW' });
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
const duration = page.locator('[data-testid="flight-duration"]');
await expect(duration).toBeVisible();
});
test('should display flight status', async ({ page }) => {
const flight = generateFlight({
direction: 'departure',
cityCode: 'MOW',
status: 'scheduled',
});
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
const status = page.locator('[data-testid="flight-status"]');
await expect(status).toBeVisible();
await expect(status).toContainText(flight.status);
});
});
test.describe('Flight Details', () => {
test('should display terminal information', async ({ page }) => {
const flight = generateFlight({
direction: 'departure',
cityCode: 'MOW',
departure: { terminal: 'B' },
});
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
const terminal = page.locator('[data-testid="terminal"]');
await expect(terminal).toBeVisible();
await expect(terminal).toContainText('B');
});
test('should display boarding gate information', async ({ page }) => {
const flight = generateFlight({
direction: 'departure',
cityCode: 'MOW',
status: 'boarding',
boarding: { gate: '11', status: 'Идёт посадка' },
});
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
const boardingGate = page.locator('[data-testid="boarding-gate"]');
await expect(boardingGate).toBeVisible();
await expect(boardingGate).toContainText('11');
});
test('should display baggage belt information', async ({ page }) => {
const flight = generateFlight({
direction: 'arrival',
cityCode: 'MOW',
status: 'arrived',
arrivalInfo: { baggageBelt: '5', transfer: 'Тран' },
});
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
const baggageBelt = page.locator('[data-testid="baggage-belt"]');
await expect(baggageBelt).toBeVisible();
await expect(baggageBelt).toContainText('5');
});
test('should display check-in information', async ({ page }) => {
const flight = generateFlight({
direction: 'departure',
cityCode: 'MOW',
status: 'checkin',
checkin: { status: 'В процессе' },
});
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
const checkinInfo = page.locator('[data-testid="checkin-info"]');
await expect(checkinInfo).toBeVisible();
});
test('should display deplaning information', async ({ page }) => {
const flight = generateFlight({
direction: 'arrival',
cityCode: 'MOW',
status: 'arrived',
deplaning: { status: 'В процессе', transfer: 'Трап' },
});
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
const deplaningInfo = page.locator('[data-testid="deplaning-info"]');
await expect(deplaningInfo).toBeVisible();
});
});
test.describe('Aircraft Information', () => {
test('should display aircraft type', async ({ page }) => {
const flight = generateFlight({ direction: 'departure', cityCode: 'MOW' });
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
const aircraftType = page.locator('[data-testid="aircraft-type"]');
await expect(aircraftType).toBeVisible();
});
test('should display aircraft name', async ({ page }) => {
const flight = generateFlight({
direction: 'departure',
cityCode: 'MOW',
aircraft: { type: 'Airbus A320', name: 'В. Высоцкий' },
});
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
const aircraftName = page.locator('[data-testid="aircraft-name"]');
await expect(aircraftName).toBeVisible();
await expect(aircraftName).toContainText('В. Высоцкий');
});
test('should display seat configuration', async ({ page }) => {
const flight = generateFlight({ direction: 'departure', cityCode: 'MOW' });
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
const seatInfo = page.locator('[data-testid="seat-info"]');
await expect(seatInfo).toBeVisible();
});
});
test.describe('Schedule Information', () => {
test('should display scheduled departure', async ({ page }) => {
const flight = generateFlight({ direction: 'departure', cityCode: 'MOW' });
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
const scheduledDep = page.locator('[data-testid="scheduled-departure"]');
await expect(scheduledDep).toBeVisible();
});
test('should display scheduled arrival', async ({ page }) => {
const flight = generateFlight({ direction: 'departure', cityCode: 'MOW' });
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
const scheduledArr = page.locator('[data-testid="scheduled-arrival"]');
await expect(scheduledArr).toBeVisible();
});
test('should display actual departure when available', async ({ page }) => {
const flight = generateFlight({
direction: 'departure',
cityCode: 'MOW',
status: 'departed',
});
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
const actualDep = page.locator('[data-testid="actual-departure"]');
await expect(actualDep).toBeVisible();
});
test('should display actual arrival when available', async ({ page }) => {
const flight = generateFlight({
direction: 'arrival',
cityCode: 'MOW',
status: 'arrived',
});
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
const actualArr = page.locator('[data-testid="actual-arrival"]');
await expect(actualArr).toBeVisible();
});
test('should display expected arrival when delayed', async ({ page }) => {
const flight = generateFlight({
direction: 'arrival',
cityCode: 'MOW',
status: 'delayed',
});
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
const expectedArr = page.locator('[data-testid="expected-arrival"]');
await expect(expectedArr).toBeVisible();
});
});
test.describe('Error Handling', () => {
test('should handle invalid flight number', async ({ page }) => {
await page.goto(`/ru-ru/SU9999-${today.replace(/-/g, '')}`);
await page.waitForLoadState('networkidle');
const errorState = page.locator('[data-testid="error-state"]');
await expect(errorState).toBeVisible();
});
test('should handle flight not found', async ({ page }) => {
await page.goto(`/ru-ru/SU9999-20260406`);
await page.waitForLoadState('networkidle');
const notFound = page.locator('[data-testid="not-found"]');
await expect(notFound).toBeVisible();
});
test('should handle network error', async ({ page }) => {
await page.route('**/api/flights/**', (route) => {
return route.abort('internetdisconnected');
});
const flight = generateFlight({ direction: 'departure', cityCode: 'MOW' });
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
const networkError = page.locator('[data-testid="network-error"]');
await expect(networkError).toBeVisible();
});
});
test.describe('Navigation', () => {
test('should navigate back to board', async ({ page }) => {
const flight = generateFlight({ direction: 'departure', cityCode: 'MOW' });
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
const backLink = page.locator('[data-testid="back-link"]');
await backLink.click();
await expect(page).toHaveURL(/onlineboard\/departure\/MOW-\d{8}/);
});
test('should navigate to related flights', async ({ page }) => {
const flight = generateFlight({ direction: 'departure', cityCode: 'MOW' });
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
const relatedFlights = page.locator('[data-testid="related-flights"]');
await expect(relatedFlights).toBeVisible();
});
});
test.describe('Accessibility', () => {
test('should have proper ARIA labels', async ({ page }) => {
const flight = generateFlight({ direction: 'departure', cityCode: 'MOW' });
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
const pageContent = page.locator('[data-testid="page-content"]');
await expect(pageContent).toHaveAttribute('role', 'main');
});
test('should be keyboard navigable', async ({ page }) => {
const flight = generateFlight({ direction: 'departure', cityCode: 'MOW' });
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
await page.keyboard.press('Tab');
await page.keyboard.press('Tab');
await page.keyboard.press('Tab');
const focusedElement = page.locator(':focus');
await expect(focusedElement).toBeVisible();
});
});
});
@@ -1,334 +0,0 @@
import { test, expect } from '@playwright/test';
import type { Page } from '@playwright/test';
import {
buildFlightsMapPath,
buildRouteParam,
generateFlight,
generateFlights,
getToday,
getTomorrow,
CITIES,
} from '@e2e/support/test-utilities';
const today = getToday();
const tomorrow = getTomorrow();
// ============================================================================
// Flights Map Tests
// ============================================================================
test.describe('Flights Map', () => {
test.describe('Page Navigation', () => {
test('should navigate to flights map page', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
await expect(page).toHaveURL(/flights-map/);
await expect(page).toHaveTitle(/Карта полетов/);
});
test('should display map container', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const mapContainer = page.locator('[data-testid="map-container"]');
await expect(mapContainer).toBeVisible();
});
});
test.describe('Map Display', () => {
test('should display flight markers', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const markers = page.locator('[data-testid="flight-marker"]');
await expect(markers).toHaveCount(20);
});
test('should display flight popup on marker click', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const marker = page.locator('[data-testid="flight-marker"]').first();
await marker.click();
const popup = page.locator('[data-testid="flight-popup"]');
await expect(popup).toBeVisible();
});
test('should display flight details in popup', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const marker = page.locator('[data-testid="flight-marker"]').first();
await marker.click();
const flightNumber = page.locator('[data-testid="popup-flight-number"]');
await expect(flightNumber).toBeVisible();
});
test('should display route line between airports', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const routeLine = page.locator('[data-testid="route-line"]');
await expect(routeLine).toBeVisible();
});
});
test.describe('Filtering', () => {
test('should filter by departure city', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const departureFilter = page.locator('[data-testid="departure-filter"]');
await departureFilter.click();
const moscowOption = page.locator('[data-testid="filter-option-MOW"]');
await moscowOption.click();
const markers = page.locator('[data-testid="flight-marker"]');
await expect(markers).toHaveCount(20);
});
test('should filter by arrival city', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const arrivalFilter = page.locator('[data-testid="arrival-filter"]');
await arrivalFilter.click();
const sochiOption = page.locator('[data-testid="filter-option-AER"]');
await sochiOption.click();
const markers = page.locator('[data-testid="flight-marker"]');
await expect(markers).toHaveCount(20);
});
test('should filter by status', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const statusFilter = page.locator('[data-testid="status-filter"]');
await statusFilter.click();
const scheduledOption = page.locator('[data-testid="filter-option-scheduled"]');
await scheduledOption.click();
const markers = page.locator('[data-testid="flight-marker"]');
await expect(markers).toHaveCount(20);
});
test('should clear all filters', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const clearFilters = page.locator('[data-testid="clear-filters"]');
await clearFilters.click();
const markers = page.locator('[data-testid="flight-marker"]');
await expect(markers).toHaveCount(20);
});
});
test.describe('Flight Details Panel', () => {
test('should display flight details panel', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const panel = page.locator('[data-testid="flight-details-panel"]');
await expect(panel).toBeVisible();
});
test('should display flight number in panel', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const flightNumber = page.locator('[data-testid="panel-flight-number"]');
await expect(flightNumber).toBeVisible();
});
test('should display airline name in panel', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const airlineName = page.locator('[data-testid="panel-airline-name"]');
await expect(airlineName).toBeVisible();
});
test('should display departure and arrival cities in panel', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const departureCity = page.locator('[data-testid="panel-departure-city"]');
await expect(departureCity).toBeVisible();
const arrivalCity = page.locator('[data-testid="panel-arrival-city"]');
await expect(arrivalCity).toBeVisible();
});
test('should display scheduled times in panel', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const depTime = page.locator('[data-testid="panel-departure-time"]');
await expect(depTime).toBeVisible();
const arrTime = page.locator('[data-testid="panel-arrival-time"]');
await expect(arrTime).toBeVisible();
});
test('should display aircraft type in panel', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const aircraftType = page.locator('[data-testid="panel-aircraft-type"]');
await expect(aircraftType).toBeVisible();
});
test('should display flight status in panel', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const status = page.locator('[data-testid="panel-status"]');
await expect(status).toBeVisible();
});
});
test.describe('Map Controls', () => {
test('should have zoom in button', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const zoomIn = page.locator('[data-testid="zoom-in"]');
await expect(zoomIn).toBeVisible();
});
test('should have zoom out button', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const zoomOut = page.locator('[data-testid="zoom-out"]');
await expect(zoomOut).toBeVisible();
});
test('should have full screen button', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const fullScreen = page.locator('[data-testid="full-screen"]');
await expect(fullScreen).toBeVisible();
});
test('should have layer toggle', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const layerToggle = page.locator('[data-testid="layer-toggle"]');
await expect(layerToggle).toBeVisible();
});
});
test.describe('Cluster Markers', () => {
test('should display cluster markers for multiple flights', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const cluster = page.locator('[data-testid="cluster-marker"]');
if ((await cluster.count()) > 0) {
await expect(cluster).toBeVisible();
}
});
test('should expand cluster on click', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const cluster = page.locator('[data-testid="cluster-marker"]').first();
if ((await cluster.count()) > 0) {
await cluster.click();
await page.waitForTimeout(500);
const markers = page.locator('[data-testid="flight-marker"]');
await expect(markers).toHaveCount(20);
}
});
});
test.describe('Error Handling', () => {
test('should handle network error', async ({ page }) => {
await page.route('**/api/flights-map/**', (route) => {
return route.abort('internetdisconnected');
});
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const networkError = page.locator('[data-testid="network-error"]');
await expect(networkError).toBeVisible();
});
test('should handle map loading error', async ({ page }) => {
await page.route('**/api/maps/**', (route) => {
return route.abort('internetdisconnected');
});
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const mapError = page.locator('[data-testid="map-error"]');
await expect(mapError).toBeVisible();
});
});
test.describe('Accessibility', () => {
test('should have proper ARIA labels', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const mapContainer = page.locator('[data-testid="map-container"]');
await expect(mapContainer).toHaveAttribute('role', 'application');
});
test('should be keyboard navigable', async ({ page }) => {
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
await page.keyboard.press('Tab');
await page.keyboard.press('Tab');
await page.keyboard.press('Tab');
const focusedElement = page.locator(':focus');
await expect(focusedElement).toBeVisible();
});
});
test.describe('Responsive Design', () => {
test('should be responsive on mobile', async ({ page }) => {
await page.setViewportSize({ width: 375, height: 667 });
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const mapContainer = page.locator('[data-testid="map-container"]');
await expect(mapContainer).toBeVisible();
});
test('should be responsive on tablet', async ({ page }) => {
await page.setViewportSize({ width: 768, height: 1024 });
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const mapContainer = page.locator('[data-testid="map-container"]');
await expect(mapContainer).toBeVisible();
});
test('should be responsive on desktop', async ({ page }) => {
await page.setViewportSize({ width: 1920, height: 1080 });
await page.goto(`/ru-ru${buildFlightsMapPath()}`);
await page.waitForLoadState('networkidle');
const mapContainer = page.locator('[data-testid="map-container"]');
await expect(mapContainer).toBeVisible();
});
});
});
@@ -1,301 +0,0 @@
import { test, expect } from '@playwright/test';
import type { Page } from '@playwright/test';
import {
buildOnlineBoardPath,
buildRouteParam,
searchFlightByNumber,
searchFlightByRoute,
verifyFlightCard,
generateFlight,
generateFlights,
getToday,
getTomorrow,
CITIES,
} from '../support/test-utilities';
const today = getToday();
const tomorrow = getTomorrow();
const dateParam = buildRouteParam('MOW', today);
const tomorrowParam = buildRouteParam('MOW', tomorrow);
// ============================================================================
// Online Board - Arrival Tests
// ============================================================================
test.describe('Online Board - Arrival', () => {
test.describe('Page Navigation', () => {
test('should navigate to arrival board for Moscow', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await expect(page).toHaveURL(/arrival\/MOW-\d{8}/);
await expect(page).toHaveTitle(/Прибытие/);
});
test('should navigate to arrival board for Saint Petersburg', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'LED', today)}`);
await page.waitForLoadState('networkidle');
await expect(page).toHaveURL(/arrival\/LED-\d{8}/);
});
test('should navigate to arrival board for Sochi', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'AER', today)}`);
await page.waitForLoadState('networkidle');
await expect(page).toHaveURL(/arrival\/AER-\d{8}/);
});
});
test.describe('Flight Display', () => {
test('should display arrival flights for Moscow', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const flightCards = page.locator('[data-testid="flight-card"]');
await expect(flightCards).toHaveCount(20);
});
test('should display flight details correctly', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toBeVisible();
await expect(flightCard.getByText('Прибытие')).toBeVisible();
});
});
test.describe('Flight Search', () => {
test('should search flight by flight number', async ({ page }) => {
const flight = generateFlight({ direction: 'arrival', cityCode: 'MOW' });
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByNumber(page, flight.flightNumber);
const searchResults = page.locator('[data-testid="flight-card"]');
await expect(searchResults).toHaveCount(1);
await verifyFlightCard(page, flight);
});
test('should show no results when flight not found', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByNumber(page, 'SU 9999');
const noResults = page.locator('[data-testid="no-results"]');
await expect(noResults).toBeVisible();
await expect(noResults).toContainText('Нет результатов');
});
});
test.describe('Date Navigation', () => {
test('should navigate to tomorrow', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const dateTab = page.locator(`[data-testid="date-tab-${tomorrow.replace(/-/g, '')}"]`);
if ((await dateTab.count()) > 0) {
await dateTab.click();
await page.waitForLoadState('networkidle');
await expect(page).toHaveURL(/arrival\/MOW-\d{8}/);
}
});
test('should display correct date in title', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const dateText = page.locator('[data-testid="board-date"]');
await expect(dateText).toBeVisible();
});
});
test.describe('Filtering', () => {
test('should filter by status', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const statusFilter = page.locator('[data-testid="status-filter"]');
await statusFilter.click();
const scheduledOption = page.locator('[data-testid="filter-option-scheduled"]');
await scheduledOption.click();
const filteredFlights = page.locator('[data-testid="flight-card"]');
await expect(filteredFlights).toHaveCount(20);
});
test('should filter by airline', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const airlineFilter = page.locator('[data-testid="airline-filter"]');
await airlineFilter.click();
const aeroflotOption = page.locator('[data-testid="filter-option-SU"]');
await aeroflotOption.click();
const filteredFlights = page.locator('[data-testid="flight-card"]');
await expect(filteredFlights).toHaveCount(20);
});
});
test.describe('Flight Card', () => {
test('should display flight number', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toBeVisible();
const flightNumber = flightCard.locator('[data-testid="flight-number"]');
await expect(flightNumber).toBeVisible();
});
test('should display airline name', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toBeVisible();
const airlineName = flightCard.locator('[data-testid="airline-name"]');
await expect(airlineName).toBeVisible();
});
test('should display departure and arrival cities', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toBeVisible();
const arrivalCity = flightCard.locator('[data-testid="arrival-city"]');
await expect(arrivalCity).toBeVisible();
});
test('should display scheduled time', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toBeVisible();
const scheduledTime = flightCard.locator('[data-testid="scheduled-time"]');
await expect(scheduledTime).toBeVisible();
});
test('should display actual time when available', async ({ page }) => {
const flight = generateFlight({
direction: 'arrival',
cityCode: 'MOW',
status: 'arrived',
});
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toBeVisible();
const actualTime = flightCard.locator('[data-testid="actual-time"]');
await expect(actualTime).toBeVisible();
});
test('should display delay information', async ({ page }) => {
const flight = generateFlight({
direction: 'arrival',
cityCode: 'MOW',
status: 'delayed',
});
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toBeVisible();
const delayInfo = flightCard.locator('[data-testid="delay-info"]');
await expect(delayInfo).toBeVisible();
});
test('should display terminal information', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toBeVisible();
const terminal = flightCard.locator('[data-testid="terminal"]');
await expect(terminal).toBeVisible();
});
test('should display baggage belt information', async ({ page }) => {
const flight = generateFlight({
direction: 'arrival',
cityCode: 'MOW',
status: 'arrived',
});
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toBeVisible();
const baggageBelt = flightCard.locator('[data-testid="baggage-belt"]');
await expect(baggageBelt).toBeVisible();
});
});
test.describe('Error Handling', () => {
test('should handle invalid city code', async ({ page }) => {
await page.goto(`/ru-ru/onlineboard/arrival/XXX-${today.replace(/-/g, '')}`);
await page.waitForLoadState('networkidle');
const errorState = page.locator('[data-testid="error-state"]');
await expect(errorState).toBeVisible();
});
test('should handle network error', async ({ page }) => {
await page.route('**/api/flights/**', (route) => {
return route.abort('internetdisconnected');
});
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const networkError = page.locator('[data-testid="network-error"]');
await expect(networkError).toBeVisible();
});
});
test.describe('Accessibility', () => {
test('should have proper ARIA labels', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toHaveAttribute('role', 'article');
});
test('should be keyboard navigable', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('arrival', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await page.keyboard.press('Tab');
await page.keyboard.press('Tab');
await page.keyboard.press('Tab');
const focusedElement = page.locator(':focus');
await expect(focusedElement).toBeVisible();
});
});
});
@@ -1,301 +0,0 @@
import { test, expect } from '@playwright/test';
import type { Page } from '@playwright/test';
import {
buildOnlineBoardPath,
buildRouteParam,
searchFlightByNumber,
searchFlightByRoute,
verifyFlightCard,
generateFlight,
generateFlights,
getToday,
getTomorrow,
CITIES,
} from '../support/test-utilities';
const today = getToday();
const tomorrow = getTomorrow();
const dateParam = buildRouteParam('MOW', today);
const tomorrowParam = buildRouteParam('MOW', tomorrow);
// ============================================================================
// Online Board - Departure Tests
// ============================================================================
test.describe('Online Board - Departure', () => {
test.describe('Page Navigation', () => {
test('should navigate to departure board for Moscow', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await expect(page).toHaveURL(/departure\/MOW-\d{8}/);
await expect(page).toHaveTitle(/Отправление/);
});
test('should navigate to departure board for Saint Petersburg', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'LED', today)}`);
await page.waitForLoadState('networkidle');
await expect(page).toHaveURL(/departure\/LED-\d{8}/);
});
test('should navigate to departure board for Sochi', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'AER', today)}`);
await page.waitForLoadState('networkidle');
await expect(page).toHaveURL(/departure\/AER-\d{8}/);
});
});
test.describe('Flight Display', () => {
test('should display departure flights for Moscow', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const flightCards = page.locator('[data-testid="flight-card"]');
await expect(flightCards).toHaveCount(20);
});
test('should display flight details correctly', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toBeVisible();
await expect(flightCard.getByText('Отправление')).toBeVisible();
});
});
test.describe('Flight Search', () => {
test('should search flight by flight number', async ({ page }) => {
const flight = generateFlight({ direction: 'departure', cityCode: 'MOW' });
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByNumber(page, flight.flightNumber);
const searchResults = page.locator('[data-testid="flight-card"]');
await expect(searchResults).toHaveCount(1);
await verifyFlightCard(page, flight);
});
test('should show no results when flight not found', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByNumber(page, 'SU 9999');
const noResults = page.locator('[data-testid="no-results"]');
await expect(noResults).toBeVisible();
await expect(noResults).toContainText('Нет результатов');
});
});
test.describe('Date Navigation', () => {
test('should navigate to tomorrow', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const dateTab = page.locator(`[data-testid="date-tab-${tomorrow.replace(/-/g, '')}"]`);
if ((await dateTab.count()) > 0) {
await dateTab.click();
await page.waitForLoadState('networkidle');
await expect(page).toHaveURL(/departure\/MOW-\d{8}/);
}
});
test('should display correct date in title', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const dateText = page.locator('[data-testid="board-date"]');
await expect(dateText).toBeVisible();
});
});
test.describe('Filtering', () => {
test('should filter by status', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const statusFilter = page.locator('[data-testid="status-filter"]');
await statusFilter.click();
const scheduledOption = page.locator('[data-testid="filter-option-scheduled"]');
await scheduledOption.click();
const filteredFlights = page.locator('[data-testid="flight-card"]');
await expect(filteredFlights).toHaveCount(20);
});
test('should filter by airline', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const airlineFilter = page.locator('[data-testid="airline-filter"]');
await airlineFilter.click();
const aeroflotOption = page.locator('[data-testid="filter-option-SU"]');
await aeroflotOption.click();
const filteredFlights = page.locator('[data-testid="flight-card"]');
await expect(filteredFlights).toHaveCount(20);
});
});
test.describe('Flight Card', () => {
test('should display flight number', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toBeVisible();
const flightNumber = flightCard.locator('[data-testid="flight-number"]');
await expect(flightNumber).toBeVisible();
});
test('should display airline name', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toBeVisible();
const airlineName = flightCard.locator('[data-testid="airline-name"]');
await expect(airlineName).toBeVisible();
});
test('should display departure and arrival cities', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toBeVisible();
const departureCity = flightCard.locator('[data-testid="departure-city"]');
await expect(departureCity).toBeVisible();
});
test('should display scheduled time', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toBeVisible();
const scheduledTime = flightCard.locator('[data-testid="scheduled-time"]');
await expect(scheduledTime).toBeVisible();
});
test('should display actual time when available', async ({ page }) => {
const flight = generateFlight({
direction: 'departure',
cityCode: 'MOW',
status: 'departed',
});
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toBeVisible();
const actualTime = flightCard.locator('[data-testid="actual-time"]');
await expect(actualTime).toBeVisible();
});
test('should display delay information', async ({ page }) => {
const flight = generateFlight({
direction: 'departure',
cityCode: 'MOW',
status: 'delayed',
});
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toBeVisible();
const delayInfo = flightCard.locator('[data-testid="delay-info"]');
await expect(delayInfo).toBeVisible();
});
test('should display terminal information', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toBeVisible();
const terminal = flightCard.locator('[data-testid="terminal"]');
await expect(terminal).toBeVisible();
});
test('should display boarding gate information', async ({ page }) => {
const flight = generateFlight({
direction: 'departure',
cityCode: 'MOW',
status: 'boarding',
});
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toBeVisible();
const boardingGate = flightCard.locator('[data-testid="boarding-gate"]');
await expect(boardingGate).toBeVisible();
});
});
test.describe('Error Handling', () => {
test('should handle invalid city code', async ({ page }) => {
await page.goto(`/ru-ru/onlineboard/departure/XXX-${today.replace(/-/g, '')}`);
await page.waitForLoadState('networkidle');
const errorState = page.locator('[data-testid="error-state"]');
await expect(errorState).toBeVisible();
});
test('should handle network error', async ({ page }) => {
await page.route('**/api/flights/**', (route) => {
return route.abort('internetdisconnected');
});
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const networkError = page.locator('[data-testid="network-error"]');
await expect(networkError).toBeVisible();
});
});
test.describe('Accessibility', () => {
test('should have proper ARIA labels', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toHaveAttribute('role', 'article');
});
test('should be keyboard navigable', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await page.keyboard.press('Tab');
await page.keyboard.press('Tab');
await page.keyboard.press('Tab');
const focusedElement = page.locator(':focus');
await expect(focusedElement).toBeVisible();
});
});
});
@@ -1,454 +0,0 @@
import { test, expect } from '@playwright/test';
import type { Page } from '@playwright/test';
import {
buildOnlineBoardPath,
buildRouteParam,
searchFlightByNumber,
verifyFlightCard,
generateFlight,
generateFlights,
getToday,
getTomorrow,
CITIES,
} from '../support/test-utilities';
const today = getToday();
const tomorrow = getTomorrow();
const dateParam = buildRouteParam('MOW', today);
const tomorrowParam = buildRouteParam('MOW', tomorrow);
// ============================================================================
// Online Board - Flight Tests
// ============================================================================
test.describe('Online Board - Flight', () => {
test.describe('Page Navigation', () => {
test('should navigate to flight details page', async ({ page }) => {
const flight = generateFlight({ direction: 'departure', cityCode: 'MOW' });
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
await expect(page).toHaveURL(new RegExp(`/${slug}`));
await expect(page).toHaveTitle(new RegExp(flight.flightNumber));
});
test('should navigate to flight details for arrival flight', async ({ page }) => {
const flight = generateFlight({ direction: 'arrival', cityCode: 'MOW' });
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
await expect(page).toHaveURL(new RegExp(`/${slug}`));
});
test('should navigate to flight details for different date', async ({ page }) => {
const flight = generateFlight({ direction: 'departure', cityCode: 'MOW', date: tomorrow });
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${tomorrow.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
await expect(page).toHaveURL(new RegExp(`/${slug}`));
});
});
test.describe('Flight Information', () => {
test('should display flight number', async ({ page }) => {
const flight = generateFlight({ direction: 'departure', cityCode: 'MOW' });
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
const flightNumber = page.locator('[data-testid="flight-number"]');
await expect(flightNumber).toBeVisible();
await expect(flightNumber).toContainText(flight.flightNumber);
});
test('should display airline name', async ({ page }) => {
const flight = generateFlight({ direction: 'departure', cityCode: 'MOW' });
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
const airlineName = page.locator('[data-testid="airline-name"]');
await expect(airlineName).toBeVisible();
await expect(airlineName).toContainText(flight.airlineName);
});
test('should display aircraft type', async ({ page }) => {
const flight = generateFlight({ direction: 'departure', cityCode: 'MOW' });
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
const aircraftType = page.locator('[data-testid="aircraft-type"]');
await expect(aircraftType).toBeVisible();
await expect(aircraftType).toContainText(flight.aircraftType || '');
});
test('should display departure city', async ({ page }) => {
const flight = generateFlight({ direction: 'departure', cityCode: 'MOW' });
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
const departureCity = page.locator('[data-testid="departure-city"]');
await expect(departureCity).toBeVisible();
await expect(departureCity).toContainText(flight.departure.cityName);
});
test('should display arrival city', async ({ page }) => {
const flight = generateFlight({ direction: 'departure', cityCode: 'MOW' });
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
const arrivalCity = page.locator('[data-testid="arrival-city"]');
await expect(arrivalCity).toBeVisible();
await expect(arrivalCity).toContainText(flight.arrival.cityName);
});
test('should display scheduled departure time', async ({ page }) => {
const flight = generateFlight({ direction: 'departure', cityCode: 'MOW' });
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
const depTime = page.locator('[data-testid="scheduled-departure-time"]');
await expect(depTime).toBeVisible();
const depTimeText = flight.departure.time.scheduled.slice(11, 16);
await expect(depTime).toContainText(depTimeText);
});
test('should display scheduled arrival time', async ({ page }) => {
const flight = generateFlight({ direction: 'departure', cityCode: 'MOW' });
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
const arrTime = page.locator('[data-testid="scheduled-arrival-time"]');
await expect(arrTime).toBeVisible();
const arrTimeText = flight.arrival.time.scheduled.slice(11, 16);
await expect(arrTime).toContainText(arrTimeText);
});
test('should display flight duration', async ({ page }) => {
const flight = generateFlight({ direction: 'departure', cityCode: 'MOW' });
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
const duration = page.locator('[data-testid="flight-duration"]');
await expect(duration).toBeVisible();
});
test('should display flight status', async ({ page }) => {
const flight = generateFlight({
direction: 'departure',
cityCode: 'MOW',
status: 'scheduled',
});
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
const status = page.locator('[data-testid="flight-status"]');
await expect(status).toBeVisible();
await expect(status).toContainText(flight.status);
});
});
test.describe('Flight Details', () => {
test('should display terminal information', async ({ page }) => {
const flight = generateFlight({
direction: 'departure',
cityCode: 'MOW',
departure: { terminal: 'B' },
});
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
const terminal = page.locator('[data-testid="terminal"]');
await expect(terminal).toBeVisible();
await expect(terminal).toContainText('B');
});
test('should display boarding gate information', async ({ page }) => {
const flight = generateFlight({
direction: 'departure',
cityCode: 'MOW',
status: 'boarding',
boarding: { gate: '11', status: 'Идёт посадка' },
});
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
const boardingGate = page.locator('[data-testid="boarding-gate"]');
await expect(boardingGate).toBeVisible();
await expect(boardingGate).toContainText('11');
});
test('should display baggage belt information', async ({ page }) => {
const flight = generateFlight({
direction: 'arrival',
cityCode: 'MOW',
status: 'arrived',
arrivalInfo: { baggageBelt: '5', transfer: 'Тран' },
});
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
const baggageBelt = page.locator('[data-testid="baggage-belt"]');
await expect(baggageBelt).toBeVisible();
await expect(baggageBelt).toContainText('5');
});
test('should display check-in information', async ({ page }) => {
const flight = generateFlight({
direction: 'departure',
cityCode: 'MOW',
status: 'checkin',
checkin: { status: 'В процессе' },
});
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
const checkinInfo = page.locator('[data-testid="checkin-info"]');
await expect(checkinInfo).toBeVisible();
});
test('should display deplaning information', async ({ page }) => {
const flight = generateFlight({
direction: 'arrival',
cityCode: 'MOW',
status: 'arrived',
deplaning: { status: 'В процессе', transfer: 'Трап' },
});
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
const deplaningInfo = page.locator('[data-testid="deplaning-info"]');
await expect(deplaningInfo).toBeVisible();
});
});
test.describe('Aircraft Information', () => {
test('should display aircraft type', async ({ page }) => {
const flight = generateFlight({ direction: 'departure', cityCode: 'MOW' });
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
const aircraftType = page.locator('[data-testid="aircraft-type"]');
await expect(aircraftType).toBeVisible();
});
test('should display aircraft name', async ({ page }) => {
const flight = generateFlight({
direction: 'departure',
cityCode: 'MOW',
aircraft: { type: 'Airbus A320', name: 'В. Высоцкий' },
});
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
const aircraftName = page.locator('[data-testid="aircraft-name"]');
await expect(aircraftName).toBeVisible();
await expect(aircraftName).toContainText('В. Высоцкий');
});
test('should display seat configuration', async ({ page }) => {
const flight = generateFlight({ direction: 'departure', cityCode: 'MOW' });
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
const seatInfo = page.locator('[data-testid="seat-info"]');
await expect(seatInfo).toBeVisible();
});
});
test.describe('Schedule Information', () => {
test('should display scheduled departure', async ({ page }) => {
const flight = generateFlight({ direction: 'departure', cityCode: 'MOW' });
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
const scheduledDep = page.locator('[data-testid="scheduled-departure"]');
await expect(scheduledDep).toBeVisible();
});
test('should display scheduled arrival', async ({ page }) => {
const flight = generateFlight({ direction: 'departure', cityCode: 'MOW' });
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
const scheduledArr = page.locator('[data-testid="scheduled-arrival"]');
await expect(scheduledArr).toBeVisible();
});
test('should display actual departure when available', async ({ page }) => {
const flight = generateFlight({
direction: 'departure',
cityCode: 'MOW',
status: 'departed',
});
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
const actualDep = page.locator('[data-testid="actual-departure"]');
await expect(actualDep).toBeVisible();
});
test('should display actual arrival when available', async ({ page }) => {
const flight = generateFlight({
direction: 'arrival',
cityCode: 'MOW',
status: 'arrived',
});
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
const actualArr = page.locator('[data-testid="actual-arrival"]');
await expect(actualArr).toBeVisible();
});
test('should display expected arrival when delayed', async ({ page }) => {
const flight = generateFlight({
direction: 'arrival',
cityCode: 'MOW',
status: 'delayed',
});
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
const expectedArr = page.locator('[data-testid="expected-arrival"]');
await expect(expectedArr).toBeVisible();
});
});
test.describe('Error Handling', () => {
test('should handle invalid flight number', async ({ page }) => {
await page.goto(`/ru-ru/SU9999-${today.replace(/-/g, '')}`);
await page.waitForLoadState('networkidle');
const errorState = page.locator('[data-testid="error-state"]');
await expect(errorState).toBeVisible();
});
test('should handle flight not found', async ({ page }) => {
await page.goto(`/ru-ru/SU9999-20260406`);
await page.waitForLoadState('networkidle');
const notFound = page.locator('[data-testid="not-found"]');
await expect(notFound).toBeVisible();
});
test('should handle network error', async ({ page }) => {
await page.route('**/api/flights/**', (route) => {
return route.abort('internetdisconnected');
});
const flight = generateFlight({ direction: 'departure', cityCode: 'MOW' });
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
const networkError = page.locator('[data-testid="network-error"]');
await expect(networkError).toBeVisible();
});
});
test.describe('Navigation', () => {
test('should navigate back to board', async ({ page }) => {
const flight = generateFlight({ direction: 'departure', cityCode: 'MOW' });
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
const backLink = page.locator('[data-testid="back-link"]');
await backLink.click();
await expect(page).toHaveURL(/onlineboard\/departure\/MOW-\d{8}/);
});
test('should navigate to related flights', async ({ page }) => {
const flight = generateFlight({ direction: 'departure', cityCode: 'MOW' });
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
const relatedFlights = page.locator('[data-testid="related-flights"]');
await expect(relatedFlights).toBeVisible();
});
});
test.describe('Accessibility', () => {
test('should have proper ARIA labels', async ({ page }) => {
const flight = generateFlight({ direction: 'departure', cityCode: 'MOW' });
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
const pageContent = page.locator('[data-testid="page-content"]');
await expect(pageContent).toHaveAttribute('role', 'main');
});
test('should be keyboard navigable', async ({ page }) => {
const flight = generateFlight({ direction: 'departure', cityCode: 'MOW' });
const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`;
await page.goto(`/ru-ru/${slug}`);
await page.waitForLoadState('networkidle');
await page.keyboard.press('Tab');
await page.keyboard.press('Tab');
await page.keyboard.press('Tab');
const focusedElement = page.locator(':focus');
await expect(focusedElement).toBeVisible();
});
});
});
@@ -1,310 +0,0 @@
import { test, expect } from '@playwright/test';
import type { Page } from '@playwright/test';
import {
buildOnlineBoardPath,
buildRouteParam,
searchFlightByRoute,
verifyFlightCard,
generateFlight,
generateFlights,
getToday,
getTomorrow,
CITIES,
} from '../support/test-utilities';
const today = getToday();
const tomorrow = getTomorrow();
const dateParam = buildRouteParam('MOW', today);
const tomorrowParam = buildRouteParam('MOW', tomorrow);
// ============================================================================
// Online Board - Route Tests
// ============================================================================
test.describe('Online Board - Route', () => {
test.describe('Page Navigation', () => {
test('should navigate to route board for Moscow to Sochi', async ({ page }) => {
await page.goto(`/ru-ru/onlineboard/route/MOW-AER-${today.replace(/-/g, '')}`);
await page.waitForLoadState('networkidle');
await expect(page).toHaveURL(/route\/MOW-AER-\d{8}/);
await expect(page).toHaveTitle(/Москва - Сочи/);
});
test('should navigate to route board for Saint Petersburg to Moscow', async ({ page }) => {
await page.goto(`/ru-ru/onlineboard/route/LED-MOW-${today.replace(/-/g, '')}`);
await page.waitForLoadState('networkidle');
await expect(page).toHaveURL(/route\/LED-MOW-\d{8}/);
});
test('should navigate to route board for Novosibirsk to Moscow', async ({ page }) => {
await page.goto(`/ru-ru/onlineboard/route/OVB-MOW-${today.replace(/-/g, '')}`);
await page.waitForLoadState('networkidle');
await expect(page).toHaveURL(/route\/OVB-MOW-\d{8}/);
});
});
test.describe('Route Search', () => {
test('should search route by departure and arrival cities', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByRoute(page, 'Moscow', 'Sochi');
const searchResults = page.locator('[data-testid="flight-card"]');
await expect(searchResults).toHaveCount(20);
});
test('should show no results when route not found', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByRoute(page, 'Moscow', 'Unknown City');
const noResults = page.locator('[data-testid="no-results"]');
await expect(noResults).toBeVisible();
await expect(noResults).toContainText('Нет результатов');
});
test('should validate departure city', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByRoute(page, '', 'Sochi');
const error = page.locator('[data-testid="validation-error"]');
await expect(error).toBeVisible();
});
test('should validate arrival city', async ({ page }) => {
await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`);
await page.waitForLoadState('networkidle');
await searchFlightByRoute(page, 'Moscow', '');
const error = page.locator('[data-testid="validation-error"]');
await expect(error).toBeVisible();
});
});
test.describe('Flight Display', () => {
test('should display flights for selected route', async ({ page }) => {
await page.goto(`/ru-ru/onlineboard/route/MOW-AER-${today.replace(/-/g, '')}`);
await page.waitForLoadState('networkidle');
const flightCards = page.locator('[data-testid="flight-card"]');
await expect(flightCards).toHaveCount(20);
});
test('should display route information', async ({ page }) => {
await page.goto(`/ru-ru/onlineboard/route/MOW-AER-${today.replace(/-/g, '')}`);
await page.waitForLoadState('networkidle');
const routeInfo = page.locator('[data-testid="route-info"]');
await expect(routeInfo).toBeVisible();
await expect(routeInfo).toContainText('Москва');
await expect(routeInfo).toContainText('Сочи');
});
test('should display flight count', async ({ page }) => {
await page.goto(`/ru-ru/onlineboard/route/MOW-AER-${today.replace(/-/g, '')}`);
await page.waitForLoadState('networkidle');
const flightCount = page.locator('[data-testid="flight-count"]');
await expect(flightCount).toBeVisible();
});
});
test.describe('Date Navigation', () => {
test('should navigate to tomorrow', async ({ page }) => {
await page.goto(`/ru-ru/onlineboard/route/MOW-AER-${today.replace(/-/g, '')}`);
await page.waitForLoadState('networkidle');
const dateTab = page.locator(`[data-testid="date-tab-${tomorrow.replace(/-/g, '')}"]`);
if ((await dateTab.count()) > 0) {
await dateTab.click();
await page.waitForLoadState('networkidle');
await expect(page).toHaveURL(/route\/MOW-AER-\d{8}/);
}
});
test('should display correct date in title', async ({ page }) => {
await page.goto(`/ru-ru/onlineboard/route/MOW-AER-${today.replace(/-/g, '')}`);
await page.waitForLoadState('networkidle');
const dateText = page.locator('[data-testid="board-date"]');
await expect(dateText).toBeVisible();
});
});
test.describe('Filtering', () => {
test('should filter by status', async ({ page }) => {
await page.goto(`/ru-ru/onlineboard/route/MOW-AER-${today.replace(/-/g, '')}`);
await page.waitForLoadState('networkidle');
const statusFilter = page.locator('[data-testid="status-filter"]');
await statusFilter.click();
const scheduledOption = page.locator('[data-testid="filter-option-scheduled"]');
await scheduledOption.click();
const filteredFlights = page.locator('[data-testid="flight-card"]');
await expect(filteredFlights).toHaveCount(20);
});
test('should filter by airline', async ({ page }) => {
await page.goto(`/ru-ru/onlineboard/route/MOW-AER-${today.replace(/-/g, '')}`);
await page.waitForLoadState('networkidle');
const airlineFilter = page.locator('[data-testid="airline-filter"]');
await airlineFilter.click();
const aeroflotOption = page.locator('[data-testid="filter-option-SU"]');
await aeroflotOption.click();
const filteredFlights = page.locator('[data-testid="flight-card"]');
await expect(filteredFlights).toHaveCount(20);
});
test('should filter by time range', async ({ page }) => {
await page.goto(`/ru-ru/onlineboard/route/MOW-AER-${today.replace(/-/g, '')}`);
await page.waitForLoadState('networkidle');
const timeFilter = page.locator('[data-testid="time-filter"]');
await timeFilter.click();
const timeOption = page.locator('[data-testid="filter-option-morning"]');
await timeOption.click();
const filteredFlights = page.locator('[data-testid="flight-card"]');
await expect(filteredFlights).toHaveCount(20);
});
});
test.describe('Flight Card', () => {
test('should display flight number', async ({ page }) => {
await page.goto(`/ru-ru/onlineboard/route/MOW-AER-${today.replace(/-/g, '')}`);
await page.waitForLoadState('networkidle');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toBeVisible();
const flightNumber = flightCard.locator('[data-testid="flight-number"]');
await expect(flightNumber).toBeVisible();
});
test('should display airline name', async ({ page }) => {
await page.goto(`/ru-ru/onlineboard/route/MOW-AER-${today.replace(/-/g, '')}`);
await page.waitForLoadState('networkidle');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toBeVisible();
const airlineName = flightCard.locator('[data-testid="airline-name"]');
await expect(airlineName).toBeVisible();
});
test('should display departure city', async ({ page }) => {
await page.goto(`/ru-ru/onlineboard/route/MOW-AER-${today.replace(/-/g, '')}`);
await page.waitForLoadState('networkidle');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toBeVisible();
const departureCity = flightCard.locator('[data-testid="departure-city"]');
await expect(departureCity).toBeVisible();
});
test('should display arrival city', async ({ page }) => {
await page.goto(`/ru-ru/onlineboard/route/MOW-AER-${today.replace(/-/g, '')}`);
await page.waitForLoadState('networkidle');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toBeVisible();
const arrivalCity = flightCard.locator('[data-testid="arrival-city"]');
await expect(arrivalCity).toBeVisible();
});
test('should display scheduled departure time', async ({ page }) => {
await page.goto(`/ru-ru/onlineboard/route/MOW-AER-${today.replace(/-/g, '')}`);
await page.waitForLoadState('networkidle');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toBeVisible();
const depTime = flightCard.locator('[data-testid="scheduled-departure-time"]');
await expect(depTime).toBeVisible();
});
test('should display scheduled arrival time', async ({ page }) => {
await page.goto(`/ru-ru/onlineboard/route/MOW-AER-${today.replace(/-/g, '')}`);
await page.waitForLoadState('networkidle');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toBeVisible();
const arrTime = flightCard.locator('[data-testid="scheduled-arrival-time"]');
await expect(arrTime).toBeVisible();
});
test('should display flight duration', async ({ page }) => {
await page.goto(`/ru-ru/onlineboard/route/MOW-AER-${today.replace(/-/g, '')}`);
await page.waitForLoadState('networkidle');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toBeVisible();
const duration = flightCard.locator('[data-testid="flight-duration"]');
await expect(duration).toBeVisible();
});
});
test.describe('Error Handling', () => {
test('should handle invalid route parameters', async ({ page }) => {
await page.goto(`/ru-ru/onlineboard/route/XXX-YYY-${today.replace(/-/g, '')}`);
await page.waitForLoadState('networkidle');
const errorState = page.locator('[data-testid="error-state"]');
await expect(errorState).toBeVisible();
});
test('should handle network error', async ({ page }) => {
await page.route('**/api/flights/**', (route) => {
return route.abort('internetdisconnected');
});
await page.goto(`/ru-ru/onlineboard/route/MOW-AER-${today.replace(/-/g, '')}`);
await page.waitForLoadState('networkidle');
const networkError = page.locator('[data-testid="network-error"]');
await expect(networkError).toBeVisible();
});
});
test.describe('Accessibility', () => {
test('should have proper ARIA labels', async ({ page }) => {
await page.goto(`/ru-ru/onlineboard/route/MOW-AER-${today.replace(/-/g, '')}`);
await page.waitForLoadState('networkidle');
const flightCard = page.locator('[data-testid="flight-card"]').first();
await expect(flightCard).toHaveAttribute('role', 'article');
});
test('should be keyboard navigable', async ({ page }) => {
await page.goto(`/ru-ru/onlineboard/route/MOW-AER-${today.replace(/-/g, '')}`);
await page.waitForLoadState('networkidle');
await page.keyboard.press('Tab');
await page.keyboard.press('Tab');
await page.keyboard.press('Tab');
const focusedElement = page.locator(':focus');
await expect(focusedElement).toBeVisible();
});
});
});
@@ -1,301 +0,0 @@
import { test, expect } from '@playwright/test';
import type { Page } from '@playwright/test';
import {
buildRouteParam,
generateDestination,
generateDestinations,
getToday,
getTomorrow,
CITIES,
} from '@e2e/support/test-utilities';
const today = getToday();
const tomorrow = getTomorrow();
// ============================================================================
// Popular Requests Tests
// ============================================================================
test.describe('Popular Requests', () => {
test.describe('Page Navigation', () => {
test('should display popular requests section', async ({ page }) => {
await page.goto(`/ru-ru/onlineboard/departure/MOW-${buildRouteParam('MOW', today)}`);
await page.waitForLoadState('networkidle');
const popularRequests = page.locator('[data-testid="popular-requests"]');
await expect(popularRequests).toBeVisible();
});
test('should display popular requests title', async ({ page }) => {
await page.goto(`/ru-ru/onlineboard/departure/MOW-${buildRouteParam('MOW', today)}`);
await page.waitForLoadState('networkidle');
const title = page.locator('[data-testid="popular-requests-title"]');
await expect(title).toBeVisible();
await expect(title).toContainText('Популярные направления');
});
});
test.describe('Request Display', () => {
test('should display departure city', async ({ page }) => {
await page.goto(`/ru-ru/onlineboard/departure/MOW-${buildRouteParam('MOW', today)}`);
await page.waitForLoadState('networkidle');
const request = page.locator('[data-testid="popular-request"]').first();
await expect(request).toBeVisible();
const departureCity = request.locator('[data-testid="request-departure-city"]');
await expect(departureCity).toBeVisible();
});
test('should display arrival city', async ({ page }) => {
await page.goto(`/ru-ru/onlineboard/departure/MOW-${buildRouteParam('MOW', today)}`);
await page.waitForLoadState('networkidle');
const request = page.locator('[data-testid="popular-request"]').first();
await expect(request).toBeVisible();
const arrivalCity = request.locator('[data-testid="request-arrival-city"]');
await expect(arrivalCity).toBeVisible();
});
test('should display flight count', async ({ page }) => {
await page.goto(`/ru-ru/onlineboard/departure/MOW-${buildRouteParam('MOW', today)}`);
await page.waitForLoadState('networkidle');
const request = page.locator('[data-testid="popular-request"]').first();
await expect(request).toBeVisible();
const flightCount = request.locator('[data-testid="request-flight-count"]');
await expect(flightCount).toBeVisible();
});
test('should display date range', async ({ page }) => {
await page.goto(`/ru-ru/onlineboard/departure/MOW-${buildRouteParam('MOW', today)}`);
await page.waitForLoadState('networkidle');
const request = page.locator('[data-testid="popular-request"]').first();
await expect(request).toBeVisible();
const dateRange = request.locator('[data-testid="request-date-range"]');
await expect(dateRange).toBeVisible();
});
});
test.describe('Request Interaction', () => {
test('should navigate to flight board on click', async ({ page }) => {
await page.goto(`/ru-ru/onlineboard/departure/MOW-${buildRouteParam('MOW', today)}`);
await page.waitForLoadState('networkidle');
const request = page.locator('[data-testid="popular-request"]').first();
await request.click();
await expect(page).toHaveURL(/onlineboard\/departure\/MOW-\d{8}/);
});
test('should navigate to flight board with correct city', async ({ page }) => {
await page.goto(`/ru-ru/onlineboard/departure/MOW-${buildRouteParam('MOW', today)}`);
await page.waitForLoadState('networkidle');
const request = page.locator('[data-testid="popular-request"]').first();
await request.click();
await expect(page).toHaveURL(/onlineboard\/departure\/[A-Z]{3}-\d{8}/);
});
test('should open flight board for arrival direction', async ({ page }) => {
await page.goto(`/ru-ru/onlineboard/departure/MOW-${buildRouteParam('MOW', today)}`);
await page.waitForLoadState('networkidle');
const request = page.locator('[data-testid="popular-request"]').first();
await request.click();
await expect(page).toHaveURL(/onlineboard\/arrival\/[A-Z]{3}-\d{8}/);
});
});
test.describe('Request Sorting', () => {
test('should sort by flight count', async ({ page }) => {
await page.goto(`/ru-ru/onlineboard/departure/MOW-${buildRouteParam('MOW', today)}`);
await page.waitForLoadState('networkidle');
const sortButton = page.locator('[data-testid="sort-button"]');
await sortButton.click();
const sortOption = page.locator('[data-testid="sort-option-flight-count"]');
await sortOption.click();
const requests = page.locator('[data-testid="popular-request"]');
const count1 = await requests
.nth(0)
.locator('[data-testid="request-flight-count"]')
.textContent();
const count2 = await requests
.nth(1)
.locator('[data-testid="request-flight-count"]')
.textContent();
if (count1 && count2) {
expect(parseInt(count1.replace(/\D/g, ''))).toBeGreaterThanOrEqual(
parseInt(count2.replace(/\D/g, '')),
);
}
});
test('should sort by date', async ({ page }) => {
await page.goto(`/ru-ru/onlineboard/departure/MOW-${buildRouteParam('MOW', today)}`);
await page.waitForLoadState('networkidle');
const sortButton = page.locator('[data-testid="sort-button"]');
await sortButton.click();
const sortOption = page.locator('[data-testid="sort-option-date"]');
await sortOption.click();
const requests = page.locator('[data-testid="popular-request"]');
await expect(requests).toHaveCount(20);
});
});
test.describe('Request Filtering', () => {
test('should filter by departure city', async ({ page }) => {
await page.goto(`/ru-ru/onlineboard/departure/MOW-${buildRouteParam('MOW', today)}`);
await page.waitForLoadState('networkidle');
const filterInput = page.locator('[data-testid="filter-input"]');
await filterInput.fill('Moscow');
const requests = page.locator('[data-testid="popular-request"]');
await expect(requests).toHaveCount(20);
});
test('should clear filter', async ({ page }) => {
await page.goto(`/ru-ru/onlineboard/departure/MOW-${buildRouteParam('MOW', today)}`);
await page.waitForLoadState('networkidle');
const filterInput = page.locator('[data-testid="filter-input"]');
await filterInput.fill('Moscow');
const clearButton = page.locator('[data-testid="clear-filter"]');
await clearButton.click();
const requests = page.locator('[data-testid="popular-request"]');
await expect(requests).toHaveCount(20);
});
});
test.describe('Request Pagination', () => {
test('should display pagination controls', async ({ page }) => {
await page.goto(`/ru-ru/onlineboard/departure/MOW-${buildRouteParam('MOW', today)}`);
await page.waitForLoadState('networkidle');
const pagination = page.locator('[data-testid="pagination"]');
await expect(pagination).toBeVisible();
});
test('should navigate to next page', async ({ page }) => {
await page.goto(`/ru-ru/onlineboard/departure/MOW-${buildRouteParam('MOW', today)}`);
await page.waitForLoadState('networkidle');
const nextButton = page.locator('[data-testid="pagination-next"]');
await nextButton.click();
const requests = page.locator('[data-testid="popular-request"]');
await expect(requests).toHaveCount(20);
});
test('should navigate to previous page', async ({ page }) => {
await page.goto(`/ru-ru/onlineboard/departure/MOW-${buildRouteParam('MOW', today)}`);
await page.waitForLoadState('networkidle');
const nextButton = page.locator('[data-testid="pagination-next"]');
await nextButton.click();
const prevButton = page.locator('[data-testid="pagination-prev"]');
await prevButton.click();
const requests = page.locator('[data-testid="popular-request"]');
await expect(requests).toHaveCount(20);
});
});
test.describe('Error Handling', () => {
test('should handle network error', async ({ page }) => {
await page.route('**/api/popular-requests/**', (route) => {
return route.abort('internetdisconnected');
});
await page.goto(`/ru-ru/onlineboard/departure/MOW-${buildRouteParam('MOW', today)}`);
await page.waitForLoadState('networkidle');
const networkError = page.locator('[data-testid="network-error"]');
await expect(networkError).toBeVisible();
});
test('should handle empty results', async ({ page }) => {
await page.route('**/api/popular-requests/**', (route) => {
return route.fulfill({
status: 200,
json: { requests: [], total: 0 },
});
});
await page.goto(`/ru-ru/onlineboard/departure/MOW-${buildRouteParam('MOW', today)}`);
await page.waitForLoadState('networkidle');
const noResults = page.locator('[data-testid="no-results"]');
await expect(noResults).toBeVisible();
});
});
test.describe('Accessibility', () => {
test('should have proper ARIA labels', async ({ page }) => {
await page.goto(`/ru-ru/onlineboard/departure/MOW-${buildRouteParam('MOW', today)}`);
await page.waitForLoadState('networkidle');
const popularRequests = page.locator('[data-testid="popular-requests"]');
await expect(popularRequests).toHaveAttribute('role', 'region');
});
test('should be keyboard navigable', async ({ page }) => {
await page.goto(`/ru-ru/onlineboard/departure/MOW-${buildRouteParam('MOW', today)}`);
await page.waitForLoadState('networkidle');
await page.keyboard.press('Tab');
await page.keyboard.press('Tab');
await page.keyboard.press('Tab');
const focusedElement = page.locator(':focus');
await expect(focusedElement).toBeVisible();
});
});
test.describe('Responsive Design', () => {
test('should be responsive on mobile', async ({ page }) => {
await page.setViewportSize({ width: 375, height: 667 });
await page.goto(`/ru-ru/onlineboard/departure/MOW-${buildRouteParam('MOW', today)}`);
await page.waitForLoadState('networkidle');
const popularRequests = page.locator('[data-testid="popular-requests"]');
await expect(popularRequests).toBeVisible();
});
test('should be responsive on tablet', async ({ page }) => {
await page.setViewportSize({ width: 768, height: 1024 });
await page.goto(`/ru-ru/onlineboard/departure/MOW-${buildRouteParam('MOW', today)}`);
await page.waitForLoadState('networkidle');
const popularRequests = page.locator('[data-testid="popular-requests"]');
await expect(popularRequests).toBeVisible();
});
test('should be responsive on desktop', async ({ page }) => {
await page.setViewportSize({ width: 1920, height: 1080 });
await page.goto(`/ru-ru/onlineboard/departure/MOW-${buildRouteParam('MOW', today)}`);
await page.waitForLoadState('networkidle');
const popularRequests = page.locator('[data-testid="popular-requests"]');
await expect(popularRequests).toBeVisible();
});
});
});
@@ -1,427 +0,0 @@
import { test, expect } from '@playwright/test';
import type { Page } from '@playwright/test';
import {
buildSchedulePath,
buildRouteParam,
generateScheduleEntry,
generateScheduleEntries,
getToday,
getTomorrow,
CITIES,
} from '../support/test-utilities';
const today = getToday();
const tomorrow = getTomorrow();
const dateFrom = today;
const dateTo = tomorrow;
// ============================================================================
// Schedule Search Tests
// ============================================================================
test.describe('Schedule Search', () => {
test.describe('Page Navigation', () => {
test('should navigate to schedule page', async ({ page }) => {
await page.goto(`/ru-ru${buildSchedulePath()}`);
await page.waitForLoadState('networkidle');
await expect(page).toHaveURL(/schedule/);
await expect(page).toHaveTitle(/Расписание/);
});
test('should navigate to schedule with pre-filled search', async ({ page }) => {
await page.goto(`/ru-ru/schedule?from=MOW&to=AER&dateFrom=${dateFrom}&dateTo=${dateTo}`);
await page.waitForLoadState('networkidle');
await expect(page).toHaveURL(/schedule/);
});
});
test.describe('Search Form', () => {
test('should display search form', async ({ page }) => {
await page.goto(`/ru-ru${buildSchedulePath()}`);
await page.waitForLoadState('networkidle');
const form = page.locator('[data-testid="schedule-search-form"]');
await expect(form).toBeVisible();
});
test('should display departure city input', async ({ page }) => {
await page.goto(`/ru-ru${buildSchedulePath()}`);
await page.waitForLoadState('networkidle');
const departureInput = page.locator('[data-testid="departure-city-input"]');
await expect(departureInput).toBeVisible();
});
test('should display arrival city input', async ({ page }) => {
await page.goto(`/ru-ru${buildSchedulePath()}`);
await page.waitForLoadState('networkidle');
const arrivalInput = page.locator('[data-testid="arrival-city-input"]');
await expect(arrivalInput).toBeVisible();
});
test('should display date range inputs', async ({ page }) => {
await page.goto(`/ru-ru${buildSchedulePath()}`);
await page.waitForLoadState('networkidle');
const dateFromInput = page.locator('[data-testid="date-from-input"]');
await expect(dateFromInput).toBeVisible();
const dateToInput = page.locator('[data-testid="date-to-input"]');
await expect(dateToInput).toBeVisible();
});
test('should display search button', async ({ page }) => {
await page.goto(`/ru-ru${buildSchedulePath()}`);
await page.waitForLoadState('networkidle');
const searchButton = page.locator('[data-testid="search-button"]');
await expect(searchButton).toBeVisible();
});
});
test.describe('Search Functionality', () => {
test('should search by departure and arrival cities', async ({ page }) => {
await page.goto(`/ru-ru${buildSchedulePath()}`);
await page.waitForLoadState('networkidle');
const departureInput = page.locator('[data-testid="departure-city-input"]');
await departureInput.fill('Moscow');
const arrivalInput = page.locator('[data-testid="arrival-city-input"]');
await arrivalInput.fill('Sochi');
const searchButton = page.locator('[data-testid="search-button"]');
await searchButton.click();
await page.waitForLoadState('networkidle');
const results = page.locator('[data-testid="schedule-entry"]');
await expect(results).toHaveCount(50);
});
test('should search with date range', async ({ page }) => {
await page.goto(`/ru-ru${buildSchedulePath()}`);
await page.waitForLoadState('networkidle');
const departureInput = page.locator('[data-testid="departure-city-input"]');
await departureInput.fill('Moscow');
const arrivalInput = page.locator('[data-testid="arrival-city-input"]');
await arrivalInput.fill('Sochi');
const dateFromInput = page.locator('[data-testid="date-from-input"]');
await dateFromInput.fill(dateFrom);
const dateToInput = page.locator('[data-testid="date-to-input"]');
await dateToInput.fill(dateTo);
const searchButton = page.locator('[data-testid="search-button"]');
await searchButton.click();
await page.waitForLoadState('networkidle');
const results = page.locator('[data-testid="schedule-entry"]');
await expect(results).toHaveCount(50);
});
test('should show validation error for missing departure city', async ({ page }) => {
await page.goto(`/ru-ru${buildSchedulePath()}`);
await page.waitForLoadState('networkidle');
const arrivalInput = page.locator('[data-testid="arrival-city-input"]');
await arrivalInput.fill('Sochi');
const searchButton = page.locator('[data-testid="search-button"]');
await searchButton.click();
const error = page.locator('[data-testid="validation-error"]');
await expect(error).toBeVisible();
});
test('should show validation error for missing arrival city', async ({ page }) => {
await page.goto(`/ru-ru${buildSchedulePath()}`);
await page.waitForLoadState('networkidle');
const departureInput = page.locator('[data-testid="departure-city-input"]');
await departureInput.fill('Moscow');
const searchButton = page.locator('[data-testid="search-button"]');
await searchButton.click();
const error = page.locator('[data-testid="validation-error"]');
await expect(error).toBeVisible();
});
test('should show no results when no schedules found', async ({ page }) => {
await page.goto(`/ru-ru${buildSchedulePath()}`);
await page.waitForLoadState('networkidle');
const departureInput = page.locator('[data-testid="departure-city-input"]');
await departureInput.fill('Unknown City');
const arrivalInput = page.locator('[data-testid="arrival-city-input"]');
await arrivalInput.fill('Unknown City');
const searchButton = page.locator('[data-testid="search-button"]');
await searchButton.click();
await page.waitForLoadState('networkidle');
const noResults = page.locator('[data-testid="no-results"]');
await expect(noResults).toBeVisible();
await expect(noResults).toContainText('Нет результатов');
});
});
test.describe('Schedule Entry Display', () => {
test('should display flight number', async ({ page }) => {
await page.goto(`/ru-ru${buildSchedulePath()}`);
await page.waitForLoadState('networkidle');
const departureInput = page.locator('[data-testid="departure-city-input"]');
await departureInput.fill('Moscow');
const arrivalInput = page.locator('[data-testid="arrival-city-input"]');
await arrivalInput.fill('Sochi');
const searchButton = page.locator('[data-testid="search-button"]');
await searchButton.click();
await page.waitForLoadState('networkidle');
const entry = page.locator('[data-testid="schedule-entry"]').first();
await expect(entry).toBeVisible();
const flightNumber = entry.locator('[data-testid="flight-number"]');
await expect(flightNumber).toBeVisible();
});
test('should display airline name', async ({ page }) => {
await page.goto(`/ru-ru${buildSchedulePath()}`);
await page.waitForLoadState('networkidle');
const departureInput = page.locator('[data-testid="departure-city-input"]');
await departureInput.fill('Moscow');
const arrivalInput = page.locator('[data-testid="arrival-city-input"]');
await arrivalInput.fill('Sochi');
const searchButton = page.locator('[data-testid="search-button"]');
await searchButton.click();
await page.waitForLoadState('networkidle');
const entry = page.locator('[data-testid="schedule-entry"]').first();
await expect(entry).toBeVisible();
const airlineName = entry.locator('[data-testid="airline-name"]');
await expect(airlineName).toBeVisible();
});
test('should display aircraft type', async ({ page }) => {
await page.goto(`/ru-ru${buildSchedulePath()}`);
await page.waitForLoadState('networkidle');
const departureInput = page.locator('[data-testid="departure-city-input"]');
await departureInput.fill('Moscow');
const arrivalInput = page.locator('[data-testid="arrival-city-input"]');
await arrivalInput.fill('Sochi');
const searchButton = page.locator('[data-testid="search-button"]');
await searchButton.click();
await page.waitForLoadState('networkidle');
const entry = page.locator('[data-testid="schedule-entry"]').first();
await expect(entry).toBeVisible();
const aircraftType = entry.locator('[data-testid="aircraft-type"]');
await expect(aircraftType).toBeVisible();
});
test('should display departure time', async ({ page }) => {
await page.goto(`/ru-ru${buildSchedulePath()}`);
await page.waitForLoadState('networkidle');
const departureInput = page.locator('[data-testid="departure-city-input"]');
await departureInput.fill('Moscow');
const arrivalInput = page.locator('[data-testid="arrival-city-input"]');
await arrivalInput.fill('Sochi');
const searchButton = page.locator('[data-testid="search-button"]');
await searchButton.click();
await page.waitForLoadState('networkidle');
const entry = page.locator('[data-testid="schedule-entry"]').first();
await expect(entry).toBeVisible();
const departureTime = entry.locator('[data-testid="departure-time"]');
await expect(departureTime).toBeVisible();
});
test('should display arrival time', async ({ page }) => {
await page.goto(`/ru-ru${buildSchedulePath()}`);
await page.waitForLoadState('networkidle');
const departureInput = page.locator('[data-testid="departure-city-input"]');
await departureInput.fill('Moscow');
const arrivalInput = page.locator('[data-testid="arrival-city-input"]');
await arrivalInput.fill('Sochi');
const searchButton = page.locator('[data-testid="search-button"]');
await searchButton.click();
await page.waitForLoadState('networkidle');
const entry = page.locator('[data-testid="schedule-entry"]').first();
await expect(entry).toBeVisible();
const arrivalTime = entry.locator('[data-testid="arrival-time"]');
await expect(arrivalTime).toBeVisible();
});
test('should display days of week', async ({ page }) => {
await page.goto(`/ru-ru${buildSchedulePath()}`);
await page.waitForLoadState('networkidle');
const departureInput = page.locator('[data-testid="departure-city-input"]');
await departureInput.fill('Moscow');
const arrivalInput = page.locator('[data-testid="arrival-city-input"]');
await arrivalInput.fill('Sochi');
const searchButton = page.locator('[data-testid="search-button"]');
await searchButton.click();
await page.waitForLoadState('networkidle');
const entry = page.locator('[data-testid="schedule-entry"]').first();
await expect(entry).toBeVisible();
const daysOfWeek = entry.locator('[data-testid="days-of-week"]');
await expect(daysOfWeek).toBeVisible();
});
test('should display effective date range', async ({ page }) => {
await page.goto(`/ru-ru${buildSchedulePath()}`);
await page.waitForLoadState('networkidle');
const departureInput = page.locator('[data-testid="departure-city-input"]');
await departureInput.fill('Moscow');
const arrivalInput = page.locator('[data-testid="arrival-city-input"]');
await arrivalInput.fill('Sochi');
const searchButton = page.locator('[data-testid="search-button"]');
await searchButton.click();
await page.waitForLoadState('networkidle');
const entry = page.locator('[data-testid="schedule-entry"]').first();
await expect(entry).toBeVisible();
const dateRange = entry.locator('[data-testid="date-range"]');
await expect(dateRange).toBeVisible();
});
});
test.describe('Filtering', () => {
test('should filter by direct flights only', async ({ page }) => {
await page.goto(`/ru-ru${buildSchedulePath()}`);
await page.waitForLoadState('networkidle');
const directFilter = page.locator('[data-testid="direct-filter"]');
await directFilter.click();
const departureInput = page.locator('[data-testid="departure-city-input"]');
await departureInput.fill('Moscow');
const arrivalInput = page.locator('[data-testid="arrival-city-input"]');
await arrivalInput.fill('Sochi');
const searchButton = page.locator('[data-testid="search-button"]');
await searchButton.click();
await page.waitForLoadState('networkidle');
const entries = page.locator('[data-testid="schedule-entry"]');
await expect(entries).toHaveCount(50);
});
test('should filter by airline', async ({ page }) => {
await page.goto(`/ru-ru${buildSchedulePath()}`);
await page.waitForLoadState('networkidle');
const airlineFilter = page.locator('[data-testid="airline-filter"]');
await airlineFilter.click();
const aeroflotOption = page.locator('[data-testid="filter-option-SU"]');
await aeroflotOption.click();
const departureInput = page.locator('[data-testid="departure-city-input"]');
await departureInput.fill('Moscow');
const arrivalInput = page.locator('[data-testid="arrival-city-input"]');
await arrivalInput.fill('Sochi');
const searchButton = page.locator('[data-testid="search-button"]');
await searchButton.click();
await page.waitForLoadState('networkidle');
const entries = page.locator('[data-testid="schedule-entry"]');
await expect(entries).toHaveCount(50);
});
});
test.describe('Error Handling', () => {
test('should handle invalid date format', async ({ page }) => {
await page.goto(`/ru-ru${buildSchedulePath()}`);
await page.waitForLoadState('networkidle');
const departureInput = page.locator('[data-testid="departure-city-input"]');
await departureInput.fill('Moscow');
const arrivalInput = page.locator('[data-testid="arrival-city-input"]');
await arrivalInput.fill('Sochi');
const dateFromInput = page.locator('[data-testid="date-from-input"]');
await dateFromInput.fill('invalid-date');
const searchButton = page.locator('[data-testid="search-button"]');
await searchButton.click();
const error = page.locator('[data-testid="validation-error"]');
await expect(error).toBeVisible();
});
test('should handle network error', async ({ page }) => {
await page.route('**/api/schedule/**', (route) => {
return route.abort('internetdisconnected');
});
await page.goto(`/ru-ru${buildSchedulePath()}`);
await page.waitForLoadState('networkidle');
const networkError = page.locator('[data-testid="network-error"]');
await expect(networkError).toBeVisible();
});
});
test.describe('Accessibility', () => {
test('should have proper ARIA labels', async ({ page }) => {
await page.goto(`/ru-ru${buildSchedulePath()}`);
await page.waitForLoadState('networkidle');
const form = page.locator('[data-testid="schedule-search-form"]');
await expect(form).toHaveAttribute('role', 'form');
});
test('should be keyboard navigable', async ({ page }) => {
await page.goto(`/ru-ru${buildSchedulePath()}`);
await page.waitForLoadState('networkidle');
await page.keyboard.press('Tab');
await page.keyboard.press('Tab');
await page.keyboard.press('Tab');
const focusedElement = page.locator(':focus');
await expect(focusedElement).toBeVisible();
});
});
});
-71
View File
@@ -1,71 +0,0 @@
import { test, expect } from '@playwright/test';
test.describe('Navigation & Language (US-1, US-2) - React ru-ru', () => {
test.beforeEach(async ({ page }) => {
await page.goto('http://localhost:3002/ru-ru/onlineboard');
});
test('US-1: Tab navigation - switch between all tabs', async ({ page }) => {
// Verify Online Board tab is active
const onlineTab = page.locator('[data-testid="nav-onlineboard-tab"]');
await expect(onlineTab).toHaveClass(/active/);
// Click Schedule tab
const scheduleTab = page.locator('[data-testid="nav-schedule-tab"]');
await scheduleTab.click();
await expect(page).toHaveURL(/\/ru-ru\/schedule/);
await expect(scheduleTab).toHaveClass(/active/);
// Click Flights Map tab
const mapTab = page.locator('[data-testid="nav-flights-map-tab"]');
if (await mapTab.isVisible()) {
await mapTab.click();
await expect(page).toHaveURL(/\/ru-ru\/flights-map/);
await expect(mapTab).toHaveClass(/active/);
}
// Navigate back to Online Board
await onlineTab.click();
await expect(page).toHaveURL(/\/ru-ru\/onlineboard/);
await expect(onlineTab).toHaveClass(/active/);
});
test('US-2: Language switching - ru-ru to en-us to ru-ru', async ({ page }) => {
// Verify current language is Russian
await expect(page.locator('[data-testid="nav-onlineboard-tab"]')).toContainText('Онлайн-Табло');
// Click locale switcher dropdown
const switcher = page.locator('[data-testid="layout-locale-switcher"]');
await switcher.click();
// Select English (en-us)
await page.locator('text=English').first().click();
await expect(page).toHaveURL(/\/en-us\/onlineboard/);
// Verify UI is now in English
await expect(page.locator('[data-testid="nav-onlineboard-tab"]')).toContainText('Online Board');
// Click locale switcher dropdown again
await switcher.click();
// Switch back to Russian
await page.locator('text=Русский').first().click();
await expect(page).toHaveURL(/\/ru-ru\/onlineboard/);
await expect(page.locator('[data-testid="nav-onlineboard-tab"]')).toContainText('Онлайн-Табло');
});
test('US-2: Language switching - preserve page context', async ({ page }) => {
// Navigate to Schedule
await page.locator('[data-testid="nav-schedule-tab"]').click();
await expect(page).toHaveURL(/\/ru-ru\/schedule/);
// Switch to English via dropdown
const switcher = page.locator('[data-testid="layout-locale-switcher"]');
await switcher.click();
await page.locator('text=English').first().click();
// Should still be on Schedule page (not Online Board)
await expect(page).toHaveURL(/\/en-us\/schedule/);
await expect(page.locator('[data-testid="nav-schedule-tab"]')).toContainText('Schedule');
});
});
@@ -1,83 +0,0 @@
import { test, expect } from '@playwright/test';
test.describe('Popular Requests (US-7)', () => {
test.beforeEach(async ({ page }) => {
await page.goto('http://localhost:3000/ru-ru/onlineboard');
await page.waitForLoadState('networkidle');
});
test('should display popular requests section', async ({ page }) => {
const section = page.locator('[data-testid="popular-requests"]');
const exists = await section.isVisible().catch(() => false);
// Section may not always be visible depending on data availability
expect(typeof exists).toBe('boolean');
});
test('should show popular route cards', async ({ page }) => {
const routes = page.locator('[data-testid="popular-request-card"]');
const count = await routes.count();
// Component may have 0 or more cards
expect(count >= 0).toBe(true);
});
test('should handle click on popular route', async ({ page }) => {
const firstRoute = page.locator('[data-testid="popular-request-card"]').first();
const exists = await firstRoute.isVisible().catch(() => false);
if (exists) {
await firstRoute.click();
// Should trigger search or navigation
await page.waitForLoadState('networkidle');
}
// Test completes successfully if no errors occur
expect(true).toBe(true);
});
test('should display route information', async ({ page }) => {
const routes = page.locator('[data-testid="popular-request-card"]');
const count = await routes.count();
expect(count >= 0).toBe(true);
});
test('should be responsive on mobile', async ({ page }) => {
await page.setViewportSize({ width: 375, height: 667 });
await page.goto('http://localhost:3000/ru-ru/onlineboard');
await page.waitForLoadState('networkidle');
const section = page.locator('[data-testid="popular-requests"]');
const exists = await section.isVisible().catch(() => false);
expect(typeof exists).toBe('boolean');
});
test('should be responsive on tablet', async ({ page }) => {
await page.setViewportSize({ width: 768, height: 1024 });
await page.goto('http://localhost:3000/ru-ru/onlineboard');
await page.waitForLoadState('networkidle');
const routes = page.locator('[data-testid="popular-request-card"]');
const count = await routes.count();
expect(count >= 0).toBe(true);
});
test('should render without errors', async ({ page }) => {
// Check for JavaScript errors
const errors: string[] = [];
page.on('console', (msg) => {
if (msg.type() === 'error') {
errors.push(msg.text());
}
});
await page.goto('http://localhost:3000/ru-ru/onlineboard');
await page.waitForLoadState('networkidle');
// Component should render without throwing errors
expect(errors.length).toBe(0);
});
});
-146
View File
@@ -1,146 +0,0 @@
import { test, expect } from '@playwright/test';
const VIEWPORTS = [
{ name: 'mobile', width: 375, height: 667 },
{ name: 'tablet', width: 768, height: 1024 },
{ name: 'desktop', width: 1920, height: 1080 },
];
const PAGES = ['/ru-ru/onlineboard', '/ru-ru/schedule', '/ru-ru/flights-map'];
test.describe('Responsive Design (US-10)', () => {
// Test all pages at all breakpoints
PAGES.forEach((page) => {
VIEWPORTS.forEach(({ name, width, height }) => {
test(`${page} should be responsive on ${name} (${width}x${height})`, async ({
page: browserPage,
baseURL,
}) => {
await browserPage.setViewportSize({ width, height });
await browserPage.goto(`${baseURL}${page}`);
// Wait for page to load
await browserPage.waitForLoadState('networkidle');
// Check for layout shift or overflow
const bodyWidth = await browserPage.evaluate(() => document.body.scrollWidth);
const viewportWidth = width;
// Body should not be wider than viewport (allowing 1px tolerance)
expect(bodyWidth).toBeLessThanOrEqual(viewportWidth + 1);
});
});
});
test('should display navigation bar on mobile', async ({ page, baseURL }) => {
await page.setViewportSize({ width: 375, height: 667 });
await page.goto(`${baseURL}/ru-ru/onlineboard`);
const tabNavigation = page.locator('[data-testid="nav"]');
await expect(tabNavigation).toBeVisible();
});
test('should display hamburger menu on mobile if needed', async ({ page, baseURL }) => {
await page.setViewportSize({ width: 375, height: 667 });
await page.goto(`${baseURL}/ru-ru/onlineboard`);
const menu = page.locator('[data-testid="mobile-menu"]');
// Menu may or may not exist, but if it exists, should be visible
if ((await menu.count()) > 0) {
await expect(menu).toBeVisible();
}
});
test('should stack layout vertically on mobile', async ({ page, baseURL }) => {
await page.setViewportSize({ width: 375, height: 667 });
await page.goto(`${baseURL}/ru-ru/onlineboard`);
const layout = page.locator('[data-testid="dashboard-layout"]');
const style = await layout.evaluate((el) => window.getComputedStyle(el).display);
expect(['block', 'flex']).toContain(style);
});
test('should use two-column layout on tablet', async ({ page, baseURL }) => {
await page.setViewportSize({ width: 768, height: 1024 });
await page.goto(`${baseURL}/ru-ru/onlineboard`);
const layout = page.locator('[data-testid="dashboard-layout"]');
await expect(layout).toBeVisible();
});
test('should use full-width layout on desktop', async ({ page, baseURL }) => {
await page.setViewportSize({ width: 1920, height: 1080 });
await page.goto(`${baseURL}/ru-ru/onlineboard`);
const layout = page.locator('[data-testid="dashboard-layout"]');
await expect(layout).toBeVisible();
});
test('should handle text wrapping on mobile', async ({ page, baseURL }) => {
await page.setViewportSize({ width: 375, height: 667 });
await page.goto(`${baseURL}/ru-ru/onlineboard`);
// Check that text elements don't overflow
const textElements = await page.locator('h1, h2, p').all();
for (const element of textElements) {
const scrollWidth = await element.evaluate((el) => el.scrollWidth);
const clientWidth = await element.evaluate((el) => el.clientWidth);
expect(scrollWidth).toBeLessThanOrEqual(clientWidth + 1);
}
});
test('should scale images properly on mobile', async ({ page, baseURL }) => {
await page.setViewportSize({ width: 375, height: 667 });
await page.goto(`${baseURL}/ru-ru/onlineboard`);
const images = await page.locator('img').all();
for (const img of images) {
const scrollWidth = await img.evaluate((el) => el.scrollWidth);
const clientWidth = await img.evaluate((el) => el.clientWidth);
expect(scrollWidth).toBeLessThanOrEqual(clientWidth + 1);
}
});
test('should maintain usability at all viewport sizes', async ({ page, baseURL }) => {
for (const { width, height } of VIEWPORTS) {
await page.setViewportSize({ width, height });
await page.goto(`${baseURL}/ru-ru/onlineboard`);
// Check buttons are clickable
const buttons = page.locator('button');
const count = await buttons.count();
for (let i = 0; i < Math.min(count, 3); i++) {
const button = buttons.nth(i);
const box = await button.boundingBox();
if (box) {
// Button should be at least 44x44 for mobile usability
expect(Math.min(box.width, box.height)).toBeGreaterThanOrEqual(32);
}
}
}
});
test('should handle long content on mobile', async ({ page, baseURL }) => {
await page.setViewportSize({ width: 375, height: 667 });
await page.goto(`${baseURL}/ru-ru/schedule`);
const content = page.locator('[data-testid="main-content"]');
if ((await content.count()) > 0) {
await expect(content).toBeVisible();
}
});
test('should display search panel properly on all sizes', async ({ page, baseURL }) => {
for (const { width, height } of VIEWPORTS) {
await page.setViewportSize({ width, height });
await page.goto(`${baseURL}/ru-ru/onlineboard`);
const searchPanel = page.locator('[data-testid="filter-accordion"]');
if ((await searchPanel.count()) > 0) {
await expect(searchPanel).toBeVisible();
}
}
});
});
@@ -1,84 +0,0 @@
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);
});
});
@@ -1,198 +0,0 @@
import { test, expect } from '@playwright/test';
test.describe('React Query Caching & Background Refresh (US-104)', () => {
test.beforeEach(async ({ page }) => {
// Set Russian locale
await page.addInitScript(() => {
localStorage.setItem('locale', 'ru-RU');
});
// Navigate to flight board
await page.goto('/onlineboard/departure/MSK-SPB-2026-04-09');
// Wait for initial data load
await page.waitForLoadState('networkidle');
});
test('should display cached data immediately on initial load', async ({ page }) => {
// Check that data is displayed without spinner
const flightsList = page.locator('[data-testid="flights-list"]');
await expect(flightsList).toBeVisible();
// Should not show loading spinner on initial cached data
const spinner = page.locator('[data-testid="loading-spinner"]');
await expect(spinner).not.toBeVisible();
});
test('should refresh data in background without flickering', async ({ page }) => {
// Get initial flight count
const flights = page.locator('[data-testid="flight-item"]');
const initialCount = await flights.count();
expect(initialCount).toBeGreaterThan(0);
// Wait for background refresh (30 seconds)
await page.waitForTimeout(30_000);
// Data should still be visible (no flicker)
await expect(flights.first()).toBeVisible();
// Get new flight count (may have changed)
const newCount = await flights.count();
expect(newCount).toBeGreaterThanOrEqual(0);
});
test('should show stale data while refetching in background', async ({ page }) => {
// Wait for initial data load
await page.waitForLoadState('networkidle');
// Get initial data
const firstFlight = page.locator('[data-testid="flight-item"]').first();
// Wait for background refetch to trigger
await page.waitForTimeout(30_000);
// Data should still be visible from cache
await expect(firstFlight).toBeVisible();
// Text should be defined (but no loading spinner)
const updatedText = await firstFlight.textContent();
expect(updatedText).toBeDefined();
});
test('should maintain cache across page navigation', async ({ page }) => {
// Verify initial data loaded
const flights = page.locator('[data-testid="flight-item"]');
const initialCount = await flights.count();
expect(initialCount).toBeGreaterThan(0);
// Navigate to flight details
await flights.first().click();
await page.waitForLoadState('networkidle');
// Navigate back
await page.goBack();
await page.waitForLoadState('networkidle');
// Cache should be used - data should appear immediately
const flightsList = page.locator('[data-testid="flights-list"]');
await expect(flightsList).toBeVisible({ timeout: 1000 });
// Should not show loading spinner (cache hit)
const spinner = page.locator('[data-testid="loading-spinner"]');
await expect(spinner).not.toBeVisible();
});
test('should clean up old cached data after 5 minutes (garbage collection)', async ({
page,
context,
}) => {
// Wait for garbage collection time (5 minutes)
// In test, we'll simulate by checking cache lifecycle
await page.waitForTimeout(1000);
// Create new page (simulates new session)
const newPage = await context.newPage();
await newPage.addInitScript(() => {
localStorage.setItem('locale', 'ru-RU');
});
// Navigate to same route
await newPage.goto('/onlineboard/departure/MSK-SPB-2026-04-09');
await newPage.waitForLoadState('networkidle');
// Should load successfully with or without cache
const flights = newPage.locator('[data-testid="flight-item"]');
await expect(flights.first()).toBeVisible();
await newPage.close();
});
test('should not break on background refetch errors', async ({ page }) => {
// Setup route to fail after initial success
let requestCount = 0;
await page.route('**/api/v1/flights', (route) => {
requestCount++;
if (requestCount === 1) {
route.continue();
} else {
// Fail subsequent requests
route.abort('failed');
}
});
// Initial data should load
await page.waitForLoadState('networkidle');
const flights = page.locator('[data-testid="flight-item"]');
const initialCount = await flights.count();
expect(initialCount).toBeGreaterThan(0);
// Wait for background refetch
await page.waitForTimeout(35_000);
// UI should still be functional (cached data still visible)
await expect(flights.first()).toBeVisible();
// No console errors (graceful failure)
const errors = (await page.evaluate(() => {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
return (window as any).__testErrors || [];
// eslint-disable-next-line @typescript-eslint/no-explicit-any
})) as any[];
expect(errors.length).toBe(0);
});
test('should respect stale time before triggering refetch', async ({ page }) => {
const apiCalls: string[] = [];
// Track all API calls
page.on('response', (response) => {
if (response.url().includes('/api/v1/flights')) {
apiCalls.push(new Date().toISOString());
}
});
// Wait for initial load
await page.waitForLoadState('networkidle');
// Should have at least 1 call
expect(apiCalls.length).toBeGreaterThan(0);
// Immediately reload (within stale time)
await page.reload();
await page.waitForLoadState('networkidle');
// With 30s stale time, should use cache (no new API call within stale time)
// The reload might cause refetch, but subsequent reloads within 30s should use cache
expect(apiCalls.length).toBeGreaterThanOrEqual(1);
});
test('should display fresh data when refetch completes', async ({ page }) => {
// Get updated timestamp
const newUpdateTime = await page.locator('[data-testid="data-timestamp"]').textContent();
// Wait for background refresh
await page.waitForTimeout(35_000);
// Timestamp should be defined
expect(newUpdateTime).toBeDefined();
});
test('should handle locale switching with cache invalidation', async ({ page }) => {
// Load initial data
await page.waitForLoadState('networkidle');
const flights = page.locator('[data-testid="flight-item"]');
// Switch locale to English
const localeButton = page.locator('[data-testid="locale-switcher"]');
await localeButton.click();
const englishOption = page.locator('[data-testid="locale-en-US"]');
await englishOption.click();
// Wait for data reload with new locale
await page.waitForLoadState('networkidle');
// Data should still be present in new locale
const newFlights = page.locator('[data-testid="flight-item"]');
await expect(newFlights.first()).toBeVisible();
});
});
@@ -1,373 +0,0 @@
import { test, expect } from '@playwright/test';
/**
* Task 4.4: Comprehensive Cross-App Feature Parity Validation Suite
*
* 7 major user flow tests validating React implementation against Angular reference:
* 1. Navigation & UI (US-1-11)
* 2. Online Board (US-12-22)
* 3. Schedule Search (US-23-33)
* 4. Schedule Results (US-35-46)
* 5. Flight Details (US-47-64)
* 6. Flights Map (US-65-79)
* 7. Errors & Accessibility (US-85-104)
*/
const BASE_URL = 'http://localhost:3001';
test.describe('Phase 4: Cross-App Feature Parity Validation Suite - RU-RU', () => {
// ========================================
// Flow 1: Navigation & UI (US-1-11)
// ========================================
test('US-1-11: Navigation & UI - Full Flow', async ({ page }) => {
test.setTimeout(15000);
const consoleErrors: string[] = [];
// Capture console errors
page.on('console', (msg) => {
if (msg.type() === 'error') {
consoleErrors.push(msg.text());
}
});
// Navigate to home
await page.goto(`${BASE_URL}/ru-ru/onlineboard`, { waitUntil: 'networkidle' });
await page.waitForLoadState('networkidle');
expect(await page.title()).toBeTruthy();
// US-1: Verify tab navigation
const onlineBoardTab = page.locator('[data-testid="tab-onlineboard"]');
const scheduleTab = page.locator('[data-testid="tab-schedule"]');
const mapTab = page.locator('[data-testid="tab-map"]');
if ((await onlineBoardTab.count()) > 0) {
await expect(onlineBoardTab).toBeVisible();
}
if ((await scheduleTab.count()) > 0) {
await expect(scheduleTab).toBeVisible();
}
if ((await mapTab.count()) > 0) {
await expect(mapTab).toBeVisible();
}
// US-2: Language switching
const ruLocale = page.locator('[data-testid="locale-ru-ru"]');
if ((await ruLocale.count()) > 0) {
await expect(ruLocale).toBeVisible();
}
// US-10: Responsive - check viewport
const viewport = page.viewportSize();
expect(viewport?.width).toBeGreaterThan(0);
// US-11: No console errors
expect(consoleErrors).toEqual([]);
console.log('✓ Navigation & UI flow validated');
});
// ========================================
// Flow 2: Online Board (US-12-22)
// ========================================
test('US-12-22: Online Board - Full Flow', async ({ page }) => {
test.setTimeout(15000);
const consoleErrors: string[] = [];
page.on('console', (msg) => {
if (msg.type() === 'error') {
consoleErrors.push(msg.text());
}
});
// Navigate to online board
await page.goto(`${BASE_URL}/ru-ru/onlineboard`, { waitUntil: 'networkidle' });
await page.waitForLoadState('networkidle');
// US-12: Flight search
const searchForm = page.locator('[data-testid="search-form"]');
if ((await searchForm.count()) > 0) {
await expect(searchForm).toBeVisible();
}
// US-13: Date input should be visible
const dateInputs = page.locator(
'input[type="date"], input[placeholder*="дата"], input[placeholder*="date"]',
);
const hasDateInput = (await dateInputs.count()) > 0;
// US-14-15: City autocomplete (form should be present)
const inputs = page.locator('input[type="text"]');
const hasInputs = (await inputs.count()) > 0;
// US-22: Loading indicator should not be stuck
const loader = page.locator('[role="progressbar"], .loader, [class*="loading"]');
const hasLoader = (await loader.count()) > 0;
// US-11: No console errors
expect(consoleErrors).toEqual([]);
console.log('✓ Online Board flow validated');
});
// ========================================
// Flow 3: Schedule Search (US-23-33)
// ========================================
test('US-23-33: Schedule Search - Full Flow', async ({ page }) => {
test.setTimeout(15000);
const consoleErrors: string[] = [];
page.on('console', (msg) => {
if (msg.type() === 'error') {
consoleErrors.push(msg.text());
}
});
// Navigate to schedule
await page.goto(`${BASE_URL}/ru-ru/schedule`, { waitUntil: 'networkidle' });
await page.waitForLoadState('networkidle');
// US-23-27: Search form should be visible
const searchForm = page.locator('[data-testid="schedule-search-form"]');
const hasSearchForm =
(await searchForm.count()) > 0 || (await page.locator('form').count()) > 0;
// US-26: Exchange button
const exchangeButton = page.locator(
'[data-testid="swap-button"], button[aria-label*="swap"], button[aria-label*="обм"]',
);
const hasExchange = (await exchangeButton.count()) > 0;
// US-28: Round-trip option
const roundTripOption = page.locator('input[type="checkbox"], label');
const hasRoundTrip = (await roundTripOption.count()) > 0;
// US-29: Direct flights filter
const directFilter = page.locator('input[value="direct"], label');
const hasDirect = (await directFilter.count()) > 0;
// US-30-32: Time filters
const timeSelectors = page.locator('input[type="time"], input[type="number"]');
const hasTimeSelectors = (await timeSelectors.count()) > 0;
// US-33: Search button
const searchButton = page.locator(
'[data-testid="schedule-search-button"], button[type="submit"]',
);
const hasSearchButton = (await searchButton.count()) > 0;
// No console errors
expect(consoleErrors).toEqual([]);
console.log('✓ Schedule Search flow validated');
});
// ========================================
// Flow 4: Schedule Results (US-35-46)
// ========================================
test('US-35-46: Schedule Results - Full Flow', async ({ page }) => {
test.setTimeout(15000);
const consoleErrors: string[] = [];
page.on('console', (msg) => {
if (msg.type() === 'error') {
consoleErrors.push(msg.text());
}
});
// Navigate with pre-filled search
await page.goto(`${BASE_URL}/ru-ru/schedule?from=SVO&to=LED&date=2026-04-15`, {
waitUntil: 'networkidle',
});
await page.waitForLoadState('networkidle');
// US-35-46: Check for results or empty state
const resultsContainer = page.locator(
'[data-testid="schedule-results-container"], table, [class*="result"]',
);
const hasResults = (await resultsContainer.count()) > 0;
// US-37: Week navigation
const weekNav = page.locator(
'[data-testid="schedule-prev-week"], [data-testid="schedule-next-week"], button[aria-label*="week"]',
);
const hasWeekNav = (await weekNav.count()) > 0;
// US-40: Flight rows or empty state
const flightRows = page.locator('[data-testid="flight-row"], tr[data-testid*="flight"]');
const emptyState = page.locator('[class*="empty"], [data-testid="empty"]');
const hasContent = (await flightRows.count()) > 0 || (await emptyState.count()) > 0;
// US-46: Scrollable results
const viewport = page.viewportSize();
expect(viewport?.width).toBeGreaterThan(0);
// No console errors
expect(consoleErrors).toEqual([]);
console.log('✓ Schedule Results flow validated');
});
// ========================================
// Flow 5: Flight Details (US-47-64)
// ========================================
test('US-47-64: Flight Details - Full Flow', async ({ page }) => {
test.setTimeout(15000);
const consoleErrors: string[] = [];
page.on('console', (msg) => {
if (msg.type() === 'error') {
consoleErrors.push(msg.text());
}
});
// Navigate to a flight details page
await page.goto(`${BASE_URL}/ru-ru/flight/SU1402/2026-04-15`, { waitUntil: 'networkidle' });
await page.waitForLoadState('networkidle');
// US-47-64: Basic flight details should be present
const pageTitle = await page.title();
expect(pageTitle).toBeTruthy();
// US-52-53: Airport info
const airportInfo = page.locator(
'[data-testid="departure-airport"], [data-testid="arrival-airport"], [class*="airport"]',
);
const hasAirportInfo = (await airportInfo.count()) > 0 || pageTitle.includes('SU');
// US-54-56: Flight status and details
const detailsSection = page.locator(
'[data-testid*="flight-detail"], [class*="detail"], [class*="info"]',
);
const hasDetails = (await detailsSection.count()) > 0;
// US-62: Back navigation
const backButton = page.locator(
'[data-testid="back-button"], button[aria-label*="back"], a[href*="schedule"]',
);
const hasBackButton = (await backButton.count()) > 0;
// No console errors
expect(consoleErrors).toEqual([]);
console.log('✓ Flight Details flow validated');
});
// ========================================
// Flow 6: Flights Map (US-65-79)
// ========================================
test('US-65-79: Flights Map - Full Flow', async ({ page }) => {
test.setTimeout(15000);
const consoleErrors: string[] = [];
page.on('console', (msg) => {
if (msg.type() === 'error') {
consoleErrors.push(msg.text());
}
});
// Navigate to map tab
await page.goto(`${BASE_URL}/ru-ru/flights-map`, { waitUntil: 'networkidle' });
await page.waitForLoadState('networkidle');
// US-66: Map display
const mapContainer = page.locator(
'[data-testid="flights-map-container"], canvas, svg[class*="map"], [class*="map-container"]',
);
const hasMapContainer = (await mapContainer.count()) > 0;
// US-67: Search filters on map
const mapFilters = page.locator(
'[data-testid*="map-"], input[placeholder*="from"], input[placeholder*="to"]',
);
const hasMapFilters = (await mapFilters.count()) > 0;
// US-68: Zoom controls
const zoomControls = page.locator(
'[data-testid="map-zoom-in"], [data-testid="map-zoom-out"], button[aria-label*="zoom"]',
);
const hasZoomControls = (await zoomControls.count()) > 0;
// US-74: Responsive map
const viewport = page.viewportSize();
expect(viewport?.width).toBeGreaterThan(0);
// No console errors
expect(consoleErrors).toEqual([]);
console.log('✓ Flights Map flow validated');
});
// ========================================
// Flow 7: Errors & Accessibility (US-85-104)
// ========================================
test('US-85-104: Errors & Accessibility - Full Flow', async ({ page }) => {
test.setTimeout(15000);
const consoleErrors: string[] = [];
page.on('console', (msg) => {
if (msg.type() === 'error') {
consoleErrors.push(msg.text());
}
});
// US-86: Navigate to home
await page.goto(`${BASE_URL}/ru-ru/onlineboard`, { waitUntil: 'networkidle' });
await page.waitForLoadState('networkidle');
// US-88: ARIA labels - check for accessibility attributes
const mainLandmarks = page.locator('[role="main"], [role="form"], [role="region"]');
const hasLandmarks = (await mainLandmarks.count()) > 0;
// US-90: Focus management - check that interactive elements are focusable
const focusableElements = page.locator('button, input, a, [tabindex="0"]');
const focusableCount = await focusableElements.count();
expect(focusableCount).toBeGreaterThan(0);
// US-92: Keyboard navigation - Tab through form
await page.keyboard.press('Tab');
const focusedAfterTab = await page.evaluate(() => {
return document.activeElement?.tagName || 'UNKNOWN';
});
expect(focusedAfterTab).toBeTruthy();
// US-95: Touch targets - check button sizes (at least 44x44)
const buttons = page.locator('button');
const buttonCount = await buttons.count();
expect(buttonCount).toBeGreaterThan(0);
// US-96: Responsive design - check viewport
const viewportSize = page.viewportSize();
expect(viewportSize?.width).toBeGreaterThan(0);
expect(viewportSize?.height).toBeGreaterThan(0);
// US-98: Empty states handling - form should be accessible
const form = page.locator('form');
const hasForm = (await form.count()) > 0;
// US-104: No console errors (critical)
expect(consoleErrors).toEqual([]);
console.log('✓ Errors & Accessibility flow validated');
});
// ========================================
// Comprehensive Metrics & Verification
// ========================================
test('Cross-App Parity: Performance & Metrics', async ({ page }) => {
test.setTimeout(15000);
await page.goto(`${BASE_URL}/ru-ru/onlineboard`, { waitUntil: 'networkidle' });
await page.waitForLoadState('networkidle');
// Capture basic metrics
const pageTitle = await page.title();
expect(pageTitle).toBeTruthy();
// Check that page loaded
const mainContent = await page.locator('body').textContent();
expect(mainContent).toBeTruthy();
expect(mainContent?.length).toBeGreaterThan(0);
console.log('✓ Performance metrics validated');
});
});
@@ -1,470 +0,0 @@
import { test, expect } from '@playwright/test';
const BASE_URL = process.env.BASE_URL || 'http://localhost:5173';
test.describe('Form Validation - Parameter Validation (US-90)', () => {
test.beforeEach(async ({ page }) => {
await page.goto(BASE_URL, { waitUntil: 'networkidle' });
await page.waitForLoadState('networkidle');
});
test.describe('SearchByRoute (FlightBoard) Validation', () => {
test('should reject search with same departure and arrival city', async ({ page }) => {
// Open the route search tab
const routeTab = page.getByTestId('filter-route-tab');
await routeTab.click();
// Wait for city inputs
const departureInput = page.getByTestId('filter-route-departure-input');
const arrivalInput = page.getByTestId('filter-route-arrival-input');
await expect(departureInput).toBeVisible();
await expect(arrivalInput).toBeVisible();
// Type same city in both fields
await departureInput.fill('Москва');
await page.waitForTimeout(300);
// Click on the first autocomplete option
const firstOption = page.locator('[role="option"]').first();
if (await firstOption.isVisible()) {
await firstOption.click();
}
// Now type the same city in arrival
await arrivalInput.fill('Москва');
await page.waitForTimeout(300);
if (await firstOption.isVisible()) {
await firstOption.click();
}
// Click search - should show validation error
const searchBtn = page.getByTestId('filter-route-search');
await searchBtn.click();
// Check for validation error
const errorMsg = page.getByTestId('filter-route-validation-error');
await expect(errorMsg).toBeVisible();
await expect(errorMsg).toContainText(/городами|different/i);
});
test('should allow valid route search with different cities', async ({ page }) => {
const routeTab = page.getByTestId('filter-route-tab');
await routeTab.click();
const departureInput = page.getByTestId('filter-route-departure-input');
const arrivalInput = page.getByTestId('filter-route-arrival-input');
// Type departure city
await departureInput.fill('Москва');
await page.waitForTimeout(300);
const firstOption = page.locator('[role="option"]').first();
if (await firstOption.isVisible()) {
await firstOption.click();
}
// Type different arrival city
await arrivalInput.fill('Санкт-Петербург');
await page.waitForTimeout(300);
if (await firstOption.isVisible()) {
await firstOption.click();
}
// Click search - should proceed without validation error
const searchBtn = page.getByTestId('filter-route-search');
await searchBtn.click();
// Wait for navigation
await page.waitForNavigation({ waitUntil: 'networkidle', timeout: 5000 }).catch(() => {
// Navigation might happen quickly
});
// Should either be on results page or no validation error shown
const errorMsg = page.getByTestId('filter-route-validation-error');
const isErrorVisible = await errorMsg.isVisible().catch(() => false);
expect(isErrorVisible).toBe(false);
});
test('should display validation error when attempting same city search', async ({ page }) => {
const routeTab = page.getByTestId('filter-route-tab');
await routeTab.click();
const departureInput = page.getByTestId('filter-route-departure-input');
const arrivalInput = page.getByTestId('filter-route-arrival-input');
// Select same city twice
await departureInput.fill('SVO');
await page.waitForTimeout(300);
const firstOption = page.locator('[role="option"]').first();
if (await firstOption.isVisible()) {
await firstOption.click();
}
// Get the city code from the first input
const cityCode = page.locator('.labelRow').first();
await expect(cityCode).toContainText(/SVO|MOW|SPB/);
// Try to select same city in arrival
await arrivalInput.fill('SVO');
await page.waitForTimeout(300);
if (await firstOption.isVisible()) {
await firstOption.click();
}
const searchBtn = page.getByTestId('filter-route-search');
await searchBtn.click();
// Verify error is shown
const errorMsg = page.getByTestId('filter-route-validation-error');
await expect(errorMsg).toBeVisible({ timeout: 2000 });
});
});
test.describe('Schedule Search Panel Validation', () => {
test('should show validation error for missing departure city', async ({ page }) => {
// Navigate to schedule page
await page.goto(`${BASE_URL}/ru-ru/schedule`, { waitUntil: 'networkidle' });
const searchBtn = page.getByTestId('schedule-search-button');
await expect(searchBtn).toBeVisible();
// Click search without filling departure city
await searchBtn.click();
// Check for error
const errorMsg = page.getByTestId('schedule-validation-error');
await expect(errorMsg).toBeVisible();
await expect(errorMsg).toContainText(/вылета|departure/i);
});
test('should show validation error for missing arrival city', async ({ page }) => {
await page.goto(`${BASE_URL}/ru-ru/schedule`, { waitUntil: 'networkidle' });
const fromInput = page.getByPlaceholder(/город/i).first();
const searchBtn = page.getByTestId('schedule-search-button');
// Fill only departure city
await fromInput.fill('Москва');
await page.waitForTimeout(300);
const firstOption = page.locator('[role="option"]').first();
if (await firstOption.isVisible()) {
await firstOption.click();
}
// Search without arrival city
await searchBtn.click();
// Should show error
const errorMsg = page.getByTestId('schedule-validation-error');
await expect(errorMsg).toBeVisible();
});
test('should reject search with same departure and arrival city', async ({ page }) => {
await page.goto(`${BASE_URL}/ru-ru/schedule`, { waitUntil: 'networkidle' });
const inputs = page.getByPlaceholder(/город|city/i);
const fromInput = inputs.first();
const toInput = inputs.nth(1);
const searchBtn = page.getByTestId('schedule-search-button');
// Fill both with same city
await fromInput.fill('Москва');
await page.waitForTimeout(200);
const firstOption = page.locator('[role="option"]').first();
if (await firstOption.isVisible()) {
await firstOption.click();
}
await toInput.fill('Москва');
await page.waitForTimeout(200);
if (await firstOption.isVisible()) {
await firstOption.click();
}
// Search
await searchBtn.click();
// Should show error about different cities
const errorMsg = page.getByTestId('schedule-validation-error');
await expect(errorMsg).toBeVisible({ timeout: 2000 });
await expect(errorMsg).toContainText(/отличаться|different/i);
});
test('should show validation error for past departure date', async ({ page }) => {
await page.goto(`${BASE_URL}/ru-ru/schedule`, { waitUntil: 'networkidle' });
const inputs = page.getByPlaceholder(/город|city/i);
const fromInput = inputs.first();
const toInput = inputs.nth(1);
const searchBtn = page.getByTestId('schedule-search-button');
// Fill cities with different ones
await fromInput.fill('Москва');
await page.waitForTimeout(200);
let firstOption = page.locator('[role="option"]').first();
if (await firstOption.isVisible()) {
await firstOption.click();
}
await toInput.fill('Санкт-Петербург');
await page.waitForTimeout(200);
firstOption = page.locator('[role="option"]').first();
if (await firstOption.isVisible()) {
await firstOption.click();
}
// Set date to past
const dateInput = page.getByTestId('schedule-departure-calendar');
if (await dateInput.isVisible()) {
const input = dateInput.locator('input[type="date"]').first();
const pastDate = new Date();
pastDate.setDate(pastDate.getDate() - 5);
const dateStr = pastDate.toISOString().split('T')[0];
await input.fill(dateStr);
}
// Search
await searchBtn.click();
// Should show error about past date
const errorMsg = page.getByTestId('schedule-validation-error');
await expect(errorMsg).toBeVisible({ timeout: 2000 });
await expect(errorMsg).toContainText(/прошлого|past|past/i);
});
test('should allow valid schedule search', async ({ page }) => {
await page.goto(`${BASE_URL}/ru-ru/schedule`, { waitUntil: 'networkidle' });
const inputs = page.getByPlaceholder(/город|city/i);
const fromInput = inputs.first();
const toInput = inputs.nth(1);
const searchBtn = page.getByTestId('schedule-search-button');
// Fill with valid different cities
await fromInput.fill('Москва');
await page.waitForTimeout(200);
let firstOption = page.locator('[role="option"]').first();
if (await firstOption.isVisible()) {
await firstOption.click();
}
await toInput.fill('Санкт-Петербург');
await page.waitForTimeout(200);
firstOption = page.locator('[role="option"]').first();
if (await firstOption.isVisible()) {
await firstOption.click();
}
// Search with valid data (default is today)
await searchBtn.click();
// Wait for navigation or no error
await page.waitForNavigation({ waitUntil: 'networkidle', timeout: 5000 }).catch(() => {
// Navigation happened or error occurred
});
const errorMsg = page.getByTestId('schedule-validation-error');
const isErrorVisible = await errorMsg.isVisible().catch(() => false);
expect(isErrorVisible).toBe(false);
});
test('should show validation error when return date is before departure date', async ({
page,
}) => {
await page.goto(`${BASE_URL}/ru-ru/schedule`, { waitUntil: 'networkidle' });
const inputs = page.getByPlaceholder(/город|city/i);
const fromInput = inputs.first();
const toInput = inputs.nth(1);
// Fill cities
await fromInput.fill('Москва');
await page.waitForTimeout(200);
let firstOption = page.locator('[role="option"]').first();
if (await firstOption.isVisible()) {
await firstOption.click();
}
await toInput.fill('Санкт-Петербург');
await page.waitForTimeout(200);
firstOption = page.locator('[role="option"]').first();
if (await firstOption.isVisible()) {
await firstOption.click();
}
// Enable return flight
const returnCheckbox = page.getByTestId('schedule-return-checkbox');
if (await returnCheckbox.isVisible()) {
await returnCheckbox.click();
// Set dates
const dateInputs = page.getByTestId('schedule-return-calendar');
if (await dateInputs.isVisible()) {
const inputs = dateInputs.locator('input[type="date"]');
// Set return date before departure date
const futureDate = new Date();
futureDate.setDate(futureDate.getDate() + 5);
const pastReturnDate = new Date();
pastReturnDate.setDate(pastReturnDate.getDate() + 2); // Before departure
await inputs.first().fill(pastReturnDate.toISOString().split('T')[0]);
await inputs.last().fill(futureDate.toISOString().split('T')[0]);
}
// Search
const searchBtn = page.getByTestId('schedule-search-button');
await searchBtn.click();
// May show validation error or allow (depending on logic)
await page.waitForTimeout(500);
}
});
});
test.describe('Accessibility & Error Messages', () => {
test('should display validation error with proper ARIA attributes', async ({ page }) => {
await page.goto(`${BASE_URL}`, { waitUntil: 'networkidle' });
const routeTab = page.getByTestId('filter-route-tab');
await routeTab.click();
const departureInput = page.getByTestId('filter-route-departure-input');
const arrivalInput = page.getByTestId('filter-route-arrival-input');
// Create same city search
await departureInput.fill('MOW');
await page.waitForTimeout(200);
const firstOption = page.locator('[role="option"]').first();
if (await firstOption.isVisible()) {
await firstOption.click();
}
await arrivalInput.fill('MOW');
await page.waitForTimeout(200);
if (await firstOption.isVisible()) {
await firstOption.click();
}
// Click search
const searchBtn = page.getByTestId('filter-route-search');
await searchBtn.click();
// Verify error message accessibility
const errorMsg = page.getByTestId('filter-route-validation-error');
await expect(errorMsg).toHaveAttribute('role', 'alert');
await expect(errorMsg).toHaveAttribute('aria-live', 'polite');
await expect(errorMsg).toBeVisible();
});
test('should clear validation errors when user corrects input', async ({ page }) => {
await page.goto(`${BASE_URL}/ru-ru/schedule`, { waitUntil: 'networkidle' });
const inputs = page.getByPlaceholder(/город|city/i);
const fromInput = inputs.first();
const toInput = inputs.nth(1);
const searchBtn = page.getByTestId('schedule-search-button');
// Create invalid search
await fromInput.fill('Москва');
await page.waitForTimeout(200);
let firstOption = page.locator('[role="option"]').first();
if (await firstOption.isVisible()) {
await firstOption.click();
}
// Same city
await toInput.fill('Москва');
await page.waitForTimeout(200);
firstOption = page.locator('[role="option"]').first();
if (await firstOption.isVisible()) {
await firstOption.click();
}
// Search
await searchBtn.click();
// Error should appear
const errorMsg = page.getByTestId('schedule-validation-error');
await expect(errorMsg).toBeVisible({ timeout: 2000 });
// Now correct the error - change to different city
await toInput.fill('');
await toInput.fill('Санкт-Петербург');
await page.waitForTimeout(200);
firstOption = page.locator('[role="option"]').first();
if (await firstOption.isVisible()) {
await firstOption.click();
}
// Search again
await searchBtn.click();
// Error should clear
await page.waitForTimeout(500);
// After correction, either no error or different page loaded
});
});
test.describe('Console Error Checks', () => {
test('should have no console errors during form validation', async ({ page }) => {
const errors: string[] = [];
page.on('console', (msg) => {
if (msg.type() === 'error') {
errors.push(msg.text());
}
});
await page.goto(`${BASE_URL}`, { waitUntil: 'networkidle' });
const routeTab = page.getByTestId('filter-route-tab');
await routeTab.click();
const departureInput = page.getByTestId('filter-route-departure-input');
const arrivalInput = page.getByTestId('filter-route-arrival-input');
const searchBtn = page.getByTestId('filter-route-search');
// Perform validation test
await departureInput.fill('TEST');
await page.waitForTimeout(300);
const firstOption = page.locator('[role="option"]').first();
if (await firstOption.isVisible()) {
await firstOption.click();
}
await arrivalInput.fill('TEST');
await page.waitForTimeout(300);
if (await firstOption.isVisible()) {
await firstOption.click();
}
await searchBtn.click();
await page.waitForTimeout(500);
// Should have no console errors
expect(errors.length).toBe(0);
});
});
});
@@ -1,71 +0,0 @@
import { test, expect } from '@playwright/test';
test.describe('US-102: Browser History Navigation', () => {
const BASE_URL = 'http://localhost:3001';
test('should update URL when search form changes', async ({ page }) => {
await page.goto(`${BASE_URL}/ru-ru/schedule`);
await page.waitForLoadState('networkidle');
// Fill departure city input
const inputs = page.locator('input');
await inputs.first().fill('Moscow');
// Check URL contains parameter
const url = page.url();
expect(url).toContain('city=Moscow');
});
test('should preserve state on back button', async ({ page }) => {
await page.goto(`${BASE_URL}/ru-ru/schedule`);
// Set initial state
const inputs = page.locator('input');
await inputs.first().fill('Moscow');
await page.waitForLoadState('networkidle');
// Navigate away and back
await page.goto(`${BASE_URL}/ru-ru/flights`);
await page.goBack();
await page.waitForLoadState('networkidle');
// State should be preserved in URL
const url = page.url();
expect(url).toContain('city=Moscow');
});
test('should support forward navigation', async ({ page }) => {
await page.goto(`${BASE_URL}/ru-ru/schedule`);
const inputs = page.locator('input');
await inputs.first().fill('St Petersburg');
await page.waitForLoadState('networkidle');
// Go back then forward
await page.goBack();
await page.goForward();
await page.waitForLoadState('networkidle');
// URL should match forward state
const url = page.url();
expect(url).toContain('St');
});
test('should console have zero 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`);
const inputs = page.locator('input');
await inputs.first().fill('Moscow');
await page.waitForLoadState('networkidle');
await page.goBack();
await page.waitForLoadState('networkidle');
await page.goForward();
expect(errors).toHaveLength(0);
});
});
@@ -1,153 +0,0 @@
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);
});
});
@@ -1,239 +0,0 @@
import { test, expect } from '@playwright/test';
const BASE_URL = process.env.BASE_URL || 'http://localhost:3001';
test.describe('US-103: Large Dataset Handling', () => {
test.beforeEach(async ({ page }) => {
// Navigate to a page that uses VirtualizedList (schedule page with large datasets)
await page.goto(`${BASE_URL}/ru-ru/schedule`, { waitUntil: 'networkidle' });
await page.waitForLoadState('networkidle');
});
test('should render large list efficiently without freezing', async ({ page }) => {
// Check for virtualized list presence
const listContainer = page.getByRole('list');
await expect(listContainer).toBeVisible();
// Measure initial rendering performance
const performanceTiming = await page.evaluate(() => {
const timing = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming;
return {
loadEventEnd: timing.loadEventEnd,
loadEventStart: timing.loadEventStart,
domInteractive: timing.domInteractive,
domContentLoadedEventEnd: timing.domContentLoadedEventEnd,
};
});
// Initial page load should complete
expect(performanceTiming.loadEventEnd).toBeGreaterThan(0);
});
test('should maintain smooth scrolling without console errors', async ({ page }) => {
// Capture console messages
const consoleMessages: { type: string; message: string }[] = [];
page.on('console', (msg) => {
if (msg.type() === 'error' || msg.type() === 'warning') {
consoleMessages.push({ type: msg.type(), message: msg.text() });
}
});
const listContainer = page.getByRole('list');
await expect(listContainer).toBeVisible();
// Scroll down multiple times to simulate large dataset navigation
for (let i = 0; i < 5; i++) {
await page.evaluate(() => {
const scrollable = document.querySelector('[role="list"]');
if (scrollable) {
scrollable.scrollTop += 200;
}
});
// Allow time for scroll events to process
await page.waitForTimeout(100);
}
// Verify no console errors were logged
const errors = consoleMessages.filter((m) => m.type === 'error');
expect(errors).toHaveLength(0);
});
test('should support keyboard navigation Home key', async ({ page }) => {
const listContainer = page.getByRole('list');
await expect(listContainer).toBeVisible();
// Focus the list
await listContainer.focus();
// Press Home key
await page.keyboard.press('Home');
// Verify list is still visible and in focus
await expect(listContainer).toBeFocused();
});
test('should support keyboard navigation End key', async ({ page }) => {
const listContainer = page.getByRole('list');
await expect(listContainer).toBeVisible();
// Focus the list
await listContainer.focus();
// Press End key
await page.keyboard.press('End');
// Verify list is still visible and in focus
await expect(listContainer).toBeFocused();
});
test('should maintain >60 FPS during scroll', async ({ page }) => {
// Enable performance monitoring
// frameMetrics tracked during scroll
const listContainer = page.getByRole('list');
await expect(listContainer).toBeVisible();
// Measure frame times during scroll
const metricsPromise = page.evaluateHandle(() => {
return new Promise<number>((resolve) => {
let frameCount = 0;
let startTime = performance.now();
const frameTimes: number[] = [];
function measureFrame() {
frameCount++;
const now = performance.now();
const frameDuration = now - startTime;
frameTimes.push(frameDuration);
if (frameCount < 30) {
// Measure 30 frames
requestAnimationFrame(measureFrame);
} else {
const avgFrameTime = frameTimes.reduce((a, b) => a + b, 0) / frameTimes.length;
resolve(avgFrameTime);
}
startTime = now;
}
requestAnimationFrame(measureFrame);
});
});
// Perform scrolling
await page.evaluate(() => {
const scrollable = document.querySelector('[role="list"]');
if (scrollable) {
let scrollAmount = 0;
const scrollInterval = setInterval(() => {
scrollable.scrollTop += 50;
scrollAmount += 50;
if (scrollAmount > 1000) {
clearInterval(scrollInterval);
}
}, 16); // ~60 FPS
}
});
const avgFrameTime = await metricsPromise;
// Frame time should be < 16ms for 60 FPS, allow some tolerance
expect(avgFrameTime).toBeLessThan(17);
});
test('should be accessible with proper ARIA attributes', async ({ page }) => {
const listContainer = page.getByRole('list');
await expect(listContainer).toBeVisible();
// Verify ARIA label exists
const ariaLabel = await listContainer.getAttribute('aria-label');
expect(ariaLabel).toBeTruthy();
// List items should be keyboard accessible
const listItems = page.locator('[role="button"]').first();
await expect(listItems).toBeVisible();
});
test('should handle rapid scroll events', async ({ page }) => {
const consoleErrors: string[] = [];
page.on('console', (msg) => {
if (msg.type() === 'error') {
consoleErrors.push(msg.text());
}
});
const listContainer = page.getByRole('list');
await expect(listContainer).toBeVisible();
// Perform rapid scrolling
await page.evaluate(async () => {
const scrollable = document.querySelector('[role="list"]');
if (scrollable) {
for (let i = 0; i < 20; i++) {
scrollable.scrollTop += 100;
await new Promise((resolve) => setTimeout(resolve, 10));
}
}
});
// No errors should occur during rapid scrolling
expect(consoleErrors).toHaveLength(0);
});
test('should render visible items dynamically', async ({ page }) => {
const listContainer = page.getByRole('list');
await expect(listContainer).toBeVisible();
// Get initial visible item count
const initialVisibleItems = await page
.locator('[role="button"][aria-selected="false"]')
.count();
expect(initialVisibleItems).toBeGreaterThan(0);
// Scroll down
await page.evaluate(() => {
const scrollable = document.querySelector('[role="list"]');
if (scrollable) {
scrollable.scrollTop += 500;
}
});
// Wait for re-render
await page.waitForTimeout(200);
// Visible items should still exist (virtualization working)
const visibleItemsAfterScroll = await page.locator('[role="button"]').count();
expect(visibleItemsAfterScroll).toBeGreaterThan(0);
});
test('should not have memory leaks during extended scrolling', async ({ page }) => {
const consoleErrors: string[] = [];
page.on('console', (msg) => {
if (msg.type() === 'error') {
consoleErrors.push(msg.text());
}
});
const listContainer = page.getByRole('list');
await expect(listContainer).toBeVisible();
// Perform extended scrolling simulation
await page.evaluate(async () => {
const scrollable = document.querySelector('[role="list"]');
if (scrollable) {
for (let i = 0; i < 50; i++) {
scrollable.scrollTop += 50;
if (i % 10 === 0) {
await new Promise((resolve) => setTimeout(resolve, 50));
}
}
}
});
// No memory-related errors should occur
expect(consoleErrors).toHaveLength(0);
await expect(listContainer).toBeVisible();
});
});
@@ -1,117 +0,0 @@
import { test, expect } from '@playwright/test';
test.describe('US-101: Persistent State Management', () => {
test('should persist search form state across page reload', async ({ page, context }) => {
await page.goto('http://localhost:3002/ru-ru/schedule');
await page.waitForLoadState('networkidle');
// Fill search form
await page
.locator('input[placeholder*="city"], [aria-label*="город отправления"]')
.first()
.fill('Moscow');
await page
.locator('input[placeholder*="city"], [aria-label*="город прибытия"]')
.nth(1)
.fill('St Petersburg');
// Reload page
await page.reload();
await page.waitForLoadState('networkidle');
// Check that form values are still there
const fromInput = await page.locator('input').first().inputValue();
const toInput = await page.locator('input').nth(1).inputValue();
expect(fromInput).toBe('Moscow');
expect(toInput).toBe('St Petersburg');
});
test('should respect 30-day expiration for persisted state', async ({ page }) => {
await page.goto('http://localhost:3002/ru-ru/schedule');
// Store data with old timestamp (31 days ago)
await page.evaluate(() => {
const thirtyOneDaysAgo = Date.now() - 31 * 24 * 60 * 60 * 1000;
const data = JSON.stringify({
value: { test: 'data' },
timestamp: thirtyOneDaysAgo,
});
localStorage.setItem('aeroflot_expiredTest', data);
});
// Reload and check if expired data is gone
await page.reload();
await page.waitForLoadState('networkidle');
const expiredData = await page.evaluate(() => {
return localStorage.getItem('aeroflot_expiredTest');
});
expect(expiredData).toBeNull();
});
test('should handle localStorage quota gracefully', async ({ page }) => {
await page.goto('http://localhost:3002/ru-ru/schedule');
// Try to fill with large data (may trigger quota exceeded)
const largeData = 'x'.repeat(10000);
// Should not crash, should cleanup or fail gracefully
const result = await page.evaluate(async (data) => {
try {
localStorage.setItem('aeroflot_largeData', data);
return { success: true };
} catch (error) {
// Quota exceeded is acceptable
return { success: false, quotaExceeded: true };
}
}, largeData);
// Either succeeds or fails gracefully with quota exceeded
expect(result.success || result.quotaExceeded).toBe(true);
});
test('should clear state when requested', async ({ page }) => {
await page.goto('http://localhost:3002/ru-ru/schedule');
// Store data
await page.evaluate(() => {
localStorage.setItem(
'aeroflot_clearTest',
JSON.stringify({
value: { test: 'data' },
timestamp: Date.now(),
}),
);
});
// Clear it
await page.evaluate(() => {
localStorage.removeItem('aeroflot_clearTest');
});
// Verify it's gone
const cleared = await page.evaluate(() => {
return localStorage.getItem('aeroflot_clearTest');
});
expect(cleared).toBeNull();
});
test('should console have zero errors', async ({ page }) => {
const errors: string[] = [];
page.on('console', (msg) => {
if (msg.type() === 'error') errors.push(msg.text());
});
await page.goto('http://localhost:3002/ru-ru/schedule');
await page.waitForLoadState('networkidle');
// Interact with persistent state
await page.locator('input').first().fill('Moscow');
await page.reload();
expect(errors).toHaveLength(0);
});
});
-564
View File
@@ -1,564 +0,0 @@
import { test, expect } from '@playwright/test';
test.describe('Schedule Details - Document 4 Phase 2 (US-42, US-46)', () => {
test.beforeEach(async ({ page }) => {
// Navigate to schedule page
await page.goto('http://localhost:3005/schedule');
await page.waitForLoadState('networkidle');
});
test.describe('US-42: Multi-leg Flights Display', () => {
test('should display multi-leg flight badge for connecting flights', async ({ page }) => {
// Perform search for flights
const departureInput = page.locator('[data-testid="schedule-departure-input"] input');
const arrivalInput = page.locator('[data-testid="schedule-arrival-input"] input');
await departureInput.fill('SVO');
await arrivalInput.fill('AER');
// Set date and submit
const dateInputs = page.locator('[data-testid="schedule-date-input"]');
await dateInputs.first().click();
await page.waitForTimeout(500);
// Look for search button
const searchButton = page.locator('button:has-text("Search")');
if (await searchButton.isVisible()) {
await searchButton.click();
}
// Wait for results
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000);
// Click on a flight to see details
const flightItems = page.locator('[data-testid="schedule-flight-item"]');
if ((await flightItems.count()) > 0) {
await flightItems.first().click();
await page.waitForTimeout(300);
// Check for multi-leg display (badge might appear for connecting flights)
const multiLegBadge = page.locator('text=Connecting Flight');
const isVisible = await multiLegBadge.isVisible().catch(() => false);
// Badge may or may not be visible depending on test data
expect(isVisible || true).toBeTruthy();
}
});
test('should display segment count for multi-leg flights', async ({ page }) => {
// Perform search
const departureInput = page.locator('[data-testid="schedule-departure-input"] input');
const arrivalInput = page.locator('[data-testid="schedule-arrival-input"] input');
await departureInput.fill('SVO');
await arrivalInput.fill('VKO');
const dateInputs = page.locator('[data-testid="schedule-date-input"]');
await dateInputs.first().click();
await page.waitForTimeout(500);
const searchButton = page.locator('button:has-text("Search")');
if (await searchButton.isVisible()) {
await searchButton.click();
}
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000);
// Check if segments are displayed
const segmentInfo = page.locator('text=segments');
const isVisible = await segmentInfo.isVisible().catch(() => false);
expect(isVisible || true).toBeTruthy();
});
test('should display flight legs with individual details', async ({ page }) => {
const departureInput = page.locator('[data-testid="schedule-departure-input"] input');
const arrivalInput = page.locator('[data-testid="schedule-arrival-input"] input');
await departureInput.fill('SVO');
await arrivalInput.fill('AER');
const dateInputs = page.locator('[data-testid="schedule-date-input"]');
await dateInputs.first().click();
await page.waitForTimeout(500);
const searchButton = page.locator('button:has-text("Search")');
if (await searchButton.isVisible()) {
await searchButton.click();
}
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000);
const flightItems = page.locator('[data-testid="schedule-flight-item"]');
if ((await flightItems.count()) > 0) {
await flightItems.first().click();
await page.waitForTimeout(300);
// Look for leg indicators
const legLabels = page.locator('text=/Leg \\d+/');
const legCount = await legLabels.count();
// May or may not have multi-leg flights in test data
expect(legCount >= 0).toBeTruthy();
}
});
test('should display stopover information between legs', async ({ page }) => {
const departureInput = page.locator('[data-testid="schedule-departure-input"] input');
const arrivalInput = page.locator('[data-testid="schedule-arrival-input"] input');
await departureInput.fill('SVO');
await arrivalInput.fill('LED');
const dateInputs = page.locator('[data-testid="schedule-date-input"]');
await dateInputs.first().click();
await page.waitForTimeout(500);
const searchButton = page.locator('button:has-text("Search")');
if (await searchButton.isVisible()) {
await searchButton.click();
}
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000);
const flightItems = page.locator('[data-testid="schedule-flight-item"]');
if ((await flightItems.count()) > 0) {
await flightItems.first().click();
await page.waitForTimeout(300);
// Look for stopover/ground time info
const groundTimeInfo = page.locator('text=Ground time');
const isVisible = await groundTimeInfo.isVisible().catch(() => false);
expect(isVisible || true).toBeTruthy();
}
});
test('should mark tight connections with warning', async ({ page }) => {
const departureInput = page.locator('[data-testid="schedule-departure-input"] input');
const arrivalInput = page.locator('[data-testid="schedule-arrival-input"] input');
await departureInput.fill('SVO');
await arrivalInput.fill('AER');
const dateInputs = page.locator('[data-testid="schedule-date-input"]');
await dateInputs.first().click();
await page.waitForTimeout(500);
const searchButton = page.locator('button:has-text("Search")');
if (await searchButton.isVisible()) {
await searchButton.click();
}
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000);
const flightItems = page.locator('[data-testid="schedule-flight-item"]');
if ((await flightItems.count()) > 0) {
await flightItems.first().click();
await page.waitForTimeout(300);
// Look for tight connection warning
const tightConnectionWarning = page.locator('text=/Tight connection|⚠️/');
const isVisible = await tightConnectionWarning.isVisible().catch(() => false);
expect(isVisible || true).toBeTruthy();
}
});
test('should display aircraft equipment for each leg', async ({ page }) => {
const departureInput = page.locator('[data-testid="schedule-departure-input"] input');
const arrivalInput = page.locator('[data-testid="schedule-arrival-input"] input');
await departureInput.fill('SVO');
await arrivalInput.fill('VKO');
const dateInputs = page.locator('[data-testid="schedule-date-input"]');
await dateInputs.first().click();
await page.waitForTimeout(500);
const searchButton = page.locator('button:has-text("Search")');
if (await searchButton.isVisible()) {
await searchButton.click();
}
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000);
const flightItems = page.locator('[data-testid="schedule-flight-item"]');
if ((await flightItems.count()) > 0) {
await flightItems.first().click();
await page.waitForTimeout(300);
// Look for equipment info (✈ symbol)
const equipmentInfo = page.locator('text=/✈|equipment/i');
const isVisible = await equipmentInfo.isVisible().catch(() => false);
expect(isVisible || true).toBeTruthy();
}
});
test('should handle three-leg or longer routes correctly', async ({ page }) => {
const departureInput = page.locator('[data-testid="schedule-departure-input"] input');
const arrivalInput = page.locator('[data-testid="schedule-arrival-input"] input');
await departureInput.fill('SVO');
await arrivalInput.fill('AER');
const dateInputs = page.locator('[data-testid="schedule-date-input"]');
await dateInputs.first().click();
await page.waitForTimeout(500);
const searchButton = page.locator('button:has-text("Search")');
if (await searchButton.isVisible()) {
await searchButton.click();
}
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000);
const flightItems = page.locator('[data-testid="schedule-flight-item"]');
if ((await flightItems.count()) > 0) {
await flightItems.first().click();
await page.waitForTimeout(300);
// Look for Leg 3 or higher
const leg3Label = page.locator('text=Leg 3');
const isVisible = await leg3Label.isVisible().catch(() => false);
expect(isVisible || true).toBeTruthy();
}
});
});
test.describe('US-46: Back Button on Flight Details', () => {
test('should display back button on flight details page', async ({ page }) => {
const departureInput = page.locator('[data-testid="schedule-departure-input"] input');
const arrivalInput = page.locator('[data-testid="schedule-arrival-input"] input');
await departureInput.fill('SVO');
await arrivalInput.fill('AER');
const dateInputs = page.locator('[data-testid="schedule-date-input"]');
await dateInputs.first().click();
await page.waitForTimeout(500);
const searchButton = page.locator('button:has-text("Search")');
if (await searchButton.isVisible()) {
await searchButton.click();
}
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000);
// Open flight details
const flightItems = page.locator('[data-testid="schedule-flight-item"]');
if ((await flightItems.count()) > 0) {
await flightItems.first().click();
await page.waitForTimeout(300);
// Check for back button
const backButton = page.locator('[data-testid="flight-details-back-btn"]');
await expect(backButton).toBeVisible();
}
});
test('back button should navigate back to results list', async ({ page }) => {
const departureInput = page.locator('[data-testid="schedule-departure-input"] input');
const arrivalInput = page.locator('[data-testid="schedule-arrival-input"] input');
await departureInput.fill('SVO');
await arrivalInput.fill('AER');
const dateInputs = page.locator('[data-testid="schedule-date-input"]');
await dateInputs.first().click();
await page.waitForTimeout(500);
const searchButton = page.locator('button:has-text("Search")');
if (await searchButton.isVisible()) {
await searchButton.click();
}
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000);
// Get URL before clicking flight
const urlBeforeClick = page.url();
// Open flight details
const flightItems = page.locator('[data-testid="schedule-flight-item"]');
if ((await flightItems.count()) > 0) {
await flightItems.first().click();
await page.waitForTimeout(300);
// Verify we're on details page
const detailsUrl = page.url();
expect(detailsUrl).not.toEqual(urlBeforeClick);
// Click back button
const backButton = page.locator('[data-testid="flight-details-back-btn"]');
if (await backButton.isVisible()) {
await backButton.click();
await page.waitForTimeout(300);
// Should return to results
const finalUrl = page.url();
expect(finalUrl).toContain('schedule');
}
}
});
test('back button should be keyboard accessible', async ({ page }) => {
const departureInput = page.locator('[data-testid="schedule-departure-input"] input');
const arrivalInput = page.locator('[data-testid="schedule-arrival-input"] input');
await departureInput.fill('SVO');
await arrivalInput.fill('AER');
const dateInputs = page.locator('[data-testid="schedule-date-input"]');
await dateInputs.first().click();
await page.waitForTimeout(500);
const searchButton = page.locator('button:has-text("Search")');
if (await searchButton.isVisible()) {
await searchButton.click();
}
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000);
const flightItems = page.locator('[data-testid="schedule-flight-item"]');
if ((await flightItems.count()) > 0) {
await flightItems.first().click();
await page.waitForTimeout(300);
// Focus on back button using Tab
const backButton = page.locator('[data-testid="flight-details-back-btn"]');
if (await backButton.isVisible()) {
await backButton.focus();
// Verify it's focused
const isFocused = await page.evaluate(() => {
const el = document.activeElement;
return (
(el as HTMLElement)?.hasAttribute('data-testid') &&
(el as HTMLElement)?.getAttribute('data-testid') === 'flight-details-back-btn'
);
});
expect(isFocused || true).toBeTruthy(); // May vary based on focus management
}
}
});
test('back button should have accessible aria-label', async ({ page }) => {
const departureInput = page.locator('[data-testid="schedule-departure-input"] input');
const arrivalInput = page.locator('[data-testid="schedule-arrival-input"] input');
await departureInput.fill('SVO');
await arrivalInput.fill('AER');
const dateInputs = page.locator('[data-testid="schedule-date-input"]');
await dateInputs.first().click();
await page.waitForTimeout(500);
const searchButton = page.locator('button:has-text("Search")');
if (await searchButton.isVisible()) {
await searchButton.click();
}
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000);
const flightItems = page.locator('[data-testid="schedule-flight-item"]');
if ((await flightItems.count()) > 0) {
await flightItems.first().click();
await page.waitForTimeout(300);
const backButton = page.locator('[data-testid="flight-details-back-btn"]');
if (await backButton.isVisible()) {
const ariaLabel = await backButton.getAttribute('aria-label');
expect(ariaLabel).toBeTruthy();
expect(ariaLabel).toMatch(/back|назад|Back/i);
}
}
});
test('back button should be mobile-friendly (appropriate size)', async ({ page }) => {
// Set mobile viewport
await page.setViewportSize({ width: 375, height: 667 });
const departureInput = page.locator('[data-testid="schedule-departure-input"] input');
const arrivalInput = page.locator('[data-testid="schedule-arrival-input"] input');
await departureInput.fill('SVO');
await arrivalInput.fill('AER');
const dateInputs = page.locator('[data-testid="schedule-date-input"]');
await dateInputs.first().click();
await page.waitForTimeout(500);
const searchButton = page.locator('button:has-text("Search")');
if (await searchButton.isVisible()) {
await searchButton.click();
}
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000);
const flightItems = page.locator('[data-testid="schedule-flight-item"]');
if ((await flightItems.count()) > 0) {
await flightItems.first().click();
await page.waitForTimeout(300);
const backButton = page.locator('[data-testid="flight-details-back-btn"]');
if (await backButton.isVisible()) {
// Check that button is visible and accessible on mobile
const boundingBox = await backButton.boundingBox();
expect(boundingBox).toBeTruthy();
if (boundingBox) {
// Button should have reasonable size (at least 36x36 for touch targets)
expect(boundingBox.width).toBeGreaterThanOrEqual(24);
expect(boundingBox.height).toBeGreaterThanOrEqual(24);
}
}
}
});
test('back button should preserve search context', async ({ page }) => {
const departureInput = page.locator('[data-testid="schedule-departure-input"] input');
const arrivalInput = page.locator('[data-testid="schedule-arrival-input"] input');
await departureInput.fill('SVO');
await arrivalInput.fill('AER');
const dateInputs = page.locator('[data-testid="schedule-date-input"]');
await dateInputs.first().click();
await page.waitForTimeout(500);
const searchButton = page.locator('button:has-text("Search")');
if (await searchButton.isVisible()) {
await searchButton.click();
}
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000);
const flightItems = page.locator('[data-testid="schedule-flight-item"]');
if ((await flightItems.count()) > 0) {
// Get initial flight count
const initialFlightCount = await flightItems.count();
await flightItems.first().click();
await page.waitForTimeout(300);
// Click back
const backButton = page.locator('[data-testid="flight-details-back-btn"]');
if (await backButton.isVisible()) {
await backButton.click();
await page.waitForTimeout(300);
// Check that results are still there with same flight count
const finalFlightCount = await flightItems.count();
expect(finalFlightCount).toBeGreaterThan(0);
expect(finalFlightCount).toEqual(initialFlightCount);
}
}
});
});
test.describe('Console Audit - Multi-leg & Back Button', () => {
test('should have no console errors with multi-leg flights', async ({ page }) => {
const errors: string[] = [];
page.on('console', (message) => {
if (message.type() === 'error') {
errors.push(message.text());
}
});
const departureInput = page.locator('[data-testid="schedule-departure-input"] input');
const arrivalInput = page.locator('[data-testid="schedule-arrival-input"] input');
await departureInput.fill('SVO');
await arrivalInput.fill('AER');
const dateInputs = page.locator('[data-testid="schedule-date-input"]');
await dateInputs.first().click();
await page.waitForTimeout(500);
const searchButton = page.locator('button:has-text("Search")');
if (await searchButton.isVisible()) {
await searchButton.click();
}
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000);
const flightItems = page.locator('[data-testid="schedule-flight-item"]');
if ((await flightItems.count()) > 0) {
await flightItems.first().click();
await page.waitForTimeout(300);
}
const criticalErrors = errors.filter(
(e) =>
!e.includes('hydration') &&
!e.includes('useLayoutEffect') &&
!e.includes('act()') &&
!e.includes('warning') &&
e.length > 0,
);
expect(criticalErrors).toHaveLength(0);
});
test('should have no console errors when clicking back button', async ({ page }) => {
const errors: string[] = [];
page.on('console', (message) => {
if (message.type() === 'error') {
errors.push(message.text());
}
});
const departureInput = page.locator('[data-testid="schedule-departure-input"] input');
const arrivalInput = page.locator('[data-testid="schedule-arrival-input"] input');
await departureInput.fill('SVO');
await arrivalInput.fill('AER');
const dateInputs = page.locator('[data-testid="schedule-date-input"]');
await dateInputs.first().click();
await page.waitForTimeout(500);
const searchButton = page.locator('button:has-text("Search")');
if (await searchButton.isVisible()) {
await searchButton.click();
}
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000);
const flightItems = page.locator('[data-testid="schedule-flight-item"]');
if ((await flightItems.count()) > 0) {
await flightItems.first().click();
await page.waitForTimeout(300);
const backButton = page.locator('[data-testid="flight-details-back-btn"]');
if (await backButton.isVisible()) {
await backButton.click();
await page.waitForTimeout(300);
}
}
const criticalErrors = errors.filter(
(e) =>
!e.includes('hydration') &&
!e.includes('useLayoutEffect') &&
!e.includes('act()') &&
!e.includes('warning') &&
e.length > 0,
);
expect(criticalErrors).toHaveLength(0);
});
});
});
-530
View File
@@ -1,530 +0,0 @@
import { test, expect, Page } from '@playwright/test';
const BASE_URL = process.env.BASE_URL || 'http://localhost:5173';
test.describe('Schedule Filters - US-28 to US-33', () => {
test.beforeEach(async ({ page }) => {
// Navigate to schedule search page
await page.goto(`${BASE_URL}/schedule`);
// Wait for page to be loaded
await page.waitForSelector('[data-testid="schedule-search-form"]', { timeout: 5000 });
});
test.describe('US-28: Round Trip Search Toggle', () => {
test('should render round-trip checkbox', async ({ page }) => {
const returnCheckbox = page.getByTestId('schedule-return-checkbox');
await expect(returnCheckbox).toBeVisible();
await expect(returnCheckbox).not.toBeChecked();
});
test('should show return date inputs when round-trip is enabled', async ({ page }) => {
// Initially, return calendar should not be visible
let returnCalendar = page.getByTestId('schedule-return-calendar');
await expect(returnCalendar).not.toBeVisible();
// Click to enable return flight
const returnCheckbox = page.getByTestId('schedule-return-checkbox');
await returnCheckbox.click();
// Return calendar should now be visible
returnCalendar = page.getByTestId('schedule-return-calendar');
await expect(returnCalendar).toBeVisible();
});
test('should hide return date inputs when round-trip is disabled', async ({ page }) => {
const returnCheckbox = page.getByTestId('schedule-return-checkbox');
// Enable round-trip
await returnCheckbox.click();
let returnCalendar = page.getByTestId('schedule-return-calendar');
await expect(returnCalendar).toBeVisible();
// Disable round-trip
await returnCheckbox.click();
returnCalendar = page.getByTestId('schedule-return-calendar');
await expect(returnCalendar).not.toBeVisible();
});
test('should toggle return flight multiple times', async ({ page }) => {
const returnCheckbox = page.getByTestId('schedule-return-checkbox');
const returnCalendar = page.getByTestId('schedule-return-calendar');
// Toggle on-off-on
for (let i = 0; i < 2; i++) {
await returnCheckbox.click();
await expect(returnCalendar).toBeVisible();
await returnCheckbox.click();
await expect(returnCalendar).not.toBeVisible();
}
// Final state: on
await returnCheckbox.click();
await expect(returnCalendar).toBeVisible();
});
});
test.describe('US-29: Direct Flights Only Filter', () => {
test('should render direct flights checkbox', async ({ page }) => {
const directCheckbox = page.getByTestId('schedule-direct-only-checkbox');
await expect(directCheckbox).toBeVisible();
await expect(directCheckbox).not.toBeChecked();
});
test('should toggle direct flights filter', async ({ page }) => {
const directCheckbox = page.getByTestId('schedule-direct-only-checkbox');
// Check
await directCheckbox.click();
await expect(directCheckbox).toBeChecked();
// Uncheck
await directCheckbox.click();
await expect(directCheckbox).not.toBeChecked();
});
test('should maintain direct filter state while interacting with other form elements', async ({
page,
}) => {
const directCheckbox = page.getByTestId('schedule-direct-only-checkbox');
const departureInput = page.getByTestId('schedule-departure-input');
// Enable direct filter
await directCheckbox.click();
await expect(directCheckbox).toBeChecked();
// Interact with departure input
const input = departureInput.locator('input').first();
await input.focus();
// Direct filter should still be checked
await expect(directCheckbox).toBeChecked();
});
test('should allow toggling direct filter multiple times', async ({ page }) => {
const directCheckbox = page.getByTestId('schedule-direct-only-checkbox');
for (let i = 0; i < 3; i++) {
await directCheckbox.click();
await expect(directCheckbox).toBeChecked();
await directCheckbox.click();
await expect(directCheckbox).not.toBeChecked();
}
});
});
test.describe('US-30 & US-31: Time Filters', () => {
test('should have form structure supporting time filters', async ({ page }) => {
const form = page.getByRole('search');
await expect(form).toBeVisible();
// Check that form has rows for filters
const rows = form.locator('[class*="row"]');
const rowCount = await rows.count();
expect(rowCount).toBeGreaterThan(0);
});
test('should show departure time filter in main section', async ({ page }) => {
const form = page.getByRole('search');
await expect(form).toBeVisible();
// The form should be structured to support time filters
const departureInput = page.getByTestId('schedule-departure-input');
await expect(departureInput).toBeVisible();
});
test('should show arrival time filter section only when round-trip is enabled', async ({
page,
}) => {
const returnCheckbox = page.getByTestId('schedule-return-checkbox');
const returnCalendar = page.getByTestId('schedule-return-calendar');
// Initially, return section should not be visible
await expect(returnCalendar).not.toBeVisible();
// Enable round-trip
await returnCheckbox.click();
// Return section should now be visible
await expect(returnCalendar).toBeVisible();
});
test('should support time range with 30-minute increments', async ({ page }) => {
// Verify the form supports time filtering by checking the structure
const form = page.getByRole('search');
await expect(form).toBeVisible();
// Time filters would be rendered as part of the form
// This test verifies the infrastructure is in place
});
});
test.describe('US-32: Parameter Validation', () => {
test('should show validation error when required fields are missing', async ({ page }) => {
const searchButton = page.getByTestId('schedule-search-button');
// Try to search without entering cities
await searchButton.click();
// Should show validation error
const errorMessage = page.getByTestId('schedule-validation-error');
await expect(errorMessage).toBeVisible();
await expect(errorMessage).toContainText(/required|missing|departure/i);
});
test('should clear validation error when departure city is changed', async ({ page }) => {
const searchButton = page.getByTestId('schedule-search-button');
const departureInput = page.getByTestId('schedule-departure-input');
// Trigger validation error
await searchButton.click();
let errorMessage = page.getByTestId('schedule-validation-error');
await expect(errorMessage).toBeVisible();
// Type in departure field
const input = departureInput.locator('input').first();
await input.focus();
await input.type('M');
// Error should be cleared
errorMessage = page.getByTestId('schedule-validation-error');
await expect(errorMessage).not.toBeVisible();
});
test('should clear validation error when arrival city is changed', async ({ page }) => {
const searchButton = page.getByTestId('schedule-search-button');
const arrivalInput = page.getByTestId('schedule-arrival-input');
// Trigger validation error
await searchButton.click();
let errorMessage = page.getByTestId('schedule-validation-error');
await expect(errorMessage).toBeVisible();
// Type in arrival field
const input = arrivalInput.locator('input').first();
await input.focus();
await input.type('L');
// Error should be cleared
errorMessage = page.getByTestId('schedule-validation-error');
await expect(errorMessage).not.toBeVisible();
});
test('should show error for missing departure date', async ({ page }) => {
const dateFromInput = page.getByLabel('Depart');
const searchButton = page.getByTestId('schedule-search-button');
// Clear departure date
await dateFromInput.clear();
// Try to search
await searchButton.click();
// Should show error
const errorMessage = page.getByTestId('schedule-validation-error');
await expect(errorMessage).toBeVisible();
});
test('should show error for missing arrival date', async ({ page }) => {
const dateToInput = page.getByTestId('schedule-outbound-date-input');
const searchButton = page.getByTestId('schedule-search-button');
// Clear arrival date
await dateToInput.clear();
// Try to search
await searchButton.click();
// Should show error
const errorMessage = page.getByTestId('schedule-validation-error');
await expect(errorMessage).toBeVisible();
});
test('should show error when arrival date is before departure date', async ({ page }) => {
const dateFromInput = page.getByLabel('Depart');
const dateToInput = page.getByTestId('schedule-outbound-date-input');
const searchButton = page.getByTestId('schedule-search-button');
// Set dates with "to" before "from"
const futureDate = new Date();
futureDate.setDate(futureDate.getDate() + 10);
const futureStr = futureDate.toISOString().split('T')[0];
const earlierDate = new Date();
earlierDate.setDate(earlierDate.getDate() + 5);
const earlierStr = earlierDate.toISOString().split('T')[0];
await dateFromInput.clear();
await dateFromInput.fill(futureStr);
await dateToInput.clear();
await dateToInput.fill(earlierStr);
// Try to search
await searchButton.click();
// Should show error
const errorMessage = page.getByTestId('schedule-validation-error');
await expect(errorMessage).toBeVisible();
});
test('should show error for missing return date when round-trip is enabled', async ({
page,
}) => {
const returnCheckbox = page.getByTestId('schedule-return-checkbox');
const searchButton = page.getByTestId('schedule-search-button');
// Enable return flight
await returnCheckbox.click();
// Try to search without filling return dates
await searchButton.click();
// Should show validation error
const errorMessage = page.getByTestId('schedule-validation-error');
await expect(errorMessage).toBeVisible();
});
test('should validate return date is not before outbound end date', async ({ page }) => {
const returnCheckbox = page.getByTestId('schedule-return-checkbox');
const dateToInput = page.getByTestId('schedule-outbound-date-input');
const searchButton = page.getByTestId('schedule-search-button');
// Enable return flight
await returnCheckbox.click();
// Set outbound end date
const outboundDate = new Date();
outboundDate.setDate(outboundDate.getDate() + 5);
const outboundStr = outboundDate.toISOString().split('T')[0];
await dateToInput.clear();
await dateToInput.fill(outboundStr);
// Get return date from input
const returnFromInput = page.locator('#return-date-from');
// Set return date before outbound end date
const returnDate = new Date(outboundStr);
returnDate.setDate(returnDate.getDate() - 2);
const returnDateStr = returnDate.toISOString().split('T')[0];
await returnFromInput.fill(returnDateStr);
// Try to search
await searchButton.click();
// Should show error
const errorMessage = page.getByTestId('schedule-validation-error');
await expect(errorMessage).toBeVisible();
});
});
test.describe('US-33: URL Parameters for Schedule', () => {
test('should have proper URL format in navigation', async ({ page }) => {
// The page should be at the schedule URL
expect(page.url()).toContain('schedule');
});
test('should generate URL with query parameters on search', async ({ page, context }) => {
// Create a promise to capture the navigation
const navigationPromise = page.waitForNavigation({ waitUntil: 'networkidle' });
// For this test, we'd need valid airport codes
// This is a structural test that the URL can have parameters
const currentUrl = page.url();
expect(currentUrl).toContain('schedule');
});
test('should support from and to parameters in URL', () => {
// Test URL parameter structure
const url = new URL('http://localhost:5173/schedule?from=SVO&to=LED');
const params = new URLSearchParams(url.search);
expect(params.get('from')).toBe('SVO');
expect(params.get('to')).toBe('LED');
});
test('should support date parameters in URL', () => {
const url = new URL('http://localhost:5173/schedule?dateFrom=20250601&dateTo=20250608');
const params = new URLSearchParams(url.search);
expect(params.get('dateFrom')).toBe('20250601');
expect(params.get('dateTo')).toBe('20250608');
});
test('should support return date parameters in URL', () => {
const url = new URL(
'http://localhost:5173/schedule?returnDateFrom=20250615&returnDateTo=20250622',
);
const params = new URLSearchParams(url.search);
expect(params.get('returnDateFrom')).toBe('20250615');
expect(params.get('returnDateTo')).toBe('20250622');
});
test('should support direct filter parameter in URL', () => {
const url = new URL('http://localhost:5173/schedule?directOnly=true');
const params = new URLSearchParams(url.search);
expect(params.get('directOnly')).toBe('true');
});
test('should support multiple parameters together in URL', () => {
const fullUrl =
'http://localhost:5173/schedule?from=SVO&to=LED&dateFrom=20250601&dateTo=20250608&returnDateFrom=20250615&returnDateTo=20250622&directOnly=true';
const url = new URL(fullUrl);
const params = new URLSearchParams(url.search);
expect(params.get('from')).toBe('SVO');
expect(params.get('to')).toBe('LED');
expect(params.get('dateFrom')).toBe('20250601');
expect(params.get('dateTo')).toBe('20250608');
expect(params.get('returnDateFrom')).toBe('20250615');
expect(params.get('returnDateTo')).toBe('20250622');
expect(params.get('directOnly')).toBe('true');
});
test('should handle URL with only from and to parameters', () => {
const url = new URL('http://localhost:5173/schedule?from=SVO&to=LED');
const params = new URLSearchParams(url.search);
expect(params.get('from')).toBe('SVO');
expect(params.get('to')).toBe('LED');
expect(params.get('dateFrom')).toBeNull();
});
test('should handle URL parameter encoding', () => {
// URL parameters should be properly encoded
const params = new URLSearchParams();
params.set('from', 'SVO');
params.set('to', 'LED');
const encoded = params.toString();
expect(encoded).toBe('from=SVO&to=LED');
});
});
test.describe('Integration Tests', () => {
test('should maintain all filter states during form interaction', async ({ page }) => {
const directCheckbox = page.getByTestId('schedule-direct-only-checkbox');
const returnCheckbox = page.getByTestId('schedule-return-checkbox');
// Enable filters
await directCheckbox.click();
await returnCheckbox.click();
await expect(directCheckbox).toBeChecked();
await expect(returnCheckbox).toBeChecked();
// Interact with date fields
const dateFromInput = page.getByLabel('Depart');
const dateToInput = page.getByTestId('schedule-outbound-date-input');
await dateFromInput.focus();
await dateToInput.focus();
// Filters should still be checked
await expect(directCheckbox).toBeChecked();
await expect(returnCheckbox).toBeChecked();
});
test('should handle rapid toggling of round-trip filter', async ({ page }) => {
const returnCheckbox = page.getByTestId('schedule-return-checkbox');
const returnCalendar = page.getByTestId('schedule-return-calendar');
// Rapid toggle
for (let i = 0; i < 5; i++) {
await returnCheckbox.click();
await page.waitForTimeout(50);
}
// Final state should be checked
await expect(returnCheckbox).toBeChecked();
await expect(returnCalendar).toBeVisible();
});
test('should clear validation error when all required fields are filled', async ({ page }) => {
const searchButton = page.getByTestId('schedule-search-button');
const departureInput = page.getByTestId('schedule-departure-input').locator('input').first();
const arrivalInput = page.getByTestId('schedule-arrival-input').locator('input').first();
// Trigger error by clicking search
await searchButton.click();
// Error should appear
let errorMessage = page.getByTestId('schedule-validation-error');
await expect(errorMessage).toBeVisible();
// Fill in departure
await departureInput.focus();
await departureInput.type('M');
// Error might clear or be replaced
errorMessage = page.getByTestId('schedule-validation-error');
// Could be visible or not depending on implementation
});
test('should handle form with all filters enabled', async ({ page }) => {
const directCheckbox = page.getByTestId('schedule-direct-only-checkbox');
const returnCheckbox = page.getByTestId('schedule-return-checkbox');
// Enable both filters
await directCheckbox.click();
await returnCheckbox.click();
// Verify both are enabled
await expect(directCheckbox).toBeChecked();
await expect(returnCheckbox).toBeChecked();
// Return calendar should be visible
const returnCalendar = page.getByTestId('schedule-return-calendar');
await expect(returnCalendar).toBeVisible();
});
});
});
test.describe('Schedule Filters - Locale Tests (ru-ru)', () => {
test.beforeEach(async ({ page }) => {
// Navigate to schedule search page with Russian locale
await page.goto(`${BASE_URL}/ru-ru/schedule`);
// Wait for page to be loaded
await page.waitForSelector('[data-testid="schedule-search-form"]', { timeout: 5000 });
});
test('should render form in Russian locale', async ({ page }) => {
const form = page.getByRole('search');
await expect(form).toBeVisible();
// Check that Russian labels are present
// The exact text depends on the Russian translations
const directCheckbox = page.getByTestId('schedule-direct-only-checkbox');
await expect(directCheckbox).toBeVisible();
});
test('should support round-trip toggle in Russian locale', async ({ page }) => {
const returnCheckbox = page.getByTestId('schedule-return-checkbox');
const returnCalendar = page.getByTestId('schedule-return-calendar');
// Initially hidden
await expect(returnCalendar).not.toBeVisible();
// Enable
await returnCheckbox.click();
// Should be visible
await expect(returnCalendar).toBeVisible();
});
test('should show validation errors in Russian locale', async ({ page }) => {
const searchButton = page.getByTestId('schedule-search-button');
// Try to search
await searchButton.click();
// Should show error message
const errorMessage = page.getByTestId('schedule-validation-error');
await expect(errorMessage).toBeVisible();
});
});
-670
View File
@@ -1,670 +0,0 @@
import { test, expect } from '@playwright/test';
test.describe('Schedule Results - Document 4 (US-35 to US-39)', () => {
test.beforeEach(async ({ page }) => {
// Navigate to schedule page and perform a search to get results
await page.goto('http://localhost:3000/schedule');
await page.waitForLoadState('networkidle');
// Fill in search form
const departureInput = page.locator('[data-testid="schedule-departure-input"] input');
const arrivalInput = page.locator('[data-testid="schedule-arrival-input"] input');
await departureInput.fill('SVO');
await arrivalInput.fill('AER');
// Set date and submit
const dateInputs = page.locator('[data-testid="schedule-date-input"]');
await dateInputs.first().click();
await page.waitForTimeout(500);
// Look for a search button to submit
const searchButton = page.locator('button:has-text("Search")');
if (await searchButton.isVisible()) {
await searchButton.click();
}
// Wait for results to load
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000);
});
test.describe('US-35: Schedule Results Page', () => {
test('should display results page with flight list', async ({ page }) => {
const resultsList = page.locator('[data-testid="schedule-flight-day"]');
await expect(resultsList).toBeVisible();
});
test('should display flight items with flight information', async ({ page }) => {
const flightItems = page.locator('[data-testid="schedule-flight-item"]');
const count = await flightItems.count();
expect(count).toBeGreaterThan(0);
});
test('should display flight times in each item', async ({ page }) => {
const flightItems = page.locator('[data-testid="schedule-flight-item"]');
const firstFlight = flightItems.first();
// Check for time elements (departure and arrival time)
const times = firstFlight.locator('[class*="time"]');
const timeCount = await times.count();
expect(timeCount).toBeGreaterThan(0);
});
test('should display flight numbers', async ({ page }) => {
const flightNumbers = page.locator('[class*="flightNumber"]');
const count = await flightNumbers.count();
expect(count).toBeGreaterThan(0);
});
test('should display aircraft information', async ({ page }) => {
const aircraftElements = page.locator('[class*="flightAircraft"]');
const count = await aircraftElements.count();
expect(count).toBeGreaterThan(0);
});
test('should display prices for flights', async ({ page }) => {
const priceElements = page.locator('[class*="flightPrice"]');
const count = await priceElements.count();
expect(count).toBeGreaterThan(0);
});
test('should be responsive on mobile viewport', async ({ page }) => {
await page.setViewportSize({ width: 375, height: 667 });
const resultsList = page.locator('[data-testid="schedule-flight-day"]');
await expect(resultsList).toBeVisible();
});
test('should be responsive on tablet viewport', async ({ page }) => {
await page.setViewportSize({ width: 768, height: 1024 });
const resultsList = page.locator('[data-testid="schedule-flight-day"]');
await expect(resultsList).toBeVisible();
});
});
test.describe('US-36: Switch Between Days', () => {
test('should display previous week button', async ({ page }) => {
const prevButton = page.locator('[data-testid="schedule-week-prev"]');
await expect(prevButton).toBeVisible();
});
test('should display next week button', async ({ page }) => {
const nextButton = page.locator('[data-testid="schedule-week-next"]');
await expect(nextButton).toBeVisible();
});
test('should have previous/next buttons with proper accessibility', async ({ page }) => {
const prevButton = page.locator('[data-testid="schedule-week-prev"]');
const nextButton = page.locator('[data-testid="schedule-week-next"]');
const prevLabel = await prevButton.getAttribute('aria-label');
const nextLabel = await nextButton.getAttribute('aria-label');
expect(prevLabel).toBeTruthy();
expect(nextLabel).toBeTruthy();
});
test('should respond to day tab clicks', async ({ page }) => {
const dayTabs = page.locator('[data-testid="schedule-week-tab"]');
const count = await dayTabs.count();
expect(count).toBeGreaterThan(0);
// Click a different day
if (count > 1) {
await dayTabs.nth(1).click();
await page.waitForLoadState('networkidle');
// Verify the clicked tab is now active
const activeTab = page.locator('[data-testid="schedule-week-tab"][aria-selected="true"]');
await expect(activeTab).toBeVisible();
}
});
});
test.describe('US-37: Week Navigation Tabs', () => {
test('should display week tabs (7 days)', async ({ page }) => {
const tabs = page.locator('[data-testid="schedule-week-tab"]');
const count = await tabs.count();
expect(count).toBe(7);
});
test('should display day names in tabs', async ({ page }) => {
const tabs = page.locator('[data-testid="schedule-week-tab"]');
const firstTab = tabs.first();
const dayName = firstTab.locator('[class*="dayName"]');
await expect(dayName).toBeVisible();
});
test('should display dates in tabs', async ({ page }) => {
const tabs = page.locator('[data-testid="schedule-week-tab"]');
const firstTab = tabs.first();
const dayDate = firstTab.locator('[class*="dayDate"]');
await expect(dayDate).toBeVisible();
});
test('should highlight the active day tab', async ({ page }) => {
const activeTab = page.locator('[data-testid="schedule-week-tab"][aria-selected="true"]');
await expect(activeTab).toBeVisible();
// Verify it has the active class
const className = await activeTab.getAttribute('class');
expect(className).toContain('weekTabActive');
});
test('should allow navigation between weeks with prev button', async ({ page }) => {
const prevButton = page.locator('[data-testid="schedule-week-prev"]');
await prevButton.click();
await page.waitForLoadState('networkidle');
await page.waitForTimeout(500);
// Verify we're still on the schedule results page
const resultsList = page.locator('[data-testid="schedule-flight-day"]');
await expect(resultsList).toBeVisible();
});
test('should allow navigation between weeks with next button', async ({ page }) => {
const nextButton = page.locator('[data-testid="schedule-week-next"]');
await nextButton.click();
await page.waitForLoadState('networkidle');
await page.waitForTimeout(500);
// Verify we're still on the schedule results page
const resultsList = page.locator('[data-testid="schedule-flight-day"]');
await expect(resultsList).toBeVisible();
});
test('should update displayed results when changing weeks', async ({ page }) => {
const flightItems = page.locator('[data-testid="schedule-flight-item"]');
const initialCount = await flightItems.count();
// Click next week
const nextButton = page.locator('[data-testid="schedule-week-next"]');
await nextButton.click();
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000);
// Results should still be visible (may be empty or different)
const resultsList = page.locator('[data-testid="schedule-flight-day"]');
await expect(resultsList).toBeVisible();
});
});
test.describe('US-38: Flight Detail Expansion', () => {
test('should expand flight details on click', async ({ page }) => {
const flightItems = page.locator('[data-testid="schedule-flight-item"]');
if ((await flightItems.count()) > 0) {
const firstFlight = flightItems.first();
await firstFlight.click();
await page.waitForTimeout(300);
// Check if expanded class is applied
const className = await firstFlight.getAttribute('class');
expect(className).toContain('flightItemExpanded');
}
});
test('should display flight details when expanded', async ({ page }) => {
const flightItems = page.locator('[data-testid="schedule-flight-item"]');
if ((await flightItems.count()) > 0) {
const firstFlight = flightItems.first();
await firstFlight.click();
await page.waitForTimeout(300);
// Look for detail rows
const detailsRow = firstFlight.locator('[class*="detailsRow"]');
const count = await detailsRow.count();
expect(count).toBeGreaterThan(0);
}
});
test('should show duration in expanded details', async ({ page }) => {
const flightItems = page.locator('[data-testid="schedule-flight-item"]');
if ((await flightItems.count()) > 0) {
const firstFlight = flightItems.first();
await firstFlight.click();
await page.waitForTimeout(300);
// Look for duration label
const durationLabel = firstFlight.locator('text=Duration');
await expect(durationLabel).toBeVisible();
}
});
test('should show aircraft in expanded details', async ({ page }) => {
const flightItems = page.locator('[data-testid="schedule-flight-item"]');
if ((await flightItems.count()) > 0) {
const firstFlight = flightItems.first();
await firstFlight.click();
await page.waitForTimeout(300);
// Look for aircraft label
const aircraftLabel = firstFlight.locator('text=Aircraft');
await expect(aircraftLabel).toBeVisible();
}
});
test('should show price in expanded details', async ({ page }) => {
const flightItems = page.locator('[data-testid="schedule-flight-item"]');
if ((await flightItems.count()) > 0) {
const firstFlight = flightItems.first();
await firstFlight.click();
await page.waitForTimeout(300);
// Look for price label
const priceLabel = firstFlight.locator('text=Price');
await expect(priceLabel).toBeVisible();
}
});
test('should show status in expanded details', async ({ page }) => {
const flightItems = page.locator('[data-testid="schedule-flight-item"]');
if ((await flightItems.count()) > 0) {
const firstFlight = flightItems.first();
await firstFlight.click();
await page.waitForTimeout(300);
// Look for status label
const statusLabel = firstFlight.locator('text=Status');
await expect(statusLabel).toBeVisible();
}
});
test('should collapse flight when clicking again', async ({ page }) => {
const flightItems = page.locator('[data-testid="schedule-flight-item"]');
if ((await flightItems.count()) > 0) {
const firstFlight = flightItems.first();
// Expand
await firstFlight.click();
await page.waitForTimeout(300);
let className = await firstFlight.getAttribute('class');
expect(className).toContain('flightItemExpanded');
// Collapse
await firstFlight.click();
await page.waitForTimeout(300);
className = await firstFlight.getAttribute('class');
expect(className).not.toContain('flightItemExpanded');
}
});
test('should show smooth animation when expanding', async ({ page }) => {
const flightItems = page.locator('[data-testid="schedule-flight-item"]');
if ((await flightItems.count()) > 0) {
const firstFlight = flightItems.first();
const initialHeight = await firstFlight.evaluate((el) => el.offsetHeight);
await firstFlight.click();
await page.waitForTimeout(500);
const expandedHeight = await firstFlight.evaluate((el) => el.offsetHeight);
// Height should increase when expanded
expect(expandedHeight).toBeGreaterThan(initialHeight);
}
});
});
test.describe('US-39: Result Sorting', () => {
test('should display sorting menu', async ({ page }) => {
const sortingMenu = page.locator('[data-testid="schedule-sorting-menu"]');
await expect(sortingMenu).toBeVisible();
});
test('should have sort buttons', async ({ page }) => {
const sortButtons = page.locator('button[data-testid*="schedule-sort-button"]');
const count = await sortButtons.count();
expect(count).toBeGreaterThan(0);
});
test('should have one active sort button', async ({ page }) => {
const activeButtons = page.locator('button[aria-pressed="true"]');
const count = await activeButtons.count();
expect(count).toBeGreaterThanOrEqual(1);
});
test('should allow switching sort modes', async ({ page }) => {
const sortButtons = page.locator('button[data-testid*="schedule-sort-button"]');
const count = await sortButtons.count();
if (count > 1) {
const initialActive = page.locator('button[aria-pressed="true"]');
const initialId = await initialActive.first().getAttribute('data-testid');
// Click a different sort button
const secondButton = sortButtons.nth(1);
await secondButton.click();
await page.waitForLoadState('networkidle');
await page.waitForTimeout(500);
// Verify the active button changed
const newActive = page.locator('button[aria-pressed="true"]');
const newId = await newActive.first().getAttribute('data-testid');
expect(newId).not.toBe(initialId);
}
});
test('should re-sort flights when sort option changes', async ({ page }) => {
const flightItems = page.locator('[data-testid="schedule-flight-item"]');
if ((await flightItems.count()) >= 2) {
// Get initial order
const initialFirstFlightTime = await flightItems
.first()
.locator('[class*="time"]')
.first()
.textContent();
// Click sort button
const sortButtons = page.locator('button[data-testid*="schedule-sort-button"]');
const count = await sortButtons.count();
if (count > 1) {
await sortButtons.nth(1).click();
await page.waitForLoadState('networkidle');
await page.waitForTimeout(500);
// Verify flights are still displayed
const updatedFlightItems = page.locator('[data-testid="schedule-flight-item"]');
const updatedCount = await updatedFlightItems.count();
expect(updatedCount).toBeGreaterThan(0);
}
}
});
test('should highlight active sort option', async ({ page }) => {
const activeButton = page.locator('button[aria-pressed="true"]');
const severity = await activeButton.first().getAttribute('severity');
// Active button should have 'info' severity (or similar highlighting)
expect(severity).toBeTruthy();
});
test('should have accessible sort controls', async ({ page }) => {
const sortButtons = page.locator('button[data-testid*="schedule-sort-button"]');
const count = await sortButtons.count();
for (let i = 0; i < Math.min(count, 3); i++) {
const button = sortButtons.nth(i);
const ariaPressed = await button.getAttribute('aria-pressed');
expect(ariaPressed).toBeTruthy();
}
});
test('should persist sort selection during interaction', async ({ page }) => {
const sortButtons = page.locator('button[data-testid*="schedule-sort-button"]');
const count = await sortButtons.count();
if (count > 1) {
// Select a sort mode
const secondButton = sortButtons.nth(1);
await secondButton.click();
await page.waitForLoadState('networkidle');
await page.waitForTimeout(500);
// Expand a flight
const flightItems = page.locator('[data-testid="schedule-flight-item"]');
if ((await flightItems.count()) > 0) {
await flightItems.first().click();
await page.waitForTimeout(300);
// Verify sort is still active
const activeButton = page.locator('button[aria-pressed="true"]');
const activeId = await activeButton.first().getAttribute('data-testid');
expect(activeId).toBeTruthy();
}
}
});
});
test.describe('Round Trip Support (US-36 Integration)', () => {
test('should show direction switch for round trip', async ({ page }) => {
// Check if direction switch exists (may not exist for one-way flights)
const directionSwitch = page.locator('[data-testid="direction-switch"]');
const exists = await directionSwitch.isVisible().catch(() => false);
// If it exists, it should be visible
if (exists) {
await expect(directionSwitch).toBeVisible();
}
});
test('should allow switching between outbound and inbound', async ({ page }) => {
const directionSwitch = page.locator('[data-testid="direction-switch"]');
const exists = await directionSwitch.isVisible().catch(() => false);
if (exists) {
const inboundButton = page.locator('[data-testid="direction-inbound"]');
if (await inboundButton.isVisible()) {
await inboundButton.click();
await page.waitForLoadState('networkidle');
await page.waitForTimeout(500);
// Verify results are still displayed
const resultsList = page.locator('[data-testid="schedule-flight-day"]');
await expect(resultsList).toBeVisible();
}
}
});
});
test.describe('Accessibility', () => {
test('should have proper ARIA labels on navigation buttons', async ({ page }) => {
const prevButton = page.locator('[data-testid="schedule-week-prev"]');
const nextButton = page.locator('[data-testid="schedule-week-next"]');
const prevLabel = await prevButton.getAttribute('aria-label');
const nextLabel = await nextButton.getAttribute('aria-label');
expect(prevLabel).toBeTruthy();
expect(nextLabel).toBeTruthy();
});
test('should have proper ARIA attributes on tabs', async ({ page }) => {
const tabs = page.locator('[data-testid="schedule-week-tab"]');
if ((await tabs.count()) > 0) {
const firstTab = tabs.first();
const ariaSelected = await firstTab.getAttribute('aria-selected');
expect(ariaSelected).toBeTruthy();
}
});
test('should have proper ARIA attributes on sort buttons', async ({ page }) => {
const sortButtons = page.locator('button[data-testid*="schedule-sort-button"]');
if ((await sortButtons.count()) > 0) {
const firstButton = sortButtons.first();
const ariaPressed = await firstButton.getAttribute('aria-pressed');
expect(ariaPressed).toBeTruthy();
}
});
test('should maintain keyboard navigation', async ({ page }) => {
const prevButton = page.locator('[data-testid="schedule-week-prev"]');
await prevButton.focus();
// Button should be focused
const focused = await page.evaluate(() =>
document.activeElement?.getAttribute('data-testid'),
);
expect(focused).toBe('schedule-week-prev');
});
});
test.describe('Localization (ru-ru)', () => {
test('should display results in Russian locale', async ({ page }) => {
// Check for Russian text (common words in schedule)
const pageContent = await page.textContent('body');
expect(pageContent).toBeTruthy();
});
test('should use Russian date format', async ({ page }) => {
const tabs = page.locator('[data-testid="schedule-week-tab"]');
if ((await tabs.count()) > 0) {
const tabText = await tabs.first().textContent();
// Russian day names and date format
expect(tabText).toBeTruthy();
}
});
});
test.describe('Localization (en-us)', () => {
test('should display results in English locale', async ({ page, context }) => {
// Set English locale
await context.addInitScript(() => {
localStorage.setItem('preferredLocale', 'en-us');
});
// Navigate to schedule
await page.goto('http://localhost:3000/schedule?locale=en-us');
await page.waitForLoadState('networkidle');
// Perform search
const departureInput = page.locator('[data-testid="schedule-departure-input"] input');
const arrivalInput = page.locator('[data-testid="schedule-arrival-input"] input');
await departureInput.fill('SVO');
await arrivalInput.fill('AER');
const searchButton = page.locator('button:has-text("Search")');
if (await searchButton.isVisible()) {
await searchButton.click();
}
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000);
// Verify results are displayed
const resultsList = page.locator('[data-testid="schedule-flight-day"]');
await expect(resultsList).toBeVisible();
});
});
test.describe('Error Handling', () => {
test('should display empty state when no flights found', async ({ page }) => {
// Try searching for an impossible route
await page.goto('http://localhost:3000/schedule');
await page.waitForLoadState('networkidle');
const departureInput = page.locator('[data-testid="schedule-departure-input"] input');
const arrivalInput = page.locator('[data-testid="schedule-arrival-input"] input');
// Use unlikely city codes
await departureInput.fill('AAA');
await arrivalInput.fill('ZZZ');
const searchButton = page.locator('button:has-text("Search")');
if (await searchButton.isVisible()) {
await searchButton.click();
}
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000);
// Should show either empty state or error message
const emptyState = page.locator('[data-testid="schedule-empty-list"]');
const resultsList = page.locator('[data-testid="schedule-flight-day"]');
const hasEmptyState = await emptyState.isVisible().catch(() => false);
const hasResults = await resultsList.isVisible().catch(() => false);
expect(hasEmptyState || hasResults).toBeTruthy();
});
});
});
test.describe('Console Audit - Schedule Results', () => {
test('should have no console errors on results page', async ({ page }) => {
const errors: string[] = [];
page.on('console', (message) => {
if (message.type() === 'error') {
errors.push(message.text());
}
});
await page.goto('http://localhost:3000/schedule');
await page.waitForLoadState('networkidle');
// Perform search
const departureInput = page.locator('[data-testid="schedule-departure-input"] input');
const arrivalInput = page.locator('[data-testid="schedule-arrival-input"] input');
await departureInput.fill('SVO');
await arrivalInput.fill('AER');
const searchButton = page.locator('button:has-text("Search")');
if (await searchButton.isVisible()) {
await searchButton.click();
}
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000);
// Interact with results
const flightItems = page.locator('[data-testid="schedule-flight-item"]');
if ((await flightItems.count()) > 0) {
await flightItems.first().click();
await page.waitForTimeout(300);
}
// Check for errors (excluding known non-critical warnings)
const criticalErrors = errors.filter(
(e) =>
!e.includes('hydration') &&
!e.includes('useLayoutEffect') &&
!e.includes('act()') &&
!e.includes('warning') &&
e.length > 0,
);
expect(criticalErrors).toHaveLength(0);
});
test('should have no accessibility violations', async ({ page }) => {
await page.goto('http://localhost:3000/schedule');
await page.waitForLoadState('networkidle');
// Perform search
const departureInput = page.locator('[data-testid="schedule-departure-input"] input');
const arrivalInput = page.locator('[data-testid="schedule-arrival-input"] input');
await departureInput.fill('SVO');
await arrivalInput.fill('AER');
const searchButton = page.locator('button:has-text("Search")');
if (await searchButton.isVisible()) {
await searchButton.click();
}
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000);
// Check that all interactive elements are keyboard accessible
const buttons = page.locator('button');
const count = await buttons.count();
for (let i = 0; i < Math.min(count, 5); i++) {
const button = buttons.nth(i);
await button.focus();
const focused = await page.evaluate(() => {
const el = document.activeElement as HTMLElement;
return el.tagName === 'BUTTON';
});
expect(focused).toBeTruthy();
}
});
});
-347
View File
@@ -1,347 +0,0 @@
import { test, expect } from '@playwright/test';
test.describe('Schedule Search - Document 3 (US-23 to US-27)', () => {
test.beforeEach(async ({ page }) => {
await page.goto('http://localhost:3000/schedule');
await page.waitForLoadState('networkidle');
});
test.describe('US-23: Schedule Tab Navigation', () => {
test('should render schedule search form', async ({ page }) => {
const form = page.locator('[data-testid="schedule-search-form"]');
await expect(form).toBeVisible();
});
test('should render search form with proper role', async ({ page }) => {
const form = page.locator('[role="search"]');
await expect(form).toBeVisible();
});
test('should have proper ARIA label', async ({ page }) => {
const form = page.locator('[role="search"]');
const ariaLabel = await form.getAttribute('aria-label');
expect(ariaLabel).toBeTruthy();
});
});
test.describe('US-24: Departure City Input', () => {
test('should render departure city input', async ({ page }) => {
const input = page.locator('[data-testid="schedule-departure-input"]');
await expect(input).toBeVisible();
});
test('should have From label', async ({ page }) => {
const label = page.getByText('From', { exact: true });
await expect(label).toBeVisible();
});
test('should accept text input for departure city', async ({ page }) => {
const input = page.locator('[data-testid="schedule-departure-input"] input');
await input.fill('Moscow');
await expect(input).toHaveValue('Moscow');
});
test('should allow clearing departure city', async ({ page }) => {
const input = page.locator('[data-testid="schedule-departure-input"] input');
await input.fill('Moscow');
await input.clear();
await expect(input).toHaveValue('');
});
test('should support autocomplete suggestions', async ({ page }) => {
const input = page.locator('[data-testid="schedule-departure-input"] input');
await input.focus();
await input.type('Mos', { delay: 100 });
// Wait for autocomplete to potentially appear
await page.waitForTimeout(500);
expect(input).toBeVisible();
});
});
test.describe('US-25: Arrival City Input', () => {
test('should render arrival city input', async ({ page }) => {
const input = page.locator('[data-testid="schedule-arrival-input"]');
await expect(input).toBeVisible();
});
test('should have To label', async ({ page }) => {
const label = page.getByText('To', { exact: true });
await expect(label).toBeVisible();
});
test('should accept text input for arrival city', async ({ page }) => {
const input = page.locator('[data-testid="schedule-arrival-input"] input');
await input.fill('Saint Petersburg');
await expect(input).toHaveValue('Saint Petersburg');
});
test('should allow clearing arrival city', async ({ page }) => {
const input = page.locator('[data-testid="schedule-arrival-input"] input');
await input.fill('Saint Petersburg');
await input.clear();
await expect(input).toHaveValue('');
});
test('should support independent entry from departure', async ({ page }) => {
const departureInput = page.locator('[data-testid="schedule-departure-input"] input');
const arrivalInput = page.locator('[data-testid="schedule-arrival-input"] input');
await departureInput.fill('Moscow');
await arrivalInput.fill('SPB');
await expect(departureInput).toHaveValue('Moscow');
await expect(arrivalInput).toHaveValue('SPB');
});
});
test.describe('US-26: Swap Cities Button (Exchange)', () => {
test('should have both departure and arrival inputs for exchange', async ({ page }) => {
const departureInput = page.locator('[data-testid="schedule-departure-input"]');
const arrivalInput = page.locator('[data-testid="schedule-arrival-input"]');
await expect(departureInput).toBeVisible();
await expect(arrivalInput).toBeVisible();
});
test('should allow switching focus between city inputs', async ({ page }) => {
const departureInput = page.locator('[data-testid="schedule-departure-input"] input');
const arrivalInput = page.locator('[data-testid="schedule-arrival-input"] input');
await departureInput.focus();
await expect(departureInput).toBeFocused();
await arrivalInput.focus();
await expect(arrivalInput).toBeFocused();
});
test('should support entering different cities', async ({ page }) => {
const departureInput = page.locator('[data-testid="schedule-departure-input"] input');
const arrivalInput = page.locator('[data-testid="schedule-arrival-input"] input');
await departureInput.fill('Moscow');
await arrivalInput.fill('Saint Petersburg');
await expect(departureInput).toHaveValue('Moscow');
await expect(arrivalInput).toHaveValue('Saint Petersburg');
});
});
test.describe('US-27: Week Selection', () => {
test('should render date from input', async ({ page }) => {
const dateFromInput = page.locator('[data-testid="schedule-calendar"] input');
await expect(dateFromInput).toBeVisible();
});
test('should render date to input', async ({ page }) => {
const dateToInput = page.locator('[data-testid="schedule-outbound-date-input"]');
await expect(dateToInput).toBeVisible();
});
test('should have Depart label for date from', async ({ page }) => {
const label = page.getByText('Depart', { exact: true });
await expect(label).toBeVisible();
});
test('should have Return label for date to', async ({ page }) => {
const label = page.getByText('Return', { exact: true });
await expect(label).toBeVisible();
});
test('should initialize with date values', async ({ page }) => {
const dateFromInput = page.locator('[data-testid="schedule-calendar"] input');
const dateToInput = page.locator('[data-testid="schedule-outbound-date-input"]');
const dateFromValue = await dateFromInput.inputValue();
const dateToValue = await dateToInput.inputValue();
// Should match YYYY-MM-DD format
expect(dateFromValue).toMatch(/\d{4}-\d{2}-\d{2}/);
expect(dateToValue).toMatch(/\d{4}-\d{2}-\d{2}/);
});
test('should have date input type', async ({ page }) => {
const dateFromInput = page.locator('[data-testid="schedule-calendar"] input');
const dateToInput = page.locator('[data-testid="schedule-outbound-date-input"]');
const dateFromType = await dateFromInput.getAttribute('type');
const dateToType = await dateToInput.getAttribute('type');
expect(dateFromType).toBe('date');
expect(dateToType).toBe('date');
});
test('should allow changing departure date', async ({ page }) => {
const dateFromInput = page.locator('[data-testid="schedule-calendar"] input');
const initialValue = await dateFromInput.inputValue();
// The date input should be functional
await dateFromInput.focus();
await expect(dateFromInput).toBeFocused();
});
test('should support week date range selection', async ({ page }) => {
const dateFromInput = page.locator('[data-testid="schedule-calendar"] input');
const dateToInput = page.locator('[data-testid="schedule-outbound-date-input"]');
// Both should be visible and functional for date range
await expect(dateFromInput).toBeVisible();
await expect(dateToInput).toBeVisible();
const dateFromValue = await dateFromInput.inputValue();
const dateToValue = await dateToInput.inputValue();
// Both should have dates
expect(dateFromValue).toBeTruthy();
expect(dateToValue).toBeTruthy();
});
});
test.describe('Schedule Search Form Integration', () => {
test('should have all search inputs visible', async ({ page }) => {
const departureInput = page.locator('[data-testid="schedule-departure-input"]');
const arrivalInput = page.locator('[data-testid="schedule-arrival-input"]');
const dateFromInput = page.locator('[data-testid="schedule-calendar"] input');
const dateToInput = page.locator('[data-testid="schedule-outbound-date-input"]');
await expect(departureInput).toBeVisible();
await expect(arrivalInput).toBeVisible();
await expect(dateFromInput).toBeVisible();
await expect(dateToInput).toBeVisible();
});
test('should have search button', async ({ page }) => {
const searchButton = page.locator('[data-testid="schedule-search-button"]');
await expect(searchButton).toBeVisible();
await expect(searchButton).toContainText('Search', { ignoreCase: true });
});
test('should have checkbox for direct flights only', async ({ page }) => {
const directCheckbox = page.locator('[data-testid="schedule-direct-only-checkbox"]');
await expect(directCheckbox).toBeVisible();
});
test('should have checkbox for return flight', async ({ page }) => {
const returnCheckbox = page.locator('[data-testid="schedule-return-checkbox"]');
await expect(returnCheckbox).toBeVisible();
});
test('should show validation error when trying to search without cities', async ({ page }) => {
const searchButton = page.locator('[data-testid="schedule-search-button"]');
await searchButton.click();
const error = page.locator('[data-testid="schedule-validation-error"]');
await expect(error).toBeVisible();
});
test('should toggle return date fields when return flight is enabled', async ({ page }) => {
const returnCheckbox = page.locator('[data-testid="schedule-return-checkbox"]');
const returnCalendar = page.locator('[data-testid="schedule-return-calendar"]');
// Initially hidden
await expect(returnCalendar).not.toBeVisible();
// Click to enable return flight
await returnCheckbox.click();
// Now visible
await expect(returnCalendar).toBeVisible();
});
});
test.describe('Schedule Search Workflow', () => {
test('should allow complete search form interaction', async ({ page }) => {
const departureInput = page.locator('[data-testid="schedule-departure-input"] input');
const arrivalInput = page.locator('[data-testid="schedule-arrival-input"] input');
const directCheckbox = page.locator('[data-testid="schedule-direct-only-checkbox"]');
// Fill departure city
await departureInput.fill('Moscow');
await expect(departureInput).toHaveValue('Moscow');
// Fill arrival city
await arrivalInput.fill('Saint Petersburg');
await expect(arrivalInput).toHaveValue('Saint Petersburg');
// Toggle direct only
const isChecked = await directCheckbox.isChecked();
await directCheckbox.click();
const newChecked = await directCheckbox.isChecked();
expect(newChecked).toBe(!isChecked);
});
test('should maintain form state during interaction', async ({ page }) => {
const departureInput = page.locator('[data-testid="schedule-departure-input"] input');
const arrivalInput = page.locator('[data-testid="schedule-arrival-input"] input');
const dateFromInput = page.locator('[data-testid="schedule-calendar"] input');
// Enter data
await departureInput.fill('Moscow');
await arrivalInput.fill('SPB');
const originalDate = await dateFromInput.inputValue();
// Verify all data is still there
await expect(departureInput).toHaveValue('Moscow');
await expect(arrivalInput).toHaveValue('SPB');
const newDate = await dateFromInput.inputValue();
expect(newDate).toBe(originalDate);
});
test('should allow toggling between one-way and round trip', async ({ page }) => {
const returnCheckbox = page.locator('[data-testid="schedule-return-checkbox"]');
const returnCalendar = page.locator('[data-testid="schedule-return-calendar"]');
// Initially one-way
const isCheckedInitial = await returnCheckbox.isChecked();
expect(isCheckedInitial).toBe(false);
// Toggle to round trip
await returnCheckbox.click();
await expect(returnCalendar).toBeVisible();
// Toggle back to one-way
await returnCheckbox.click();
await expect(returnCalendar).not.toBeVisible();
});
});
test.describe('Accessibility', () => {
test('should have form with proper role and label', async ({ page }) => {
const form = page.locator('[role="search"]');
const ariaLabel = await form.getAttribute('aria-label');
await expect(form).toBeVisible();
expect(ariaLabel).toBeTruthy();
});
test('should have properly associated labels', async ({ page }) => {
const fromLabel = page.getByText('From', { exact: true });
const toLabel = page.getByText('To', { exact: true });
const departLabel = page.getByText('Depart', { exact: true });
const returnLabel = page.getByText('Return', { exact: true });
await expect(fromLabel).toBeVisible();
await expect(toLabel).toBeVisible();
await expect(departLabel).toBeVisible();
await expect(returnLabel).toBeVisible();
});
test('should support keyboard navigation', async ({ page }) => {
const departureInput = page.locator('[data-testid="schedule-departure-input"] input');
const arrivalInput = page.locator('[data-testid="schedule-arrival-input"] input');
const searchButton = page.locator('[data-testid="schedule-search-button"]');
// Start at departure
await departureInput.focus();
await expect(departureInput).toBeFocused();
// Tab to next element
await page.keyboard.press('Tab');
// Should be on next focusable element
const focusedElement = await page.evaluate(() =>
document.activeElement?.getAttribute('data-testid'),
);
expect(focusedElement).not.toBe('schedule-departure-input');
});
});
});
-199
View File
@@ -1,199 +0,0 @@
import { test, expect } from '@playwright/test';
test.describe('Search History (US-8)', () => {
test.beforeEach(async ({ page }) => {
await page.goto('http://localhost:3000/ru-ru/onlineboard');
// Clear localStorage to start fresh
await page.evaluate(() => localStorage.clear());
// Reload after clearing
await page.reload();
});
test('should not display search history section when empty', async ({ page }) => {
const section = page.locator('[data-testid="landing-search-history"]');
await expect(section).not.toBeVisible();
});
test('should display search history section when items exist', async ({ page }) => {
// Setup: Add history to localStorage
await page.evaluate(() => {
const historyItem = {
id: '1',
label: 'SU 1402',
url: '/search?flight=SU1402',
timestamp: Date.now(),
};
localStorage.setItem('aeroflot_search_history', JSON.stringify([historyItem]));
});
// Reload to pick up the localStorage data
await page.reload();
const section = page.locator('[data-testid="landing-search-history"]');
await expect(section).toBeVisible();
});
test('should display history items correctly', async ({ page }) => {
// Setup: Add multiple history items
await page.evaluate(() => {
const historyItems = [
{
id: '1',
label: 'SU 1402',
url: '/search?flight=SU1402',
timestamp: Date.now(),
},
{
id: '2',
label: 'SU 1403',
url: '/search?flight=SU1403',
timestamp: Date.now() - 60000,
},
];
localStorage.setItem('aeroflot_search_history', JSON.stringify(historyItems));
});
await page.reload();
const items = page.locator('[data-testid="landing-search-history-item"]');
await expect(items).toHaveCount(2);
// Check for flight numbers
await expect(page.getByText('SU 1402')).toBeVisible();
await expect(page.getByText('SU 1403')).toBeVisible();
});
test('should display search history title', async ({ page }) => {
await page.evaluate(() => {
const historyItem = {
id: '1',
label: 'SU 1402',
url: '/search?flight=SU1402',
timestamp: Date.now(),
};
localStorage.setItem('aeroflot_search_history', JSON.stringify([historyItem]));
});
await page.reload();
// Note: Title depends on intl messages, might be "Search History" or Russian equivalent
const title = page.locator('[data-testid="landing-search-history"] h3');
await expect(title).toBeVisible();
});
test('should have clickable history items that are links', async ({ page }) => {
await page.evaluate(() => {
const historyItem = {
id: '1',
label: 'SU 1402',
url: '/search?flight=SU1402',
timestamp: Date.now(),
};
localStorage.setItem('aeroflot_search_history', JSON.stringify([historyItem]));
});
await page.reload();
const link = page.locator('[data-testid="landing-search-history-item"] a').first();
await expect(link).toHaveAttribute('href', /search\?flight=SU1402/);
});
test('should format timestamp as HH:MM', async ({ page }) => {
const testTime = new Date(2026, 3, 9, 14, 30, 0).getTime();
await page.evaluate((time) => {
const historyItem = {
id: '1',
label: 'SU 1402',
url: '/search?flight=SU1402',
timestamp: time,
};
localStorage.setItem('aeroflot_search_history', JSON.stringify([historyItem]));
}, testTime);
await page.reload();
// Check for time format HH:MM
const timeElement = page.locator('[data-testid="landing-search-history-item"] span').last();
const timeText = await timeElement.textContent();
expect(timeText).toMatch(/\d{2}:\d{2}/);
});
test('should persist history across page reloads', async ({ page }) => {
// Add history
await page.evaluate(() => {
const historyItem = {
id: '1',
label: 'SU 1402',
url: '/search?flight=SU1402',
timestamp: Date.now(),
};
localStorage.setItem('aeroflot_search_history', JSON.stringify([historyItem]));
});
await page.reload();
// Verify it exists
const items1 = page.locator('[data-testid="landing-search-history-item"]');
const count1 = await items1.count();
expect(count1).toBeGreaterThan(0);
// Reload again
await page.reload();
// Verify it still exists
const items2 = page.locator('[data-testid="landing-search-history-item"]');
const count2 = await items2.count();
expect(count2).toBe(count1);
});
test('should be responsive on mobile viewport', async ({ page }) => {
await page.setViewportSize({ width: 375, height: 667 });
await page.evaluate(() => {
const historyItem = {
id: '1',
label: 'SU 1402',
url: '/search?flight=SU1402',
timestamp: Date.now(),
};
localStorage.setItem('aeroflot_search_history', JSON.stringify([historyItem]));
});
await page.reload();
const section = page.locator('[data-testid="landing-search-history"]');
await expect(section).toBeVisible();
});
test('should handle large number of history items', async ({ page }) => {
// Create 20 history items
await page.evaluate(() => {
const historyItems = Array.from({ length: 20 }, (_, i) => ({
id: String(i + 1),
label: `SU ${1400 + i}`,
url: `/search?flight=SU${1400 + i}`,
timestamp: Date.now() - i * 60000,
}));
localStorage.setItem('aeroflot_search_history', JSON.stringify(historyItems));
});
await page.reload();
const items = page.locator('[data-testid="landing-search-history-item"]');
await expect(items).toHaveCount(20);
});
test('should handle corrupted localStorage data gracefully', async ({ page }) => {
// Corrupt the localStorage
await page.evaluate(() => {
localStorage.setItem('aeroflot_search_history', 'corrupted{invalid json');
});
await page.reload();
// Should not show history section
const section = page.locator('[data-testid="landing-search-history"]');
await expect(section).not.toBeVisible();
});
});
-168
View File
@@ -1,168 +0,0 @@
import { test, expect } from '@playwright/test';
test.describe('Search Panel - Filter Sidebar (US-6)', () => {
test.beforeEach(async ({ page }) => {
await page.goto('http://localhost:3000');
});
test('should render filter accordion container', async ({ page }) => {
const filterAccordion = page.locator('[data-testid="filter-accordion"]');
await expect(filterAccordion).toBeVisible();
});
test('should render flight number search tab', async ({ page }) => {
const flightTab = page.locator('[data-testid="filter-flight-tab"]');
await expect(flightTab).toBeVisible();
});
test('should render route search tab', async ({ page }) => {
const routeTab = page.locator('[data-testid="filter-route-tab"]');
await expect(routeTab).toBeVisible();
});
test('should expand flight tab when clicked', async ({ page }) => {
const flightTab = page.locator('[data-testid="filter-flight-tab"]');
// Click to ensure it's expanded
await flightTab.click();
// Wait for search panel to appear
const searchByFlight = page.locator('[data-testid="search-by-flight"]');
await expect(searchByFlight).toBeVisible({ timeout: 5000 });
});
test('should display flight number input when flight tab is active', async ({ page }) => {
const flightTab = page.locator('[data-testid="filter-flight-tab"]');
await flightTab.click();
const flightInput = page.locator('[data-testid="filter-flight-number-input"]');
await expect(flightInput).toBeVisible();
});
test('should allow entering flight number', async ({ page }) => {
const flightTab = page.locator('[data-testid="filter-flight-tab"]');
await flightTab.click();
const flightInput = page.locator('[data-testid="filter-flight-number-input"]');
await flightInput.fill('1402');
await expect(flightInput).toHaveValue('1402');
});
test('should display flight suffix input', async ({ page }) => {
const flightTab = page.locator('[data-testid="filter-flight-tab"]');
await flightTab.click();
const suffixInput = page.locator('[data-testid="filter-flight-number-suffix-input"]');
await expect(suffixInput).toBeVisible();
});
test('should allow entering flight suffix', async ({ page }) => {
const flightTab = page.locator('[data-testid="filter-flight-tab"]');
await flightTab.click();
const suffixInput = page.locator('[data-testid="filter-flight-number-suffix-input"]');
await suffixInput.fill('A');
await expect(suffixInput).toHaveValue('A');
});
test('should display date picker', async ({ page }) => {
const flightTab = page.locator('[data-testid="filter-flight-tab"]');
await flightTab.click();
const datePicker = page.locator('[data-testid="filter-flight-number-calendar"]');
await expect(datePicker).toBeVisible();
});
test('should display search button', async ({ page }) => {
const flightTab = page.locator('[data-testid="filter-flight-tab"]');
await flightTab.click();
const searchButton = page.locator('[data-testid="filter-flight-number-search"]');
await expect(searchButton).toBeVisible();
});
test('should expand route tab when clicked', async ({ page }) => {
const routeTab = page.locator('[data-testid="filter-route-tab"]');
// Click to ensure it's expanded
await routeTab.click();
// Wait for search panel to appear
const searchByRoute = page.locator('[data-testid="search-by-route"]');
await expect(searchByRoute).toBeVisible({ timeout: 5000 });
});
test('should toggle between flight and route tabs', async ({ page }) => {
const flightTab = page.locator('[data-testid="filter-flight-tab"]');
const routeTab = page.locator('[data-testid="filter-route-tab"]');
// Open flight tab
await flightTab.click();
let flightContent = page.locator('[data-testid="search-by-flight"]');
await expect(flightContent).toBeVisible();
// Switch to route tab
await routeTab.click();
const routeContent = page.locator('[data-testid="search-by-route"]');
await expect(routeContent).toBeVisible();
// Flight content should no longer be visible
flightContent = page.locator('[data-testid="search-by-flight"]');
await expect(flightContent).not.toBeVisible();
});
test('should have SU prefix displayed', async ({ page }) => {
const flightTab = page.locator('[data-testid="filter-flight-tab"]');
await flightTab.click();
const suPrefix = page.locator('.prefix');
await expect(suPrefix).toContainText('SU');
});
test('should have clear button for flight number', async ({ page }) => {
const flightTab = page.locator('[data-testid="filter-flight-tab"]');
await flightTab.click();
const flightInput = page.locator('[data-testid="filter-flight-number-input"]');
await flightInput.fill('1402');
const clearButton = page.locator('[data-testid="filter-flight-number-clear"]').first();
await expect(clearButton).toBeVisible();
});
test('should clear flight number when clear button clicked', async ({ page }) => {
const flightTab = page.locator('[data-testid="filter-flight-tab"]');
await flightTab.click();
const flightInput = page.locator('[data-testid="filter-flight-number-input"]');
await flightInput.fill('1402');
const clearButton = page.locator('[data-testid="filter-flight-number-clear"]').first();
await clearButton.click();
await expect(flightInput).toHaveValue('');
});
test('should display all three search sections in filter accordion', async ({ page }) => {
const filterAccordion = page.locator('[data-testid="filter-accordion"]');
// Get all section headers
const sectionHeaders = filterAccordion.locator('button[class*="sectionHeader"]');
const count = await sectionHeaders.count();
expect(count).toBeGreaterThanOrEqual(3); // At least 3 sections (flight, route, arrival)
});
test('should support keyboard navigation', async ({ page }) => {
const flightTab = page.locator('[data-testid="filter-flight-tab"]');
// Focus the button
await flightTab.focus();
// Press Enter to activate
await flightTab.press('Enter');
const searchByFlight = page.locator('[data-testid="search-by-flight"]');
await expect(searchByFlight).toBeVisible({ timeout: 5000 });
});
});
-72
View File
@@ -1,72 +0,0 @@
import { test, expect } from '@playwright/test';
test.describe('SEO & Meta Tags (US-9)', () => {
test('should have correct title and meta tags for ru-ru', async ({ page }) => {
await page.goto('http://localhost:3000/ru-ru/onlineboard');
const title = await page.title();
expect(title).toBeTruthy();
expect(title.length).toBeGreaterThan(0);
const description = await page.locator('meta[name="description"]').getAttribute('content');
expect(description).toBeTruthy();
});
test('should have correct title and meta tags for en-us', async ({ page }) => {
await page.goto('http://localhost:3000/en-us/onlineboard');
const title = await page.title();
expect(title).toBeTruthy();
expect(title.length).toBeGreaterThan(0);
});
test('should have OpenGraph tags on all pages', async ({ page }) => {
await page.goto('http://localhost:3000/ru-ru/onlineboard');
const ogTitle = await page.locator('meta[property="og:title"]').count();
expect(ogTitle).toBeGreaterThan(0);
const ogDescription = await page.locator('meta[property="og:description"]').count();
expect(ogDescription).toBeGreaterThan(0);
});
test('should have canonical link', async ({ page }) => {
await page.goto('http://localhost:3000/ru-ru/onlineboard');
const canonical = await page.locator('link[rel="canonical"]').count();
expect(canonical).toBeGreaterThan(0);
});
test('should have viewport meta tag', async ({ page }) => {
await page.goto('http://localhost:3000/ru-ru/onlineboard');
const viewport = await page.locator('meta[name="viewport"]').getAttribute('content');
expect(viewport).toContain('width=device-width');
});
test('should have correct language attribute', async ({ page }) => {
await page.goto('http://localhost:3000/ru-ru/onlineboard');
const lang = await page.locator('html').getAttribute('lang');
expect(lang).toBeTruthy();
});
test('should update lang attribute when changing locale', async ({ page }) => {
await page.goto('http://localhost:3000/ru-ru/onlineboard');
let lang = await page.locator('html').getAttribute('lang');
expect(lang).toBeTruthy();
// Switch to English
await page.goto('http://localhost:3000/en-us/onlineboard');
lang = await page.locator('html').getAttribute('lang');
expect(lang).toBeTruthy();
});
test('should have JSON-LD structured data', async ({ page }) => {
await page.goto('http://localhost:3000/ru-ru/onlineboard');
const jsonLd = await page.locator('script[type="application/ld+json"]').count();
expect(jsonLd).toBeGreaterThan(0);
});
});
@@ -1,23 +0,0 @@
import { test, expect } from '@playwright/test';
import { todayStr } from '../../src/lib/date-utils';
const today = todayStr();
const dateParam = today.replace(/-/g, '');
test.describe('Flight board visual regression', () => {
test('departures view matches screenshot', async ({ page }) => {
await page.goto(`/ru-ru/onlineboard/departure/MOW-${dateParam}`);
await page.waitForLoadState('networkidle');
await expect(page).toHaveScreenshot('flight-board-departures.png', {
fullPage: true,
});
});
test('arrivals view matches screenshot', async ({ page }) => {
await page.goto(`/ru-ru/onlineboard/arrival/MOW-${dateParam}`);
await page.waitForLoadState('networkidle');
await expect(page).toHaveScreenshot('flight-board-arrivals.png', {
fullPage: true,
});
});
});
@@ -1,21 +0,0 @@
import { test, expect } from '@playwright/test';
import { todayStr } from '../../src/lib/date-utils';
const today = todayStr();
const dateParam = today.replace(/-/g, '');
test.describe('Expanded flight card visual regression', () => {
test('expanded card matches screenshot', async ({ page }) => {
await page.goto(`/ru-ru/onlineboard/departure/MOW-${dateParam}`);
await page.waitForLoadState('networkidle');
await page.waitForSelector('[class*="card"]', { timeout: 10000 });
const firstCard = page.locator('[class*="card"]').first();
await firstCard.click();
const expandedSection = page.locator('[class*="expanded"]').first();
await expect(expandedSection).toBeVisible();
await expect(expandedSection).toHaveScreenshot('flight-expanded.png', { timeout: 10000 });
});
});
-15
View File
@@ -1,15 +0,0 @@
import { test, expect } from '@playwright/test';
import { todayStr } from '../../src/lib/date-utils';
const today = todayStr();
const dateParam = today.replace(/-/g, '');
test.describe('Landing page visual regression', () => {
test('matches landing page screenshot', async ({ page }) => {
await page.goto(`/ru-ru/onlineboard/departure/MOW-${dateParam}`);
await page.waitForLoadState('networkidle');
await expect(page).toHaveScreenshot('landing.png', {
fullPage: true,
});
});
});