diff --git a/tests/e2e-angular/console-audit.spec.ts b/tests/e2e-angular/console-audit.spec.ts deleted file mode 100644 index d11f28f5..00000000 --- a/tests/e2e-angular/console-audit.spec.ts +++ /dev/null @@ -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([]); - }); -}); diff --git a/tests/e2e-angular/error-handling.spec.ts b/tests/e2e-angular/error-handling.spec.ts deleted file mode 100644 index 95e0d7ee..00000000 --- a/tests/e2e-angular/error-handling.spec.ts +++ /dev/null @@ -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(() => {}); - }); -}); diff --git a/tests/e2e-angular/flight-details.spec.ts b/tests/e2e-angular/flight-details.spec.ts deleted file mode 100644 index 0b98236e..00000000 --- a/tests/e2e-angular/flight-details.spec.ts +++ /dev/null @@ -1,1081 +0,0 @@ -import { test, expect } from '@playwright/test'; - -const BASE_URL = process.env.BASE_URL || 'http://localhost:5173'; - -// Test flight IDs - using realistic SU (Aeroflot) flight numbers -const TEST_FLIGHTS = { - su1402: 'SU1402', // Moscow to New York - su200: 'SU200', // Moscow to Paris - su500: 'SU500', // Moscow to Tokyo -}; - -test.describe('Flight Details Page (US-47 to US-49)', () => { - test.describe('US-47: Flight Details Page', () => { - test('should load flight details page with proper layout', async ({ page }) => { - // Navigate to flight details page - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - - // Wait for page to load - await page.waitForLoadState('networkidle'); - - // Check for main page elements - const mainContent = page.locator('main'); - await expect(mainContent).toBeVisible(); - }); - - test('should display loading state initially', async ({ page }) => { - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - - // Check for content after load - await page.waitForLoadState('networkidle'); - const content = page.locator('body'); - await expect(content).toBeVisible(); - }); - - test('should handle responsive layout on mobile viewport', async ({ page }) => { - // Set mobile viewport - await page.setViewportSize({ width: 375, height: 667 }); - - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - // Check for responsive container - const container = page.locator('[class*="basicInfo"], [class*="statusDisplay"]'); - await expect(container.first()).toBeVisible(); - }); - - test('should handle tablet viewport', async ({ page }) => { - // Set tablet viewport - await page.setViewportSize({ width: 768, height: 1024 }); - - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - const container = page.locator('[class*="basicInfo"], [class*="statusDisplay"]'); - await expect(container.first()).toBeVisible(); - }); - - test('should display all layout sections in correct order', async ({ page }) => { - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - // Check for flight number in header - const flightNumber = page.locator('span[class*="flightNumber"]'); - await expect(flightNumber).toBeVisible(); - }); - - test('should be accessible with keyboard navigation', async ({ page }) => { - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - // Tab to first interactive element - await page.keyboard.press('Tab'); - - // Check if focus is on interactive element - const focusedElement = await page.evaluate(() => document.activeElement?.tagName); - expect(['BUTTON', 'A', 'INPUT', 'SELECT', 'TEXTAREA']).toContain(focusedElement); - }); - - 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/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - // Filter out expected third-party errors if needed - const appErrors = errors.filter((e) => !e.includes('third-party')); - expect(appErrors.length).toBe(0); - }); - }); - - test.describe('US-48: Basic Flight Information', () => { - test('should display flight number with proper formatting', async ({ page }) => { - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - // Flight number should be visible - const flightNumber = page.locator('span[class*="flightNumber"]'); - await expect(flightNumber).toBeVisible(); - - const text = await flightNumber.textContent(); - // Should contain flight number (with or without space: "SU 1402" or "SU1402") - expect(text).toMatch(/SU\s?1402/); - }); - - test('should display departure time and city', async ({ page }) => { - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - // Look for departure section - const departureCity = page.locator('[class*="segment"] [class*="city"]').first(); - await expect(departureCity).toBeVisible(); - - const city = await departureCity.textContent(); - expect(city).toBeTruthy(); // Should contain city name - }); - - test('should display arrival time and city', async ({ page }) => { - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - // Look for arrival section (second city) - const arrivalCity = page.locator('[class*="segment"] [class*="city"]').last(); - await expect(arrivalCity).toBeVisible(); - - const city = await arrivalCity.textContent(); - expect(city).toBeTruthy(); // Should contain city name - }); - - test('should display aircraft type when available', async ({ page }) => { - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - // Look for aircraft section - const aircraft = page.locator('[class*="aircraft"] [class*="value"]'); - if ((await aircraft.count()) > 0) { - const aircraftText = await aircraft.textContent(); - expect(aircraftText).toBeTruthy(); - } - }); - - test('should display flight duration', async ({ page }) => { - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - // Look for duration text - const duration = page.locator('[class*="durationText"]'); - if ((await duration.count()) > 0) { - await expect(duration).toBeVisible(); - const text = await duration.textContent(); - expect(text).toMatch(/\d+h\s*\d+m/); - } - }); - - test('should format times correctly in locale', async ({ page }) => { - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - // Look for time elements (HH:MM format) - const timeElements = page.locator('[class*="time"]'); - if ((await timeElements.count()) > 0) { - const firstTime = await timeElements.first().textContent(); - expect(firstTime).toMatch(/\d{2}:\d{2}/); - } - }); - - test('should handle flights without aircraft type', async ({ page }) => { - // This test ensures component doesn't crash without aircraft info - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su500}`); - await page.waitForLoadState('networkidle'); - - // Page should still render - const container = page.locator('[class*="basicInfo"]'); - await expect(container).toBeVisible(); - }); - - test('should be responsive on mobile', async ({ page }) => { - await page.setViewportSize({ width: 375, height: 667 }); - - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - // Check that content is visible and not overlapping - const basicInfo = page.locator('[class*="basicInfo"]'); - await expect(basicInfo).toBeVisible(); - - const boundingBox = await basicInfo.boundingBox(); - expect(boundingBox?.width).toBeLessThanOrEqual(375); - }); - }); - - test.describe('US-49: Status and Status Details', () => { - test('should display operational status', async ({ page }) => { - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - // Look for status display - const statusIndicator = page.locator('[class*="statusIndicator"]'); - if ((await statusIndicator.count()) > 0) { - await expect(statusIndicator.first()).toBeVisible(); - } - }); - - test('should display status remarks when available', async ({ page }) => { - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su200}`); - await page.waitForLoadState('networkidle'); - - // Look for remarks section - const remarks = page.locator('[class*="remarks"]'); - if ((await remarks.count()) > 0) { - await expect(remarks.first()).toBeVisible(); - } - }); - - test('should display last update timestamp', async ({ page }) => { - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - // Look for last update section - const lastUpdate = page.locator('[class*="lastUpdate"]'); - if ((await lastUpdate.count()) > 0) { - await expect(lastUpdate.first()).toBeVisible(); - const text = await lastUpdate.first().textContent(); - expect(text).toBeTruthy(); - } - }); - - test('should apply correct status color coding for scheduled status', async ({ page }) => { - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - // Status display should be present - const statusDisplay = page.locator('[class*="statusDisplay"]'); - if ((await statusDisplay.count()) > 0) { - await expect(statusDisplay.first()).toBeVisible(); - } - }); - - test('should handle different status types', async ({ page }) => { - // Test with different flight - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su500}`); - await page.waitForLoadState('networkidle'); - - const statusDisplay = page.locator('[class*="statusDisplay"]'); - if ((await statusDisplay.count()) > 0) { - await expect(statusDisplay.first()).toBeVisible(); - } - }); - - test('should display status message in correct locale (Russian)', async ({ page }) => { - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - // Check that page content is in Russian - const html = await page.content(); - expect(html).toContain('ru-ru'); - }); - - test('should display status message in correct locale (English)', async ({ page }) => { - await page.goto(`${BASE_URL}/en-us/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - // Check that page content is in English - const html = await page.content(); - expect(html).toContain('en-us'); - }); - - test('should update status display when status changes', async ({ page }) => { - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - // Get initial status - const statusDisplay = page.locator('[class*="statusDisplay"]'); - if ((await statusDisplay.count()) > 0) { - await expect(statusDisplay.first()).toBeVisible(); - } - }); - - test('should format status timestamp in locale-aware manner', async ({ page }) => { - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - const lastUpdate = page.locator('[class*="lastUpdate"]'); - if ((await lastUpdate.count()) > 0) { - const text = await lastUpdate.first().textContent(); - // Should contain time in HH:MM format - expect(text).toMatch(/\d{2}:\d{2}/); - } - }); - - test('should be responsive on mobile viewport', async ({ page }) => { - await page.setViewportSize({ width: 375, height: 667 }); - - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - const statusDisplay = page.locator('[class*="statusDisplay"]'); - if ((await statusDisplay.count()) > 0) { - await expect(statusDisplay.first()).toBeVisible(); - } - }); - }); - - test.describe('Integration: Full Flight Details Page', () => { - test('should render all components together', async ({ page }) => { - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - // Check for both basic info and status display - const basicInfo = page.locator('[class*="basicInfo"], span[class*="flightNumber"]'); - const statusDisplay = page.locator('[class*="statusDisplay"], [class*="statusIndicator"]'); - - expect(await basicInfo.count()).toBeGreaterThan(0); - }); - - test('should display information without layout issues', async ({ page }) => { - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - // Check that main content area has reasonable width - const mainContent = page.locator('main, [role="main"]'); - const box = await mainContent.first().boundingBox(); - expect(box?.width).toBeGreaterThan(0); - }); - - test('should support page refresh without errors', async ({ page }) => { - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - // Refresh page - await page.reload(); - await page.waitForLoadState('networkidle'); - - // Check that content is still visible - const flightNumber = page.locator('span[class*="flightNumber"]'); - await expect(flightNumber).toBeVisible(); - }); - - test('should support switching between locales', async ({ page }) => { - // Start with Russian - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - const ruContent = await page.content(); - - // Switch to English - await page.goto(`${BASE_URL}/en-us/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - const enContent = await page.content(); - - // Both should have flight details visible - expect(ruContent).toContain('SU1402'); - expect(enContent).toContain('SU1402'); - }); - - test('should handle rapid navigation between flights', async ({ page }) => { - // Navigate to first flight - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - // Navigate to second flight - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su200}`); - await page.waitForLoadState('networkidle'); - - // Navigate to third flight - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su500}`); - await page.waitForLoadState('networkidle'); - - // Page should render correctly - const flightNumber = page.locator('span[class*="flightNumber"]'); - await expect(flightNumber).toBeVisible(); - }); - }); - - test.describe('US-50: Aircraft Information', () => { - test('should display aircraft type', async ({ page }) => { - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - // Look for aircraft type display - const aircraftType = page.locator('[class*="Aircraft"]'); - if ((await aircraftType.count()) > 0) { - await expect(aircraftType.first()).toBeVisible(); - } - }); - - test('should display aircraft registration if available', async ({ page }) => { - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - // Look for registration section - const registration = page.locator('[class*="registration"]'); - if ((await registration.count()) > 0) { - await expect(registration.first()).toBeVisible(); - } - }); - - test('should display seat configuration', async ({ page }) => { - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - // Look for seats section - const seats = page.locator('[class*="seats"]'); - if ((await seats.count()) > 0) { - await expect(seats.first()).toBeVisible(); - } - }); - - test('should be responsive on mobile', async ({ page }) => { - await page.setViewportSize({ width: 375, height: 667 }); - - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - const aircraft = page.locator('[class*="Aircraft"]'); - if ((await aircraft.count()) > 0) { - const box = await aircraft.first().boundingBox(); - expect(box?.width).toBeLessThanOrEqual(375); - } - }); - - test('should handle missing aircraft data', async ({ page }) => { - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su500}`); - await page.waitForLoadState('networkidle'); - - // Page should still render without errors - const mainContent = page.locator('main'); - await expect(mainContent).toBeVisible(); - }); - }); - - test.describe('US-51: Airline Information', () => { - test('should display airline name', async ({ page }) => { - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - // Look for airline name - const airlineName = page.locator('[class*="Airline"]'); - if ((await airlineName.count()) > 0) { - const text = await airlineName.first().textContent(); - expect(text).toBeTruthy(); - } - }); - - test('should display airline logo', async ({ page }) => { - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - // Look for airline logo image - const logo = page.locator('[class*="logo"]'); - if ((await logo.count()) > 0) { - await expect(logo.first()).toBeVisible(); - } - }); - - test('should display airline IATA code', async ({ page }) => { - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - // Look for airline code - const airlineCode = page.locator('[class*="code"]'); - if ((await airlineCode.count()) > 0) { - const text = await airlineCode.first().textContent(); - expect(text).toMatch(/^[A-Z]{2}$/); - } - }); - - test('should be responsive on mobile', async ({ page }) => { - await page.setViewportSize({ width: 375, height: 667 }); - - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - const airline = page.locator('[class*="Airline"]'); - if ((await airline.count()) > 0) { - const box = await airline.first().boundingBox(); - expect(box?.width).toBeLessThanOrEqual(375); - } - }); - - test('should support both locales', async ({ page }) => { - // Russian - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - const ruAirline = page.locator('[class*="Airline"]'); - expect(await ruAirline.count()).toBeGreaterThanOrEqual(0); - - // English - await page.goto(`${BASE_URL}/en-us/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - const enAirline = page.locator('[class*="Airline"]'); - expect(await enAirline.count()).toBeGreaterThanOrEqual(0); - }); - }); - - test.describe('US-52: Airport Information', () => { - test('should display departure airport code', async ({ page }) => { - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - // Look for airport codes - const codes = page.locator('[class*="code"]'); - const codeCount = await codes.count(); - expect(codeCount).toBeGreaterThanOrEqual(1); - }); - - test('should display departure airport name', async ({ page }) => { - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - // Look for airport names - const names = page.locator('[class*="name"]'); - const nameCount = await names.count(); - expect(nameCount).toBeGreaterThanOrEqual(1); - }); - - test('should display city name', async ({ page }) => { - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - // Look for city information - const cities = page.locator('[class*="city"]'); - const cityCount = await cities.count(); - expect(cityCount).toBeGreaterThanOrEqual(1); - }); - - test('should display terminal information if available', async ({ page }) => { - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - // Look for terminal section - const terminal = page.locator('[class*="terminal"]'); - if ((await terminal.count()) > 0) { - await expect(terminal.first()).toBeVisible(); - } - }); - - test('should be responsive on mobile', async ({ page }) => { - await page.setViewportSize({ width: 375, height: 667 }); - - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - const airport = page.locator('[class*="Airport"]'); - if ((await airport.count()) > 0) { - const box = await airport.first().boundingBox(); - expect(box?.width).toBeLessThanOrEqual(375); - } - }); - }); - - test.describe('US-53: Days of Operation', () => { - test('should display operating days indicator', async ({ page }) => { - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - // Look for days of operation - const daysSection = page.locator('[class*="Days"]'); - if ((await daysSection.count()) > 0) { - await expect(daysSection.first()).toBeVisible(); - } - }); - - test('should display week grid', async ({ page }) => { - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - // Look for day indicators - const dayItems = page.locator('[class*="dayItem"]'); - const dayCount = await dayItems.count(); - // Should have 7 days or display days in some form - if (dayCount > 0) { - expect(dayCount).toBeGreaterThanOrEqual(1); - } - }); - - test('should show operating days highlighted', async ({ page }) => { - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - // Look for operating day indicators - const operating = page.locator('[class*="operating"]'); - if ((await operating.count()) > 0) { - await expect(operating.first()).toBeVisible(); - } - }); - - test('should be responsive on mobile', async ({ page }) => { - await page.setViewportSize({ width: 375, height: 667 }); - - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - const days = page.locator('[class*="Days"]'); - if ((await days.count()) > 0) { - const box = await days.first().boundingBox(); - expect(box?.width).toBeLessThanOrEqual(375); - } - }); - }); - - test.describe('US-54: Flight Actions', () => { - test('should display buy ticket button', async ({ page }) => { - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - const buyButton = page.locator( - 'button:has-text("Buy"), button:has-text("Ticket"), [class*="buyTicket"]', - ); - if ((await buyButton.count()) > 0) { - await expect(buyButton.first()).toBeVisible(); - } - }); - - test('should display print button', async ({ page }) => { - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - const printButton = page.locator('button:has-text("Print"), [class*="printItinerary"]'); - if ((await printButton.count()) > 0) { - await expect(printButton.first()).toBeVisible(); - } - }); - - test('should display share button', async ({ page }) => { - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - const shareButton = page.locator('button:has-text("Share"), [class*="shareFlight"]'); - if ((await shareButton.count()) > 0) { - await expect(shareButton.first()).toBeVisible(); - } - }); - - test('should have responsive button layout', async ({ page }) => { - await page.setViewportSize({ width: 375, height: 667 }); - - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - const buttons = page.locator('button'); - const count = await buttons.count(); - expect(count).toBeGreaterThan(0); - }); - - test('should work on tablet size', async ({ page }) => { - await page.setViewportSize({ width: 768, height: 1024 }); - - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - const buttons = page.locator('button'); - const count = await buttons.count(); - expect(count).toBeGreaterThan(0); - }); - }); - - test.describe('US-55: Route Timeline', () => { - test('should display flight route timeline', async ({ page }) => { - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - // Look for route/timeline elements - const timeline = page.locator('[class*="timeline"]'); - if ((await timeline.count()) > 0) { - await expect(timeline.first()).toBeVisible(); - } - }); - - test('should display departure point', async ({ page }) => { - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - const departureCodes = page.locator('[class*="code"]'); - const count = await departureCodes.count(); - expect(count).toBeGreaterThanOrEqual(1); - }); - - test('should display arrival point', async ({ page }) => { - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - const arrivalCodes = page.locator('[class*="code"]'); - const count = await arrivalCodes.count(); - // Should have at least 2 codes (departure and arrival) - expect(count).toBeGreaterThanOrEqual(1); - }); - - test('should display times on route', async ({ page }) => { - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - const times = page.locator('[class*="time"]'); - const count = await times.count(); - expect(count).toBeGreaterThanOrEqual(1); - }); - - test('should be responsive on mobile', async ({ page }) => { - await page.setViewportSize({ width: 375, height: 667 }); - - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - const timeline = page.locator('[class*="timeline"]'); - if ((await timeline.count()) > 0) { - const box = await timeline.first().boundingBox(); - expect(box?.width).toBeLessThanOrEqual(375); - } - }); - }); - - test.describe('US-56: Transfer Information', () => { - test('should display transfer information section', async ({ page }) => { - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - // Look for transfer section - const transfer = page.locator('[class*="Transfer"]'); - if ((await transfer.count()) > 0) { - await expect(transfer.first()).toBeVisible(); - } - }); - - test('should display layover time if available', async ({ page }) => { - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - const layover = page.locator('[class*="layover"]'); - if ((await layover.count()) > 0) { - await expect(layover.first()).toBeVisible(); - } - }); - - test('should display baggage information if available', async ({ page }) => { - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - const baggage = page.locator('[class*="baggage"]'); - if ((await baggage.count()) > 0) { - await expect(baggage.first()).toBeVisible(); - } - }); - - test('should display visa requirements if needed', async ({ page }) => { - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - const visa = page.locator('[class*="visa"]'); - if ((await visa.count()) > 0) { - await expect(visa.first()).toBeVisible(); - } - }); - - test('should be responsive on mobile', async ({ page }) => { - await page.setViewportSize({ width: 375, height: 667 }); - - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - const transfer = page.locator('[class*="Transfer"]'); - if ((await transfer.count()) > 0) { - const box = await transfer.first().boundingBox(); - expect(box?.width).toBeLessThanOrEqual(375); - } - }); - - test('should support both locales', async ({ page }) => { - // Russian - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - expect(await page.content()).toContain('ru-ru'); - - // English - await page.goto(`${BASE_URL}/en-us/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - expect(await page.content()).toContain('en-us'); - }); - }); - - test.describe('US-40: Cabin Services', () => { - test('should display cabin services section when available', async ({ page }) => { - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - // Services may or may not be available depending on flight data - const pageContent = await page.content(); - expect(pageContent).toBeDefined(); - }); - - test('should display meal services when included', async ({ page }) => { - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su200}`); - await page.waitForLoadState('networkidle'); - - // Check for meal-related content - const pageContent = await page.content(); - expect(pageContent).toBeDefined(); - }); - - test('should show amenities with proper styling', async ({ page }) => { - await page.goto(`${BASE_URL}/en-us/onlineboard/flights/${TEST_FLIGHTS.su500}`); - await page.waitForLoadState('networkidle'); - - const pageContent = await page.content(); - expect(pageContent).toBeDefined(); - }); - - test('should handle missing services gracefully', async ({ page }) => { - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - // Page should load without errors even if no services - const errors: string[] = []; - page.on('console', (msg) => { - if (msg.type() === 'error') { - errors.push(msg.text()); - } - }); - - expect(errors.length).toBe(0); - }); - }); - - test.describe('US-41: Flight Schedule and Time Information', () => { - test('should display departure and arrival times', async ({ page }) => { - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - // Flight details should contain time information - const pageContent = await page.content(); - expect(pageContent).toContain(':'); // Time format includes colons - }); - - test('should display flight duration', async ({ page }) => { - await page.goto(`${BASE_URL}/en-us/onlineboard/flights/${TEST_FLIGHTS.su200}`); - await page.waitForLoadState('networkidle'); - - const pageContent = await page.content(); - expect(pageContent).toBeDefined(); - }); - - test('should show timezone information', async ({ page }) => { - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su500}`); - await page.waitForLoadState('networkidle'); - - const pageContent = await page.content(); - expect(pageContent).toBeDefined(); - }); - - test('should display operating days information', async ({ page }) => { - await page.goto(`${BASE_URL}/en-us/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - const pageContent = await page.content(); - expect(pageContent).toBeDefined(); - }); - - test('should be responsive on mobile viewport', async ({ page }) => { - await page.setViewportSize({ width: 375, height: 667 }); - - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su200}`); - await page.waitForLoadState('networkidle'); - - // Schedule info should be visible and properly sized - const viewport = await page.evaluate(() => ({ - width: window.innerWidth, - height: window.innerHeight, - })); - - expect(viewport.width).toBeLessThanOrEqual(375); - }); - }); - - test.describe('US-62: Share Flight Information', () => { - test('should display share button on flight details page', async ({ page }) => { - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - const shareButton = page.locator('button[aria-label*="Share"], button:has-text("Share")'); - // Share button might be in FlightActions - const pageContent = await page.content(); - expect(pageContent).toBeDefined(); - }); - - test('should handle share button click on desktop', async ({ page }) => { - await page.goto(`${BASE_URL}/en-us/onlineboard/flights/${TEST_FLIGHTS.su200}`); - await page.waitForLoadState('networkidle'); - - // Check that page loads without errors - const errors: string[] = []; - page.on('console', (msg) => { - if (msg.type() === 'error') { - errors.push(msg.text()); - } - }); - - expect(errors.length).toBe(0); - }); - - test('should show mobile-friendly share UI on mobile', async ({ page }) => { - await page.setViewportSize({ width: 375, height: 667 }); - - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su500}`); - await page.waitForLoadState('networkidle'); - - const viewport = await page.evaluate(() => window.innerWidth); - expect(viewport).toBeLessThanOrEqual(375); - }); - - test('should support share across different locales', async ({ page }) => { - // Russian - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - let pageContent = await page.content(); - expect(pageContent).toBeDefined(); - - // English - await page.goto(`${BASE_URL}/en-us/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - pageContent = await page.content(); - expect(pageContent).toBeDefined(); - }); - - test('should not break page when share is clicked', async ({ page }) => { - await page.goto(`${BASE_URL}/en-us/onlineboard/flights/${TEST_FLIGHTS.su200}`); - await page.waitForLoadState('networkidle'); - - // Try to find and click share button if it exists - const shareBtnLocator = page.locator('button[aria-label*="Share"], button:has-text("Share")'); - const buttonCount = await shareBtnLocator.count(); - - if (buttonCount > 0) { - await shareBtnLocator.first().click(); - // Wait for potential feedback - await page.waitForTimeout(500); - } - - // Page should still be functional - const main = page.locator('main'); - await expect(main).toBeVisible(); - }); - }); - - test.describe('US-63: No Errors on Flight Details Page', () => { - test('should have no console errors on page load', async ({ page }) => { - const errors: string[] = []; - page.on('console', (msg) => { - if (msg.type() === 'error') { - errors.push(msg.text()); - } - }); - - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - expect(errors).toHaveLength(0); - }); - - test('should have no console warnings on page load', async ({ page }) => { - const warnings: string[] = []; - page.on('console', (msg) => { - if (msg.type() === 'warning') { - warnings.push(msg.text()); - } - }); - - await page.goto(`${BASE_URL}/en-us/onlineboard/flights/${TEST_FLIGHTS.su200}`); - await page.waitForLoadState('networkidle'); - - // Should have minimal warnings (may have some third-party) - const appWarnings = warnings.filter((w) => !w.includes('third-party')); - expect(appWarnings.length).toBeLessThanOrEqual(0); - }); - - test('should handle missing flight data without crashing', async ({ page }) => { - const errors: string[] = []; - page.on('console', (msg) => { - if (msg.type() === 'error') { - errors.push(msg.text()); - } - }); - - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/INVALID`); - await page.waitForLoadState('networkidle'); - - // May show error message but should not have JS errors - const appErrors = errors.filter((e) => !e.includes('404') && !e.includes('not found')); - expect(appErrors.length).toBeLessThanOrEqual(5); - }); - - test('should not have rendering errors across viewports', async ({ page }) => { - const errors: string[] = []; - page.on('console', (msg) => { - if (msg.type() === 'error') { - errors.push(msg.text()); - } - }); - - // Mobile - await page.setViewportSize({ width: 375, height: 667 }); - await page.goto(`${BASE_URL}/en-us/onlineboard/flights/${TEST_FLIGHTS.su500}`); - await page.waitForLoadState('networkidle'); - - expect(errors).toHaveLength(0); - }); - }); - - test.describe('US-64: Data Integrity', () => { - test('should display all required fields when available', async ({ page }) => { - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - // Check for flight number (always present) - const flightNumberRegex = /[A-Z]{2}\d+/; - const pageContent = await page.content(); - expect(pageContent).toMatch(flightNumberRegex); - }); - - test('should handle missing optional fields gracefully', async ({ page }) => { - await page.goto(`${BASE_URL}/en-us/onlineboard/flights/${TEST_FLIGHTS.su200}`); - await page.waitForLoadState('networkidle'); - - const errors: string[] = []; - page.on('console', (msg) => { - if (msg.type() === 'error') { - errors.push(msg.text()); - } - }); - - expect(errors).toHaveLength(0); - }); - - test('should validate data types are displayed correctly', async ({ page }) => { - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su500}`); - await page.waitForLoadState('networkidle'); - - const pageContent = await page.content(); - // Flight details should contain proper data structures - expect(pageContent).toBeDefined(); - expect(pageContent.length).toBeGreaterThan(100); - }); - - test('should not display invalid or malformed data', async ({ page }) => { - await page.goto(`${BASE_URL}/en-us/onlineboard/flights/${TEST_FLIGHTS.su1402}`); - await page.waitForLoadState('networkidle'); - - // Look for obviously malformed patterns (multiple consecutive special chars, etc) - const pageContent = await page.content(); - // eslint-disable-next-line no-useless-escape - const malformedPattern = /[^\w\s\-:,.()\/\\]{3,}/g; - const matches = pageContent.match(malformedPattern) || []; - - // Should have minimal malformed data (exclude data URIs, etc) - const actualMalformed = matches.filter((m) => !m.includes('data:')); - expect(actualMalformed.length).toBeLessThanOrEqual(10); - }); - - test('should handle edge cases with null and empty values', async ({ page }) => { - await page.goto(`${BASE_URL}/ru-ru/onlineboard/flights/${TEST_FLIGHTS.su200}`); - await page.waitForLoadState('networkidle'); - - const errors: string[] = []; - page.on('console', (msg) => { - if (msg.type() === 'error') { - errors.push(msg.text()); - } - }); - - // Interact with page to trigger any error handling - await page.locator('main').scrollIntoViewIfNeeded(); - - expect(errors).toHaveLength(0); - }); - }); -}); diff --git a/tests/e2e-angular/flight-results.spec.ts b/tests/e2e-angular/flight-results.spec.ts deleted file mode 100644 index f2e2e9a8..00000000 --- a/tests/e2e-angular/flight-results.spec.ts +++ /dev/null @@ -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(); - } - } - }); - }); -}); diff --git a/tests/e2e-angular/flights-map.spec.ts b/tests/e2e-angular/flights-map.spec.ts deleted file mode 100644 index 0c86b442..00000000 --- a/tests/e2e-angular/flights-map.spec.ts +++ /dev/null @@ -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); - }); - }); -}); diff --git a/tests/e2e-angular/integration/08 - online board - route.spec.ts b/tests/e2e-angular/integration/08 - online board - route.spec.ts deleted file mode 100644 index 33c8e754..00000000 --- a/tests/e2e-angular/integration/08 - online board - route.spec.ts +++ /dev/null @@ -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); - }); - }); -}); diff --git a/tests/e2e-angular/integration/10 - schedule - search.spec.ts b/tests/e2e-angular/integration/10 - schedule - search.spec.ts deleted file mode 100644 index a2ca0637..00000000 --- a/tests/e2e-angular/integration/10 - schedule - search.spec.ts +++ /dev/null @@ -1,1453 +0,0 @@ -import { test, expect } from '@playwright/test'; -import type { Page } from '@playwright/test'; -import { - buildSchedulePath, - buildRouteParam, - generateScheduleEntry, - generateScheduleEntries, - getToday, - getTomorrow, - getFutureDate, - getPastDate, - CITIES, - FIXTURES, -} from '../support/test-utilities'; - -const today = getToday(); -const tomorrow = getTomorrow(); -const futureDate = getFutureDate(7); -const pastDate = getPastDate(7); -const dateFrom = today; -const dateTo = tomorrow; - -// ============================================================================ -// Schedule Search Tests (40+ tests) -// ============================================================================ - -test.describe('Schedule Search', () => { - test.describe('Category 1: Basic Schedule Search', () => { - test('Should search by departure and arrival city (manual input) (Test 1)', async ({ - page, - }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('Moscow'); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('Sochi'); - - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - await searchButton.click(); - await page.waitForLoadState('networkidle'); - - const results = page.locator('[data-testid="schedule-search-result"]'); - await expect(results).toHaveCount(50); - }); - - test('Should search by cities from autocomplete list (Test 2)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('Moscow'); - await departureInput.press('Enter'); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('Sochi'); - await arrivalInput.press('Enter'); - - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - await searchButton.click(); - await page.waitForLoadState('networkidle'); - - const results = page.locator('[data-testid="schedule-search-result"]'); - await expect(results).toHaveCount(50); - }); - - test('Should search with today date (Test 3)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('Moscow'); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('Sochi'); - - const dateInput = page.locator('[data-testid="schedule-date-input"]'); - await dateInput.fill(today); - - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - await searchButton.click(); - await page.waitForLoadState('networkidle'); - - const results = page.locator('[data-testid="schedule-search-result"]'); - await expect(results).toHaveCount(50); - }); - - test('Should search with future date (Test 4)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('Moscow'); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('Sochi'); - - const dateInput = page.locator('[data-testid="schedule-date-input"]'); - await dateInput.fill(futureDate); - - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - await searchButton.click(); - await page.waitForLoadState('networkidle'); - - const results = page.locator('[data-testid="schedule-search-result"]'); - await expect(results).toHaveCount(50); - }); - - test('Should search with past date and show validation (Test 5)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('Moscow'); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('Sochi'); - - const dateInput = page.locator('[data-testid="schedule-date-input"]'); - await dateInput.fill(pastDate); - - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - await searchButton.click(); - - const validationError = page.locator('[data-testid="schedule-validation-error"]'); - await expect(validationError).toBeVisible(); - }); - - test('Should search without date and show validation (Test 6)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('Moscow'); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('Sochi'); - - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - await searchButton.click(); - - const validationError = page.locator('[data-testid="schedule-validation-error"]'); - await expect(validationError).toBeVisible(); - }); - - test('Should search with invalid cities and show error (Test 7)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('Invalid City'); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('Another Invalid City'); - - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - await searchButton.click(); - await page.waitForLoadState('networkidle'); - - const noResults = page.locator('[data-testid="schedule-no-results"]'); - await expect(noResults).toBeVisible(); - }); - - test('Should search with same departure and arrival and show validation (Test 8)', async ({ - page, - }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('Moscow'); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('Moscow'); - - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - await searchButton.click(); - - const validationError = page.locator('[data-testid="schedule-validation-error"]'); - await expect(validationError).toBeVisible(); - }); - }); - - test.describe('Category 2: Round-Trip Search', () => { - test('Should search with outbound date only (Test 9)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('Moscow'); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('Sochi'); - - const outboundDateInput = page.locator('[data-testid="schedule-outbound-date-input"]'); - await outboundDateInput.fill(today); - - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - await searchButton.click(); - await page.waitForLoadState('networkidle'); - - const results = page.locator('[data-testid="schedule-search-result"]'); - await expect(results).toHaveCount(50); - }); - - test('Should search with outbound and return dates (Test 10)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('Moscow'); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('Sochi'); - - const outboundDateInput = page.locator('[data-testid="schedule-outbound-date-input"]'); - await outboundDateInput.fill(today); - - const returnDateInput = page.locator('[data-testid="schedule-return-date-input"]'); - await returnDateInput.fill(tomorrow); - - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - await searchButton.click(); - await page.waitForLoadState('networkidle'); - - const results = page.locator('[data-testid="schedule-search-result"]'); - await expect(results).toHaveCount(50); - }); - - test('Should select return date from calendar (Test 11)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('Moscow'); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('Sochi'); - - const calendarInput = page.locator('[data-testid="schedule-calendar"]'); - await calendarInput.click(); - - const calendarDate = page.locator('[data-testid="calendar-date"]').first(); - await calendarDate.click(); - - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - await searchButton.click(); - await page.waitForLoadState('networkidle'); - - const results = page.locator('[data-testid="schedule-search-result"]'); - await expect(results).toHaveCount(50); - }); - - test('Should verify date range selection (Test 12)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('Moscow'); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('Sochi'); - - const dateRange = page.locator('[data-testid="schedule-date-range"]'); - await expect(dateRange).toBeVisible(); - }); - - test('Should verify date format DD.MM.YYYY (Test 13)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('Moscow'); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('Sochi'); - - const dateInput = page.locator('[data-testid="schedule-date-input"]'); - await dateInput.fill(today); - - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - await searchButton.click(); - await page.waitForLoadState('networkidle'); - - const dateText = page.locator('[data-testid="schedule-date-display"]'); - 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 14)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const dateInput = page.locator('[data-testid="schedule-date-input"]'); - await expect(dateInput).toBeEnabled(); - }); - - test('Should search with invalid return date and show error (Test 15)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('Moscow'); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('Sochi'); - - const outboundDateInput = page.locator('[data-testid="schedule-outbound-date-input"]'); - await outboundDateInput.fill(today); - - const returnDateInput = page.locator('[data-testid="schedule-return-date-input"]'); - await returnDateInput.fill('invalid-date'); - - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - await searchButton.click(); - - const validationError = page.locator('[data-testid="schedule-validation-error"]'); - await expect(validationError).toBeVisible(); - }); - - test('Should search with return date before outbound date and show validation (Test 16)', async ({ - page, - }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('Moscow'); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('Sochi'); - - const outboundDateInput = page.locator('[data-testid="schedule-outbound-date-input"]'); - await outboundDateInput.fill(futureDate); - - const returnDateInput = page.locator('[data-testid="schedule-return-date-input"]'); - await returnDateInput.fill(today); - - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - await searchButton.click(); - - const validationError = page.locator('[data-testid="schedule-validation-error"]'); - await expect(validationError).toBeVisible(); - }); - - test('Should exchange departure and arrival cities (Test 17)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('Moscow'); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('Sochi'); - - const exchangeButton = page.locator('[data-testid="schedule-exchange-button"]'); - await exchangeButton.click(); - - const departureValue = await departureInput.inputValue(); - const arrivalValue = await arrivalInput.inputValue(); - - expect(departureValue).toContain('Sochi'); - expect(arrivalValue).toContain('Moscow'); - }); - - test('Should verify round-trip URL pattern (Test 18)', 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/); - await expect(page).toHaveURL(/from=MOW/); - await expect(page).toHaveURL(/to=AER/); - await expect(page).toHaveURL(/dateFrom=/); - await expect(page).toHaveURL(/dateTo=/); - }); - }); - - test.describe('Category 3: Flight Results', () => { - test('Should verify flight results display (Test 19)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('Moscow'); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('Sochi'); - - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - await searchButton.click(); - await page.waitForLoadState('networkidle'); - - const results = page.locator('[data-testid="schedule-search-results"]'); - await expect(results).toBeVisible(); - }); - - test('Should verify flight count (Test 20)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('Moscow'); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('Sochi'); - - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - await searchButton.click(); - await page.waitForLoadState('networkidle'); - - const results = page.locator('[data-testid="schedule-search-result"]'); - await expect(results).toHaveCount(50); - }); - - test('Should verify flight details in results (Test 21)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('Moscow'); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('Sochi'); - - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - await searchButton.click(); - await page.waitForLoadState('networkidle'); - - const result = page.locator('[data-testid="schedule-search-result"]').first(); - await expect(result).toBeVisible(); - - const flightNumber = result.locator('[data-testid="schedule-flight-number"]'); - await expect(flightNumber).toBeVisible(); - }); - - test('Should verify empty results message (Test 22)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('Unknown City'); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('Unknown City'); - - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - await searchButton.click(); - await page.waitForLoadState('networkidle'); - - const noResults = page.locator('[data-testid="schedule-no-results"]'); - await expect(noResults).toBeVisible(); - await expect(noResults).toContainText('Нет результатов'); - }); - - test('Should verify loading state (Test 23)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('Moscow'); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('Sochi'); - - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - await searchButton.click(); - - const loadingSpinner = page.locator('[data-testid="schedule-loading-spinner"]'); - await expect(loadingSpinner).toBeVisible(); - }); - - test('Should verify error state (Test 24)', async ({ page }) => { - await page.route('**/api/schedule/**', (route) => { - return route.abort('internetdisconnected'); - }); - - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('Moscow'); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('Sochi'); - - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - await searchButton.click(); - - const networkError = page.locator('[data-testid="schedule-network-error"]'); - await expect(networkError).toBeVisible(); - }); - - test('Should verify sort options (Test 25)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const sortButton = page.locator('[data-testid="schedule-sort-button"]'); - await expect(sortButton).toBeVisible(); - - await sortButton.click(); - - const sortOption = page.locator('[data-testid="sort-option-time"]'); - await expect(sortOption).toBeVisible(); - }); - - test('Should verify filter options (Test 26)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const filterButton = page.locator('[data-testid="schedule-filter-button"]'); - await expect(filterButton).toBeVisible(); - - await filterButton.click(); - - const filterOption = page.locator('[data-testid="filter-option-direct"]'); - await expect(filterOption).toBeVisible(); - }); - }); - - test.describe('Category 4: Flight Details', () => { - test('Should open flight details from results (Test 27)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('Moscow'); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('Sochi'); - - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - await searchButton.click(); - await page.waitForLoadState('networkidle'); - - const result = page.locator('[data-testid="schedule-search-result"]').first(); - await result.click(); - await page.waitForLoadState('networkidle'); - - await expect(page).toHaveURL(/schedule\/detail/); - }); - - test('Should verify flight details content (Test 28)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('Moscow'); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('Sochi'); - - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - await searchButton.click(); - await page.waitForLoadState('networkidle'); - - const result = page.locator('[data-testid="schedule-search-result"]').first(); - await result.click(); - await page.waitForLoadState('networkidle'); - - const flightNumber = page.locator('[data-testid="schedule-flight-number"]'); - await expect(flightNumber).toBeVisible(); - }); - - test('Should verify flight route details (Test 29)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('Moscow'); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('Sochi'); - - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - await searchButton.click(); - await page.waitForLoadState('networkidle'); - - const result = page.locator('[data-testid="schedule-search-result"]').first(); - await result.click(); - await page.waitForLoadState('networkidle'); - - const departureCity = page.locator('[data-testid="schedule-departure-city"]'); - await expect(departureCity).toBeVisible(); - - const arrivalCity = page.locator('[data-testid="schedule-arrival-city"]'); - await expect(arrivalCity).toBeVisible(); - }); - - test('Should verify flight status details (Test 30)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('Moscow'); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('Sochi'); - - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - await searchButton.click(); - await page.waitForLoadState('networkidle'); - - const result = page.locator('[data-testid="schedule-search-result"]').first(); - await result.click(); - await page.waitForLoadState('networkidle'); - - const status = page.locator('[data-testid="schedule-status"]'); - await expect(status).toBeVisible(); - }); - - test('Should close flight details (Test 31)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('Moscow'); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('Sochi'); - - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - await searchButton.click(); - await page.waitForLoadState('networkidle'); - - const result = page.locator('[data-testid="schedule-search-result"]').first(); - await result.click(); - await page.waitForLoadState('networkidle'); - - const closeBtn = page.locator('[data-testid="schedule-close-details-btn"]'); - await closeBtn.click(); - await page.waitForLoadState('networkidle'); - - await expect(page).toHaveURL(/schedule/); - }); - }); - - test.describe('Category 5: Edge Cases', () => { - test('Should search for non-existent cities (Test 32)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('XXX'); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('YYY'); - - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - await searchButton.click(); - await page.waitForLoadState('networkidle'); - - const noResults = page.locator('[data-testid="schedule-no-results"]'); - await expect(noResults).toBeVisible(); - }); - - test('Should search with special characters (Test 33)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('Moscow!@#$%'); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('Sochi!@#$%'); - - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - await searchButton.click(); - await page.waitForLoadState('networkidle'); - - const noResults = page.locator('[data-testid="schedule-no-results"]'); - await expect(noResults).toBeVisible(); - }); - - test('Should search with very long city names (Test 34)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const longCityName = 'Москва'.repeat(10); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill(longCityName); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill(longCityName); - - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - await searchButton.click(); - - const validationError = page.locator('[data-testid="schedule-validation-error"]'); - await expect(validationError).toBeVisible(); - }); - - test('Should search with Unicode characters (Test 35)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('Moscow 🛫'); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('Sochi 🛬'); - - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - await searchButton.click(); - await page.waitForLoadState('networkidle'); - - const noResults = page.locator('[data-testid="schedule-no-results"]'); - await expect(noResults).toBeVisible(); - }); - - test('Should handle rapid search attempts (Test 36)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - - for (let i = 0; i < 5; i++) { - await departureInput.fill('Moscow'); - await arrivalInput.fill('Sochi'); - await searchButton.click(); - await page.waitForLoadState('networkidle'); - } - - const results = page.locator('[data-testid="schedule-search-result"]'); - await expect(results).toHaveCount(50); - }); - - test('Should search with invalid date range (Test 37)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('Moscow'); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('Sochi'); - - const dateFromInput = page.locator('[data-testid="schedule-date-from-input"]'); - await dateFromInput.fill('invalid'); - - const dateToInput = page.locator('[data-testid="schedule-date-to-input"]'); - await dateToInput.fill('invalid'); - - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - await searchButton.click(); - - const validationError = page.locator('[data-testid="schedule-validation-error"]'); - await expect(validationError).toBeVisible(); - }); - - test('Should search with return date before outbound date (Test 38)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('Moscow'); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('Sochi'); - - const outboundDateInput = page.locator('[data-testid="schedule-outbound-date-input"]'); - await outboundDateInput.fill(futureDate); - - const returnDateInput = page.locator('[data-testid="schedule-return-date-input"]'); - await returnDateInput.fill(today); - - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - await searchButton.click(); - - const validationError = page.locator('[data-testid="schedule-validation-error"]'); - await expect(validationError).toBeVisible(); - }); - - test('Should search with very long date range (Test 39)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('Moscow'); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('Sochi'); - - const dateFromInput = page.locator('[data-testid="schedule-date-from-input"]'); - await dateFromInput.fill(today); - - const dateToInput = page.locator('[data-testid="schedule-date-to-input"]'); - await dateToInput.fill(getFutureDate(365)); - - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - await searchButton.click(); - await page.waitForLoadState('networkidle'); - - const results = page.locator('[data-testid="schedule-search-result"]'); - await expect(results).toHaveCount(50); - }); - - test('Should search with empty cities (Test 40)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill(''); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill(''); - - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - await searchButton.click(); - - const validationError = page.locator('[data-testid="schedule-validation-error"]'); - await expect(validationError).toBeVisible(); - }); - - test('Should search for different cities (Test 41)', async ({ page }) => { - const cities = ['MOW', 'LED', 'AER', 'OVB', 'KRR']; - - for (const cityCode of cities) { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill(cityCode); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('AER'); - - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - await searchButton.click(); - await page.waitForLoadState('networkidle'); - - const results = page.locator('[data-testid="schedule-search-result"]'); - await expect(results).toHaveCount(50); - } - }); - - test('Should display correct date in title (Test 42)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('Moscow'); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('Sochi'); - - const dateInput = page.locator('[data-testid="schedule-date-input"]'); - await dateInput.fill(today); - - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - await searchButton.click(); - await page.waitForLoadState('networkidle'); - - const dateText = page.locator('[data-testid="schedule-date-display"]'); - await expect(dateText).toBeVisible(); - await expect(dateText).toContainText(today); - }); - - test('Should filter by airline (Test 43)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const airlineFilter = page.locator('[data-testid="schedule-airline-filter"]'); - await airlineFilter.click(); - - const aeroflotOption = page.locator('[data-testid="filter-option-SU"]'); - await aeroflotOption.click(); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('Moscow'); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('Sochi'); - - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - await searchButton.click(); - await page.waitForLoadState('networkidle'); - - const results = page.locator('[data-testid="schedule-search-result"]'); - await expect(results).toHaveCount(50); - }); - - test('Should filter by direct flights (Test 44)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const directFilter = page.locator('[data-testid="schedule-direct-filter"]'); - await directFilter.click(); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('Moscow'); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('Sochi'); - - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - await searchButton.click(); - await page.waitForLoadState('networkidle'); - - const results = page.locator('[data-testid="schedule-search-result"]'); - await expect(results).toHaveCount(50); - }); - - test('Should display flight number (Test 45)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('Moscow'); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('Sochi'); - - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - await searchButton.click(); - await page.waitForLoadState('networkidle'); - - const result = page.locator('[data-testid="schedule-search-result"]').first(); - await expect(result).toBeVisible(); - - const flightNumber = result.locator('[data-testid="schedule-flight-number"]'); - await expect(flightNumber).toBeVisible(); - }); - - test('Should display airline name (Test 46)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('Moscow'); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('Sochi'); - - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - await searchButton.click(); - await page.waitForLoadState('networkidle'); - - const result = page.locator('[data-testid="schedule-search-result"]').first(); - await expect(result).toBeVisible(); - - const airlineName = result.locator('[data-testid="schedule-airline-name"]'); - await expect(airlineName).toBeVisible(); - }); - - test('Should display departure and arrival cities (Test 47)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('Moscow'); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('Sochi'); - - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - await searchButton.click(); - await page.waitForLoadState('networkidle'); - - const result = page.locator('[data-testid="schedule-search-result"]').first(); - await expect(result).toBeVisible(); - - const depCity = result.locator('[data-testid="schedule-departure-city"]'); - await expect(depCity).toBeVisible(); - - const arrCity = result.locator('[data-testid="schedule-arrival-city"]'); - await expect(arrCity).toBeVisible(); - }); - - test('Should display scheduled time (Test 48)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('Moscow'); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('Sochi'); - - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - await searchButton.click(); - await page.waitForLoadState('networkidle'); - - const result = page.locator('[data-testid="schedule-search-result"]').first(); - await expect(result).toBeVisible(); - - const depTime = result.locator('[data-testid="schedule-departure-time"]'); - await expect(depTime).toBeVisible(); - }); - - test('Should display arrival time (Test 49)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('Moscow'); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('Sochi'); - - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - await searchButton.click(); - await page.waitForLoadState('networkidle'); - - const result = page.locator('[data-testid="schedule-search-result"]').first(); - await expect(result).toBeVisible(); - - const arrTime = result.locator('[data-testid="schedule-arrival-time"]'); - await expect(arrTime).toBeVisible(); - }); - - test('Should display aircraft type (Test 50)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('Moscow'); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('Sochi'); - - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - await searchButton.click(); - await page.waitForLoadState('networkidle'); - - const result = page.locator('[data-testid="schedule-search-result"]').first(); - await expect(result).toBeVisible(); - - const aircraftType = result.locator('[data-testid="schedule-aircraft-type"]'); - await expect(aircraftType).toBeVisible(); - }); - }); - - test.describe('Additional Schedule Tests', () => { - test('Should handle network error (Test 51)', async ({ page }) => { - await page.route('**/api/schedule/**', (route) => { - return route.abort('internetdisconnected'); - }); - - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('Moscow'); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('Sochi'); - - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - await searchButton.click(); - - const networkError = page.locator('[data-testid="schedule-network-error"]'); - await expect(networkError).toBeVisible(); - }); - - test('Should have proper ARIA labels (Test 52)', 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 (Test 53)', 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(); - }); - - test('Should search by flight number (Test 54)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('Moscow'); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('Sochi'); - - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - await searchButton.click(); - await page.waitForLoadState('networkidle'); - - const flightSearchInput = page.locator('[data-testid="schedule-flight-search-input"]'); - await flightSearchInput.fill('SU 1234'); - await flightSearchInput.press('Enter'); - await page.waitForLoadState('networkidle'); - - const results = page.locator('[data-testid="schedule-search-result"]'); - await expect(results).toHaveCount(50); - }); - - test('Should show no results when flight not found (Test 55)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('Moscow'); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('Sochi'); - - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - await searchButton.click(); - await page.waitForLoadState('networkidle'); - - const flightSearchInput = page.locator('[data-testid="schedule-flight-search-input"]'); - await flightSearchInput.fill('SU 9999'); - await flightSearchInput.press('Enter'); - await page.waitForLoadState('networkidle'); - - const noResults = page.locator('[data-testid="schedule-no-results"]'); - await expect(noResults).toBeVisible(); - }); - - test('Should search by route (Test 56)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const routeTab = page.locator('[data-testid="schedule-route-search-tab"]'); - await routeTab.click(); - await page.waitForTimeout(500); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('Moscow'); - await page.waitForTimeout(500); - await departureInput.press('Enter'); - await page.waitForTimeout(500); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('Sochi'); - await page.waitForTimeout(500); - await arrivalInput.press('Enter'); - await page.waitForLoadState('networkidle'); - - const results = page.locator('[data-testid="schedule-search-result"]'); - await expect(results).toHaveCount(50); - }); - - test('Should display days of week (Test 57)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('Moscow'); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('Sochi'); - - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - await searchButton.click(); - await page.waitForLoadState('networkidle'); - - const result = page.locator('[data-testid="schedule-search-result"]').first(); - await expect(result).toBeVisible(); - - const daysOfWeek = result.locator('[data-testid="schedule-days-of-week"]'); - await expect(daysOfWeek).toBeVisible(); - }); - - test('Should display effective date range (Test 58)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('Moscow'); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('Sochi'); - - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - await searchButton.click(); - await page.waitForLoadState('networkidle'); - - const result = page.locator('[data-testid="schedule-search-result"]').first(); - await expect(result).toBeVisible(); - - const dateRange = result.locator('[data-testid="schedule-date-range"]'); - await expect(dateRange).toBeVisible(); - }); - - test('Should search with different date ranges (Test 59)', async ({ page }) => { - const dateRanges = [ - { from: today, to: tomorrow }, - { from: today, to: getFutureDate(3) }, - { from: today, to: getFutureDate(7) }, - ]; - - for (const range of dateRanges) { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('Moscow'); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('Sochi'); - - const dateFromInput = page.locator('[data-testid="schedule-date-from-input"]'); - await dateFromInput.fill(range.from); - - const dateToInput = page.locator('[data-testid="schedule-date-to-input"]'); - await dateToInput.fill(range.to); - - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - await searchButton.click(); - await page.waitForLoadState('networkidle'); - - const results = page.locator('[data-testid="schedule-search-result"]'); - await expect(results).toHaveCount(50); - } - }); - - test('Should search with one-way trip (Test 60)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('Moscow'); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('Sochi'); - - const oneWayTab = page.locator('[data-testid="schedule-one-way-tab"]'); - await oneWayTab.click(); - await page.waitForTimeout(500); - - const dateInput = page.locator('[data-testid="schedule-date-input"]'); - await dateInput.fill(today); - - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - await searchButton.click(); - await page.waitForLoadState('networkidle'); - - const results = page.locator('[data-testid="schedule-search-result"]'); - await expect(results).toHaveCount(50); - }); - - test('Should search with round-trip (Test 61)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('Moscow'); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('Sochi'); - - const roundTripTab = page.locator('[data-testid="schedule-round-trip-tab"]'); - await roundTripTab.click(); - await page.waitForTimeout(500); - - const outboundDateInput = page.locator('[data-testid="schedule-outbound-date-input"]'); - await outboundDateInput.fill(today); - - const returnDateInput = page.locator('[data-testid="schedule-return-date-input"]'); - await returnDateInput.fill(tomorrow); - - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - await searchButton.click(); - await page.waitForLoadState('networkidle'); - - const results = page.locator('[data-testid="schedule-search-result"]'); - await expect(results).toHaveCount(50); - }); - - test('Should switch between one-way and round-trip (Test 62)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('Moscow'); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('Sochi'); - - const roundTripTab = page.locator('[data-testid="schedule-round-trip-tab"]'); - await roundTripTab.click(); - await page.waitForTimeout(500); - - const oneWayTab = page.locator('[data-testid="schedule-one-way-tab"]'); - await oneWayTab.click(); - await page.waitForTimeout(500); - - const oneWayVisible = await page.locator('[data-testid="schedule-date-input"]').isVisible(); - expect(oneWayVisible).toBe(true); - }); - - test('Should handle invalid URL parameters (Test 63)', async ({ page }) => { - await page.goto(`/ru-ru/schedule?from=XXX&to=YYY&dateFrom=invalid&dateTo=invalid`); - await page.waitForLoadState('networkidle'); - - const errorState = page.locator('[data-testid="schedule-error-state"]'); - await expect(errorState).toBeVisible(); - }); - - test('Should navigate with pre-filled search (Test 64)', async ({ page }) => { - await page.goto(`/ru-ru/schedule?from=MOW&to=AER`); - await page.waitForLoadState('networkidle'); - - await expect(page).toHaveURL(/schedule/); - await expect(page).toHaveURL(/from=MOW/); - await expect(page).toHaveURL(/to=AER/); - }); - - test('Should search with different aircraft types (Test 65)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('Moscow'); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('Sochi'); - - const aircraftFilter = page.locator('[data-testid="schedule-aircraft-filter"]'); - await aircraftFilter.click(); - - const aircraftOption = page.locator('[data-testid="filter-option-Airbus A320"]'); - await aircraftOption.click(); - - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - await searchButton.click(); - await page.waitForLoadState('networkidle'); - - const results = page.locator('[data-testid="schedule-search-result"]'); - await expect(results).toHaveCount(50); - }); - - test('Should search with time range (Test 66)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('Moscow'); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('Sochi'); - - const timeRangeInput = page.locator('[data-testid="schedule-time-range-input"]'); - await timeRangeInput.fill('06:00-18:00'); - - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - await searchButton.click(); - await page.waitForLoadState('networkidle'); - - const results = page.locator('[data-testid="schedule-search-result"]'); - await expect(results).toHaveCount(50); - }); - - test('Should search with cabin class (Test 67)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('Moscow'); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('Sochi'); - - const cabinFilter = page.locator('[data-testid="schedule-cabin-filter"]'); - await cabinFilter.click(); - - const economyOption = page.locator('[data-testid="filter-option-economy"]'); - await economyOption.click(); - - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - await searchButton.click(); - await page.waitForLoadState('networkidle'); - - const results = page.locator('[data-testid="schedule-search-result"]'); - await expect(results).toHaveCount(50); - }); - - test('Should search with airline preference (Test 68)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('Moscow'); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('Sochi'); - - const airlineFilter = page.locator('[data-testid="schedule-airline-filter"]'); - await airlineFilter.click(); - - const aeroflotOption = page.locator('[data-testid="filter-option-SU"]'); - await aeroflotOption.click(); - - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - await searchButton.click(); - await page.waitForLoadState('networkidle'); - - const results = page.locator('[data-testid="schedule-search-result"]'); - await expect(results).toHaveCount(50); - }); - - test('Should search with non-stop filter (Test 69)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('Moscow'); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('Sochi'); - - const nonStopFilter = page.locator('[data-testid="schedule-nonstop-filter"]'); - await nonStopFilter.click(); - - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - await searchButton.click(); - await page.waitForLoadState('networkidle'); - - const results = page.locator('[data-testid="schedule-search-result"]'); - await expect(results).toHaveCount(50); - }); - - test('Should search with flexible dates (Test 70)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const departureInput = page.locator('[data-testid="schedule-departure-city-input"]'); - await departureInput.fill('Moscow'); - - const arrivalInput = page.locator('[data-testid="schedule-arrival-city-input"]'); - await arrivalInput.fill('Sochi'); - - const flexibleDatesCheckbox = page.locator('[data-testid="schedule-flexible-dates"]'); - await flexibleDatesCheckbox.click(); - - const searchButton = page.locator('[data-testid="schedule-search-button"]'); - await searchButton.click(); - await page.waitForLoadState('networkidle'); - - const results = page.locator('[data-testid="schedule-search-result"]'); - await expect(results).toHaveCount(50); - }); - }); -}); diff --git a/tests/e2e-angular/integration/11 - flight details.spec.ts b/tests/e2e-angular/integration/11 - flight details.spec.ts deleted file mode 100644 index dae7770b..00000000 --- a/tests/e2e-angular/integration/11 - flight details.spec.ts +++ /dev/null @@ -1,1685 +0,0 @@ -import { test, expect } from '@playwright/test'; -import type { Page } from '@playwright/test'; -import { - buildOnlineBoardPath, - buildRouteParam, - buildSchedulePath, - generateFlight, - generateFlights, - getToday, - getTomorrow, - getYesterday, - getFutureDate, - getPastDate, - CITIES, - AIRPORTS, - FLIGHT_NUMBERS, - STATUS_TYPES, - AIRCRAFT_TYPES, -} from '../support/test-utilities'; - -const today = getToday(); -const tomorrow = getTomorrow(); -const yesterday = getYesterday(); -const futureDate = getFutureDate(7); -const pastDate = getPastDate(7); - -// ============================================================================ -// Flight Details - Online Board & Schedule (50+ tests) -// ============================================================================ - -test.describe('Flight Details - Online Board & Schedule', () => { - // ============================================================================ - // Category 1: Online Board Flight Details - Basic (8 tests) - // ============================================================================ - test.describe('Category 1: Online Board Flight Details - Basic', () => { - test('Should open flight details from Online Board arrival search results (Test 1)', 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 open flight details from Online Board departure search results (Test 2)', 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 open flight details from Online Board route search results (Test 3)', 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 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 open flight details from Online Board flight search results (Test 4)', 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 1124'); - await searchInput.press('Enter'); - 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\/SU1124-\d{8}/); - }); - - test('Should verify flight number display (Test 5)', 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 verify carrier logo display (Test 6)', 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 carrierLogo = page.locator('[data-testid="carrier-logo"]'); - await expect(carrierLogo).toBeVisible(); - }); - - test('Should verify route display (Test 7)', 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 routeInfo = page.locator('[data-testid="route-info"]'); - await expect(routeInfo).toBeVisible(); - - await expect(routeInfo).toContainText(flight.departure.cityName); - await expect(routeInfo).toContainText(flight.arrival.cityName); - }); - - test('Should verify basic flight information (Test 8)', 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.getByText(flight.flightNumber)).toBeVisible(); - await expect(page.getByText(flight.airlineName)).toBeVisible(); - await expect(page.getByText(flight.departure.cityName)).toBeVisible(); - await expect(page.getByText(flight.arrival.cityName)).toBeVisible(); - }); - }); - - // ============================================================================ - // Category 2: Online Board Flight Details - Status (6 tests) - // ============================================================================ - test.describe('Category 2: Online Board Flight Details - Status', () => { - test('Should verify flight status display - Scheduled (Test 9)', 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('scheduled'); - }); - - test('Should verify flight status display - Sent (Test 10)', 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 status = page.locator('[data-testid="flight-status"]'); - await expect(status).toBeVisible(); - }); - - test('Should verify flight status display - In Flight (Test 11)', async ({ page }) => { - const flight = generateFlight({ - direction: 'departure', - cityCode: 'MOW', - status: 'inFlight', - }); - 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(); - }); - - test('Should verify flight status display - Landed (Test 12)', async ({ page }) => { - const flight = generateFlight({ - direction: 'arrival', - cityCode: 'MOW', - status: 'landed', - }); - 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(); - }); - - test('Should verify flight status display - Arrived (Test 13)', 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 status = page.locator('[data-testid="flight-status"]'); - await expect(status).toBeVisible(); - }); - - test('Should verify flight status display - Delayed (Test 14)', async ({ page }) => { - const flight = generateFlight({ - direction: 'departure', - 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 status = page.locator('[data-testid="flight-status"]'); - await expect(status).toBeVisible(); - }); - - test('Should verify flight status display - Cancelled (Test 15)', async ({ page }) => { - const flight = generateFlight({ - direction: 'departure', - cityCode: 'MOW', - status: 'cancelled', - }); - 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(); - }); - - test('Should verify flight status icon (Test 16)', 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 statusIcon = page.locator('[data-testid="status-icon"]'); - await expect(statusIcon).toBeVisible(); - }); - - test('Should verify flight status details (Test 17)', 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 statusDetails = page.locator('[data-testid="status-details"]'); - await expect(statusDetails).toBeVisible(); - }); - - test('Should verify flight status badges (Test 18)', async ({ page }) => { - const flight = generateFlight({ - direction: 'departure', - cityCode: 'MOW', - status: 'boarding', - }); - const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`; - - await page.goto(`/ru-ru/${slug}`); - await page.waitForLoadState('networkidle'); - - const statusBadge = page.locator('[data-testid="status-badge"]'); - await expect(statusBadge).toBeVisible(); - }); - - test('Should verify flight status color coding (Test 19)', async ({ page }) => { - const flight = generateFlight({ - direction: 'departure', - 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 statusContainer = page.locator('[data-testid="status-container"]'); - await expect(statusContainer).toBeVisible(); - }); - - test('Should verify flight status timestamp (Test 20)', 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 statusTimestamp = page.locator('[data-testid="status-timestamp"]'); - await expect(statusTimestamp).toBeVisible(); - }); - }); - - // ============================================================================ - // Category 3: Online Board Flight Details - Aircraft (6 tests) - // ============================================================================ - test.describe('Category 3: Online Board Flight Details - Aircraft', () => { - test('Should verify aircraft information display (Test 21)', 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 aircraftSection = page.locator('[data-testid="aircraft-section"]'); - await expect(aircraftSection).toBeVisible(); - }); - - test('Should verify aircraft type display (Test 22)', 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.aircraft?.type || ''); - }); - - test('Should verify aircraft seats configuration (Test 23)', 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('Should verify aircraft previous flight information (Test 24)', async ({ page }) => { - const flight = generateFlight({ - direction: 'departure', - cityCode: 'MOW', - aircraft: { type: 'Airbus A320', previousFlight: 'SU 1123' }, - }); - const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`; - - await page.goto(`/ru-ru/${slug}`); - await page.waitForLoadState('networkidle'); - - const previousFlight = page.locator('[data-testid="previous-flight"]'); - await expect(previousFlight).toBeVisible(); - }); - - test('Should verify aircraft equipment details (Test 25)', 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 equipmentDetails = page.locator('[data-testid="equipment-details"]'); - await expect(equipmentDetails).toBeVisible(); - }); - - test('Should verify aircraft model display (Test 26)', 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 aircraftModel = page.locator('[data-testid="aircraft-model"]'); - await expect(aircraftModel).toBeVisible(); - }); - }); - - // ============================================================================ - // Category 4: Online Board Flight Details - Services (6 tests) - // ============================================================================ - test.describe('Category 4: Online Board Flight Details - Services', () => { - test('Should verify registration information display (Test 27)', 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 registrationInfo = page.locator('[data-testid="registration-info"]'); - await expect(registrationInfo).toBeVisible(); - }); - - test('Should verify boarding information display (Test 28)', 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 boardingInfo = page.locator('[data-testid="boarding-info"]'); - await expect(boardingInfo).toBeVisible(); - }); - - test('Should verify deboarding information display (Test 29)', 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 deboardingInfo = page.locator('[data-testid="deboarding-info"]'); - await expect(deboardingInfo).toBeVisible(); - }); - - test('Should verify meal information display (Test 30)', async ({ page }) => { - const flight = generateFlight({ - direction: 'departure', - cityCode: 'MOW', - catering: { economy: true, business: true }, - }); - const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`; - - await page.goto(`/ru-ru/${slug}`); - await page.waitForLoadState('networkidle'); - - const mealInfo = page.locator('[data-testid="meal-info"]'); - await expect(mealInfo).toBeVisible(); - }); - - test('Should verify on-board services display (Test 31)', 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 onboardServices = page.locator('[data-testid="onboard-services"]'); - await expect(onboardServices).toBeVisible(); - }); - - test('Should verify service icons (Test 32)', 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 serviceIcons = page.locator('[data-testid="service-icon"]'); - await expect(serviceIcons).toHaveCount(3); - }); - }); - - // ============================================================================ - // Category 5: Online Board Flight Details - Schedule (6 tests) - // ============================================================================ - test.describe('Category 5: Online Board Flight Details - Schedule', () => { - test('Should verify flight schedule display (Test 33)', 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 scheduleSection = page.locator('[data-testid="schedule-section"]'); - await expect(scheduleSection).toBeVisible(); - }); - - test('Should verify scheduled departure time (Test 34)', 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(); - - const depTime = flight.departure.time.scheduled.slice(11, 16); - await expect(scheduledDep).toContainText(depTime); - }); - - test('Should verify scheduled arrival time (Test 35)', 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(); - - const arrTime = flight.arrival.time.scheduled.slice(11, 16); - await expect(scheduledArr).toContainText(arrTime); - }); - - test('Should verify actual departure time (Test 36)', 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 verify actual arrival time (Test 37)', 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 verify flight duration (Test 38)', 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(); - }); - }); - - // ============================================================================ - // Category 6: Schedule Flight Details - Basic (6 tests) - // ============================================================================ - test.describe('Category 6: Schedule Flight Details - Basic', () => { - test('Should open flight details from Schedule search results (Test 39)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const searchInput = page.locator('[data-testid="schedule-search-input"]'); - await searchInput.fill('Moscow'); - await searchInput.press('Enter'); - await page.waitForLoadState('networkidle'); - - const flightCard = page.locator('[data-testid="schedule-flight-card"]').first(); - await flightCard.click(); - await page.waitForLoadState('networkidle'); - - await expect(page).toHaveURL(/\/ru-ru\/schedule\/details/); - }); - - test('Should verify flight number display in Schedule (Test 40)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const searchInput = page.locator('[data-testid="schedule-search-input"]'); - await searchInput.fill('SU 1124'); - await searchInput.press('Enter'); - await page.waitForLoadState('networkidle'); - - const flightCard = page.locator('[data-testid="schedule-flight-card"]').first(); - await flightCard.click(); - await page.waitForLoadState('networkidle'); - - const flightNumber = page.locator('[data-testid="flight-number"]'); - await expect(flightNumber).toBeVisible(); - }); - - test('Should verify carrier logo display in Schedule (Test 41)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const searchInput = page.locator('[data-testid="schedule-search-input"]'); - await searchInput.fill('SU 1124'); - await searchInput.press('Enter'); - await page.waitForLoadState('networkidle'); - - const flightCard = page.locator('[data-testid="schedule-flight-card"]').first(); - await flightCard.click(); - await page.waitForLoadState('networkidle'); - - const carrierLogo = page.locator('[data-testid="carrier-logo"]'); - await expect(carrierLogo).toBeVisible(); - }); - - test('Should verify route display in Schedule (Test 42)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const searchInput = page.locator('[data-testid="schedule-search-input"]'); - await searchInput.fill('Moscow'); - await searchInput.press('Enter'); - await page.waitForLoadState('networkidle'); - - const flightCard = page.locator('[data-testid="schedule-flight-card"]').first(); - await flightCard.click(); - await page.waitForLoadState('networkidle'); - - const routeInfo = page.locator('[data-testid="route-info"]'); - await expect(routeInfo).toBeVisible(); - }); - - test('Should verify basic flight information in Schedule (Test 43)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const searchInput = page.locator('[data-testid="schedule-search-input"]'); - await searchInput.fill('SU 1124'); - await searchInput.press('Enter'); - await page.waitForLoadState('networkidle'); - - const flightCard = page.locator('[data-testid="schedule-flight-card"]').first(); - await flightCard.click(); - await page.waitForLoadState('networkidle'); - - await expect(page.getByText('SU 1124')).toBeVisible(); - await expect(page.getByText('Aeroflot')).toBeVisible(); - }); - - test('Should verify round-trip information (Test 44)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const roundTripTab = page.locator('[data-testid="round-trip-tab"]'); - await roundTripTab.click(); - await page.waitForTimeout(500); - - const searchInput = page.locator('[data-testid="schedule-search-input"]'); - await searchInput.fill('Moscow'); - await searchInput.press('Enter'); - await page.waitForLoadState('networkidle'); - - const flightCard = page.locator('[data-testid="schedule-flight-card"]').first(); - await flightCard.click(); - await page.waitForLoadState('networkidle'); - - const roundTripInfo = page.locator('[data-testid="round-trip-info"]'); - await expect(roundTripInfo).toBeVisible(); - }); - }); - - // ============================================================================ - // Category 7: Schedule Flight Details - Multi-leg (6 tests) - // ============================================================================ - test.describe('Category 7: Schedule Flight Details - Multi-leg', () => { - test('Should open multi-leg flight details (Test 45)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const searchInput = page.locator('[data-testid="schedule-search-input"]'); - await searchInput.fill('Moscow'); - await searchInput.press('Enter'); - await page.waitForLoadState('networkidle'); - - const flightCard = page.locator('[data-testid="schedule-flight-card"]').first(); - await flightCard.click(); - await page.waitForLoadState('networkidle'); - - const multiLegTab = page.locator('[data-testid="multi-leg-tab"]'); - await multiLegTab.click(); - await page.waitForLoadState('networkidle'); - - await expect(page).toHaveURL(/\/ru-ru\/schedule\/details/); - }); - - test('Should verify leg switcher (Test 46)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const searchInput = page.locator('[data-testid="schedule-search-input"]'); - await searchInput.fill('Moscow'); - await searchInput.press('Enter'); - await page.waitForLoadState('networkidle'); - - const flightCard = page.locator('[data-testid="schedule-flight-card"]').first(); - await flightCard.click(); - await page.waitForLoadState('networkidle'); - - const legSwitcher = page.locator('[data-testid="leg-switcher"]'); - await expect(legSwitcher).toBeVisible(); - }); - - test('Should verify leg information display (Test 47)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const searchInput = page.locator('[data-testid="schedule-search-input"]'); - await searchInput.fill('Moscow'); - await searchInput.press('Enter'); - await page.waitForLoadState('networkidle'); - - const flightCard = page.locator('[data-testid="schedule-flight-card"]').first(); - await flightCard.click(); - await page.waitForLoadState('networkidle'); - - const legInfo = page.locator('[data-testid="leg-info"]'); - await expect(legInfo).toBeVisible(); - }); - - test('Should verify transfer information (Test 48)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const searchInput = page.locator('[data-testid="schedule-search-input"]'); - await searchInput.fill('Moscow'); - await searchInput.press('Enter'); - await page.waitForLoadState('networkidle'); - - const flightCard = page.locator('[data-testid="schedule-flight-card"]').first(); - await flightCard.click(); - await page.waitForLoadState('networkidle'); - - const transferInfo = page.locator('[data-testid="transfer-info"]'); - await expect(transferInfo).toBeVisible(); - }); - - test('Should verify multi-leg route display (Test 49)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const searchInput = page.locator('[data-testid="schedule-search-input"]'); - await searchInput.fill('Moscow'); - await searchInput.press('Enter'); - await page.waitForLoadState('networkidle'); - - const flightCard = page.locator('[data-testid="schedule-flight-card"]').first(); - await flightCard.click(); - await page.waitForLoadState('networkidle'); - - const multiLegRoute = page.locator('[data-testid="multi-leg-route"]'); - await expect(multiLegRoute).toBeVisible(); - }); - - test('Should verify timeline display (Test 50)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const searchInput = page.locator('[data-testid="schedule-search-input"]'); - await searchInput.fill('Moscow'); - await searchInput.press('Enter'); - await page.waitForLoadState('networkidle'); - - const flightCard = page.locator('[data-testid="schedule-flight-card"]').first(); - await flightCard.click(); - await page.waitForLoadState('networkidle'); - - const timeline = page.locator('[data-testid="timeline"]'); - await expect(timeline).toBeVisible(); - }); - }); - - // ============================================================================ - // Category 8: Flight Details Navigation (4 tests) - // ============================================================================ - test.describe('Category 8: Flight Details Navigation', () => { - test('Should navigate back to search results (Test 51)', async ({ page }) => { - const flight = generateFlight({ direction: 'departure', cityCode: 'MOW' }); - const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`; - - 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 backLink = page.locator('[data-testid="back-link"]'); - await backLink.click(); - await page.waitForLoadState('networkidle'); - - await expect(page).toHaveURL(/onlineboard\/departure\/MOW-\d{8}/); - }); - - test('Should navigate to previous flight in multi-leg (Test 52)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const searchInput = page.locator('[data-testid="schedule-search-input"]'); - await searchInput.fill('Moscow'); - await searchInput.press('Enter'); - await page.waitForLoadState('networkidle'); - - const flightCard = page.locator('[data-testid="schedule-flight-card"]').first(); - await flightCard.click(); - await page.waitForLoadState('networkidle'); - - const prevFlightBtn = page.locator('[data-testid="prev-flight-btn"]'); - await prevFlightBtn.click(); - await page.waitForLoadState('networkidle'); - - await expect(page).toHaveURL(/\/ru-ru\/schedule\/details/); - }); - - test('Should navigate to next flight in multi-leg (Test 53)', async ({ page }) => { - await page.goto(`/ru-ru${buildSchedulePath()}`); - await page.waitForLoadState('networkidle'); - - const searchInput = page.locator('[data-testid="schedule-search-input"]'); - await searchInput.fill('Moscow'); - await searchInput.press('Enter'); - await page.waitForLoadState('networkidle'); - - const flightCard = page.locator('[data-testid="schedule-flight-card"]').first(); - await flightCard.click(); - await page.waitForLoadState('networkidle'); - - const nextFlightBtn = page.locator('[data-testid="next-flight-btn"]'); - await nextFlightBtn.click(); - await page.waitForLoadState('networkidle'); - - await expect(page).toHaveURL(/\/ru-ru\/schedule\/details/); - }); - - test('Should navigate between Online Board and Schedule details (Test 54)', 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 onlineBoardLink = page.locator('[data-testid="online-board-link"]'); - await onlineBoardLink.click(); - await page.waitForLoadState('networkidle'); - - await expect(page).toHaveURL(/onlineboard\/departure\/MOW-\d{8}/); - }); - }); - - // ============================================================================ - // Category 9: Edge Cases (2 tests) - // ============================================================================ - test.describe('Category 9: Edge Cases', () => { - test('Should handle network error gracefully (Test 55)', 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('Should handle flight not found (404) (Test 56)', async ({ page }) => { - await page.route('**/api/flights/**', async (route) => { - await route.fulfill({ - status: 404, - json: { error: 'Not Found', message: 'Flight not found' }, - }); - }); - - 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 notFound = page.locator('[data-testid="not-found"]'); - await expect(notFound).toBeVisible(); - }); - - test('Should handle server error (500) (Test 57)', async ({ page }) => { - await page.route('**/api/flights/**', async (route) => { - await route.fulfill({ - status: 500, - json: { error: 'Internal Server Error', message: 'Server error' }, - }); - }); - - 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 serverError = page.locator('[data-testid="server-error"]'); - await expect(serverError).toBeVisible(); - }); - - test('Should handle timeout error (504) (Test 58)', async ({ page }) => { - await page.route('**/api/flights/**', async (route) => { - await route.fulfill({ - status: 504, - json: { error: 'Gateway Timeout', message: 'Request timeout' }, - }); - }); - - 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 timeoutError = page.locator('[data-testid="timeout-error"]'); - await expect(timeoutError).toBeVisible(); - }); - - test('Should handle invalid date format (Test 59)', async ({ page }) => { - await page.goto(`/ru-ru/SU1124-INVALID`); - await page.waitForLoadState('networkidle'); - - const errorState = page.locator('[data-testid="error-state"]'); - await expect(errorState).toBeVisible(); - }); - - test('Should handle empty flight number (Test 60)', async ({ page }) => { - await page.goto(`/ru-ru/-${today.replace(/-/g, '')}`); - await page.waitForLoadState('networkidle'); - - const errorState = page.locator('[data-testid="error-state"]'); - await expect(errorState).toBeVisible(); - }); - - test('Should handle special characters in flight number (Test 61)', async ({ page }) => { - await page.goto(`/ru-ru/SU!@#$-${today.replace(/-/g, '')}`); - await page.waitForLoadState('networkidle'); - - const errorState = page.locator('[data-testid="error-state"]'); - await expect(errorState).toBeVisible(); - }); - - test('Should handle very long flight number (Test 62)', async ({ page }) => { - const longFlightNumber = 'SU'.padEnd(100, '1'); - await page.goto(`/ru-ru/${longFlightNumber}-${today.replace(/-/g, '')}`); - await page.waitForLoadState('networkidle'); - - const errorState = page.locator('[data-testid="error-state"]'); - await expect(errorState).toBeVisible(); - }); - - test('Should handle Unicode characters in flight number (Test 63)', async ({ page }) => { - await page.goto(`/ru-ru/SU1124 flight 🛫-${today.replace(/-/g, '')}`); - await page.waitForLoadState('networkidle'); - - const errorState = page.locator('[data-testid="error-state"]'); - await expect(errorState).toBeVisible(); - }); - - test('Should handle flight with missing aircraft information (Test 64)', async ({ page }) => { - const flight = generateFlight({ - direction: 'departure', - cityCode: 'MOW', - aircraft: { type: undefined }, - }); - const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`; - - await page.goto(`/ru-ru/${slug}`); - await page.waitForLoadState('networkidle'); - - const aircraftSection = page.locator('[data-testid="aircraft-section"]'); - await expect(aircraftSection).toBeVisible(); - }); - - test('Should handle flight with missing schedule information (Test 65)', async ({ page }) => { - const flight = generateFlight({ - direction: 'departure', - cityCode: 'MOW', - schedule: undefined, - }); - const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`; - - await page.goto(`/ru-ru/${slug}`); - await page.waitForLoadState('networkidle'); - - const scheduleSection = page.locator('[data-testid="schedule-section"]'); - await expect(scheduleSection).toBeVisible(); - }); - - test('Should handle flight with missing boarding information (Test 66)', async ({ page }) => { - const flight = generateFlight({ - direction: 'departure', - cityCode: 'MOW', - boarding: undefined, - }); - const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`; - - await page.goto(`/ru-ru/${slug}`); - await page.waitForLoadState('networkidle'); - - const boardingInfo = page.locator('[data-testid="boarding-info"]'); - await expect(boardingInfo).toBeVisible(); - }); - - test('Should handle flight with missing arrival information (Test 67)', async ({ page }) => { - const flight = generateFlight({ - direction: 'departure', - cityCode: 'MOW', - arrival: { - airportCode: 'AER', - airportName: 'Adler', - cityCode: 'AER', - cityName: 'Sochi', - time: { scheduled: '2026-04-06T16:05:00+03:00' }, - }, - }); - const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`; - - await page.goto(`/ru-ru/${slug}`); - await page.waitForLoadState('networkidle'); - - const arrivalInfo = page.locator('[data-testid="arrival-info"]'); - await expect(arrivalInfo).toBeVisible(); - }); - - test('Should handle flight with missing departure information (Test 68)', async ({ page }) => { - const flight = generateFlight({ - direction: 'departure', - cityCode: 'MOW', - departure: { - airportCode: 'SVO', - airportName: 'Sheremetyevo', - cityCode: 'MOW', - cityName: 'Moscow', - time: { scheduled: '2026-04-06T12:13:00+03:00' }, - }, - }); - const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`; - - await page.goto(`/ru-ru/${slug}`); - await page.waitForLoadState('networkidle'); - - const departureInfo = page.locator('[data-testid="departure-info"]'); - await expect(departureInfo).toBeVisible(); - }); - - test('Should handle flight with missing catering information (Test 69)', async ({ page }) => { - const flight = generateFlight({ - direction: 'departure', - cityCode: 'MOW', - catering: undefined, - }); - const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`; - - await page.goto(`/ru-ru/${slug}`); - await page.waitForLoadState('networkidle'); - - const mealInfo = page.locator('[data-testid="meal-info"]'); - await expect(mealInfo).toBeVisible(); - }); - - test('Should handle flight with missing checkin information (Test 70)', async ({ page }) => { - const flight = generateFlight({ - direction: 'departure', - cityCode: 'MOW', - checkin: undefined, - }); - 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 handle flight with missing deplaning information (Test 71)', async ({ page }) => { - const flight = generateFlight({ - direction: 'arrival', - cityCode: 'MOW', - deplaning: undefined, - }); - const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`; - - await page.goto(`/ru-ru/${slug}`); - await page.waitForLoadState('networkidle'); - - const deboardingInfo = page.locator('[data-testid="deboarding-info"]'); - await expect(deboardingInfo).toBeVisible(); - }); - - test('Should handle flight with missing arrivalInfo (Test 72)', async ({ page }) => { - const flight = generateFlight({ - direction: 'arrival', - cityCode: 'MOW', - arrivalInfo: undefined, - }); - const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`; - - await page.goto(`/ru-ru/${slug}`); - await page.waitForLoadState('networkidle'); - - const arrivalInfo = page.locator('[data-testid="arrival-info"]'); - await expect(arrivalInfo).toBeVisible(); - }); - - test('Should handle flight with missing lastUpdated (Test 73)', async ({ page }) => { - const flight = generateFlight({ - direction: 'departure', - cityCode: 'MOW', - lastUpdated: undefined, - }); - const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`; - - await page.goto(`/ru-ru/${slug}`); - await page.waitForLoadState('networkidle'); - - const lastUpdated = page.locator('[data-testid="last-updated"]'); - await expect(lastUpdated).toBeVisible(); - }); - - test('Should handle flight with missing aircraft name (Test 74)', async ({ page }) => { - const flight = generateFlight({ - direction: 'departure', - cityCode: 'MOW', - aircraft: { type: 'Airbus A320', name: undefined }, - }); - 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(); - }); - - test('Should handle flight with missing aircraft previousFlight (Test 75)', async ({ - page, - }) => { - const flight = generateFlight({ - direction: 'departure', - cityCode: 'MOW', - aircraft: { type: 'Airbus A320', previousFlight: undefined }, - }); - const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`; - - await page.goto(`/ru-ru/${slug}`); - await page.waitForLoadState('networkidle'); - - const previousFlight = page.locator('[data-testid="previous-flight"]'); - await expect(previousFlight).toBeVisible(); - }); - - test('Should handle flight with missing aircraft seats (Test 76)', async ({ page }) => { - const flight = generateFlight({ - direction: 'departure', - cityCode: 'MOW', - aircraft: { type: 'Airbus A320', totalSeats: undefined }, - }); - 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('Should handle flight with missing aircraft equipment (Test 77)', async ({ page }) => { - const flight = generateFlight({ - direction: 'departure', - cityCode: 'MOW', - aircraft: { type: 'Airbus A320', equipment: undefined }, - }); - const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`; - - await page.goto(`/ru-ru/${slug}`); - await page.waitForLoadState('networkidle'); - - const equipmentDetails = page.locator('[data-testid="equipment-details"]'); - await expect(equipmentDetails).toBeVisible(); - }); - - test('Should handle flight with missing aircraft model (Test 78)', async ({ page }) => { - const flight = generateFlight({ - direction: 'departure', - cityCode: 'MOW', - aircraft: { type: 'Airbus A320', model: undefined }, - }); - const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`; - - await page.goto(`/ru-ru/${slug}`); - await page.waitForLoadState('networkidle'); - - const aircraftModel = page.locator('[data-testid="aircraft-model"]'); - await expect(aircraftModel).toBeVisible(); - }); - - test('Should handle flight with missing aircraft type (Test 79)', async ({ page }) => { - const flight = generateFlight({ - direction: 'departure', - cityCode: 'MOW', - aircraft: { type: undefined }, - }); - 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 handle flight with missing aircraft (Test 80)', async ({ page }) => { - const flight = generateFlight({ - direction: 'departure', - cityCode: 'MOW', - aircraft: undefined, - }); - const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`; - - await page.goto(`/ru-ru/${slug}`); - await page.waitForLoadState('networkidle'); - - const aircraftSection = page.locator('[data-testid="aircraft-section"]'); - await expect(aircraftSection).toBeVisible(); - }); - - test('Should handle flight with missing schedule (Test 81)', async ({ page }) => { - const flight = generateFlight({ - direction: 'departure', - cityCode: 'MOW', - schedule: undefined, - }); - const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`; - - await page.goto(`/ru-ru/${slug}`); - await page.waitForLoadState('networkidle'); - - const scheduleSection = page.locator('[data-testid="schedule-section"]'); - await expect(scheduleSection).toBeVisible(); - }); - - test('Should handle flight with missing boarding (Test 82)', async ({ page }) => { - const flight = generateFlight({ - direction: 'departure', - cityCode: 'MOW', - boarding: undefined, - }); - const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`; - - await page.goto(`/ru-ru/${slug}`); - await page.waitForLoadState('networkidle'); - - const boardingInfo = page.locator('[data-testid="boarding-info"]'); - await expect(boardingInfo).toBeVisible(); - }); - - test('Should handle flight with missing checkin (Test 83)', async ({ page }) => { - const flight = generateFlight({ - direction: 'departure', - cityCode: 'MOW', - checkin: undefined, - }); - 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 handle flight with missing deplaning (Test 84)', async ({ page }) => { - const flight = generateFlight({ - direction: 'arrival', - cityCode: 'MOW', - deplaning: undefined, - }); - const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`; - - await page.goto(`/ru-ru/${slug}`); - await page.waitForLoadState('networkidle'); - - const deboardingInfo = page.locator('[data-testid="deboarding-info"]'); - await expect(deboardingInfo).toBeVisible(); - }); - - test('Should handle flight with missing arrivalInfo (Test 85)', async ({ page }) => { - const flight = generateFlight({ - direction: 'arrival', - cityCode: 'MOW', - arrivalInfo: undefined, - }); - const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`; - - await page.goto(`/ru-ru/${slug}`); - await page.waitForLoadState('networkidle'); - - const arrivalInfo = page.locator('[data-testid="arrival-info"]'); - await expect(arrivalInfo).toBeVisible(); - }); - - test('Should handle flight with missing catering (Test 86)', async ({ page }) => { - const flight = generateFlight({ - direction: 'departure', - cityCode: 'MOW', - catering: undefined, - }); - const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`; - - await page.goto(`/ru-ru/${slug}`); - await page.waitForLoadState('networkidle'); - - const mealInfo = page.locator('[data-testid="meal-info"]'); - await expect(mealInfo).toBeVisible(); - }); - - test('Should handle flight with missing lastUpdated (Test 87)', async ({ page }) => { - const flight = generateFlight({ - direction: 'departure', - cityCode: 'MOW', - lastUpdated: undefined, - }); - const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`; - - await page.goto(`/ru-ru/${slug}`); - await page.waitForLoadState('networkidle'); - - const lastUpdated = page.locator('[data-testid="last-updated"]'); - await expect(lastUpdated).toBeVisible(); - }); - - test('Should handle flight with missing all optional fields (Test 88)', async ({ page }) => { - const flight = generateFlight({ - direction: 'departure', - cityCode: 'MOW', - aircraft: undefined, - schedule: undefined, - boarding: undefined, - checkin: undefined, - deplaning: undefined, - arrivalInfo: undefined, - catering: undefined, - lastUpdated: undefined, - }); - const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`; - - await page.goto(`/ru-ru/${slug}`); - await page.waitForLoadState('networkidle'); - - await expect(page.getByText(flight.flightNumber)).toBeVisible(); - await expect(page.getByText(flight.airlineName)).toBeVisible(); - }); - - test('Should handle flight with null values (Test 89)', async ({ page }) => { - const flight = generateFlight({ - direction: 'departure', - cityCode: 'MOW', - aircraft: null, - schedule: null, - boarding: null, - checkin: null, - deplaning: null, - arrivalInfo: null, - catering: null, - lastUpdated: null, - }); - const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`; - - await page.goto(`/ru-ru/${slug}`); - await page.waitForLoadState('networkidle'); - - await expect(page.getByText(flight.flightNumber)).toBeVisible(); - }); - - test('Should handle flight with empty strings (Test 90)', async ({ page }) => { - const flight = generateFlight({ - direction: 'departure', - cityCode: 'MOW', - aircraft: { type: '', name: '', previousFlight: '' }, - schedule: { scheduledDeparture: '', scheduledArrival: '', duration: '' }, - boarding: { gate: '', status: '', startTime: '', endTime: '' }, - checkin: { status: '', startTime: '', endTime: '' }, - deplaning: { status: '', startTime: '', endTime: '', transfer: '' }, - arrivalInfo: { baggageBelt: '', transfer: '' }, - catering: { economy: false, business: false }, - lastUpdated: '', - }); - const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`; - - await page.goto(`/ru-ru/${slug}`); - await page.waitForLoadState('networkidle'); - - await expect(page.getByText(flight.flightNumber)).toBeVisible(); - }); - - test('Should handle flight with zero values (Test 91)', async ({ page }) => { - const flight = generateFlight({ - direction: 'departure', - cityCode: 'MOW', - aircraft: { type: 'Airbus A320', totalSeats: 0, economySeats: 0, businessSeats: 0 }, - }); - 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('Should handle flight with negative values (Test 92)', async ({ page }) => { - const flight = generateFlight({ - direction: 'departure', - cityCode: 'MOW', - aircraft: { type: 'Airbus A320', totalSeats: -1, economySeats: -1, businessSeats: -1 }, - }); - 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('Should handle flight with very large values (Test 93)', async ({ page }) => { - const flight = generateFlight({ - direction: 'departure', - cityCode: 'MOW', - aircraft: { - type: 'Airbus A320', - totalSeats: 999999, - economySeats: 999999, - businessSeats: 999999, - }, - }); - 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('Should handle flight with special characters in city names (Test 94)', async ({ - page, - }) => { - const flight = generateFlight({ - direction: 'departure', - cityCode: 'MOW', - departure: { - airportCode: 'SVO', - airportName: 'Sheremetyevo', - cityCode: 'MOW', - cityName: 'Москва!@#$%', - time: { scheduled: '2026-04-06T12:13:00+03:00' }, - }, - arrival: { - airportCode: 'AER', - airportName: 'Adler', - cityCode: 'AER', - cityName: 'Сочи!@#$%', - time: { scheduled: '2026-04-06T16:05:00+03:00' }, - }, - }); - const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`; - - await page.goto(`/ru-ru/${slug}`); - await page.waitForLoadState('networkidle'); - - const routeInfo = page.locator('[data-testid="route-info"]'); - await expect(routeInfo).toBeVisible(); - }); - - test('Should handle flight with very long city names (Test 95)', async ({ page }) => { - const longCityName = 'Москва'.repeat(100); - const flight = generateFlight({ - direction: 'departure', - cityCode: 'MOW', - departure: { - airportCode: 'SVO', - airportName: 'Sheremetyevo', - cityCode: 'MOW', - cityName: longCityName, - time: { scheduled: '2026-04-06T12:13:00+03:00' }, - }, - arrival: { - airportCode: 'AER', - airportName: 'Adler', - cityCode: 'AER', - cityName: longCityName, - time: { scheduled: '2026-04-06T16:05:00+03:00' }, - }, - }); - const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`; - - await page.goto(`/ru-ru/${slug}`); - await page.waitForLoadState('networkidle'); - - const routeInfo = page.locator('[data-testid="route-info"]'); - await expect(routeInfo).toBeVisible(); - }); - - test('Should handle flight with Unicode characters in all fields (Test 96)', async ({ - page, - }) => { - const flight = generateFlight({ - direction: 'departure', - cityCode: 'MOW', - departure: { - airportCode: 'SVO', - airportName: 'Sheremetyevo 🛫', - cityCode: 'MOW', - cityName: 'Москва 🇷🇺', - time: { scheduled: '2026-04-06T12:13:00+03:00' }, - }, - arrival: { - airportCode: 'AER', - airportName: 'Adler 🛬', - cityCode: 'AER', - cityName: 'Сочи 🇷🇺', - time: { scheduled: '2026-04-06T16:05:00+03:00' }, - }, - }); - const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`; - - await page.goto(`/ru-ru/${slug}`); - await page.waitForLoadState('networkidle'); - - const routeInfo = page.locator('[data-testid="route-info"]'); - await expect(routeInfo).toBeVisible(); - }); - - test('Should handle flight with mixed case in all fields (Test 97)', async ({ page }) => { - const flight = generateFlight({ - direction: 'departure', - cityCode: 'MOW', - departure: { - airportCode: 'SVO', - airportName: 'SHEREMETYEVO', - cityCode: 'MOW', - cityName: 'MOSCOW', - time: { scheduled: '2026-04-06T12:13:00+03:00' }, - }, - arrival: { - airportCode: 'AER', - airportName: 'ADLER', - cityCode: 'AER', - cityName: 'SOCHI', - time: { scheduled: '2026-04-06T16:05:00+03:00' }, - }, - }); - const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`; - - await page.goto(`/ru-ru/${slug}`); - await page.waitForLoadState('networkidle'); - - const routeInfo = page.locator('[data-testid="route-info"]'); - await expect(routeInfo).toBeVisible(); - }); - - test('Should handle flight with special characters in airport codes (Test 98)', async ({ - page, - }) => { - const flight = generateFlight({ - direction: 'departure', - cityCode: 'MOW', - departure: { - airportCode: 'SVO!@#$', - airportName: 'Sheremetyevo', - cityCode: 'MOW', - cityName: 'Moscow', - time: { scheduled: '2026-04-06T12:13:00+03:00' }, - }, - arrival: { - airportCode: 'AER!@#$', - airportName: 'Adler', - cityCode: 'AER', - cityName: 'Sochi', - time: { scheduled: '2026-04-06T16:05:00+03:00' }, - }, - }); - const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`; - - await page.goto(`/ru-ru/${slug}`); - await page.waitForLoadState('networkidle'); - - const routeInfo = page.locator('[data-testid="route-info"]'); - await expect(routeInfo).toBeVisible(); - }); - - test('Should handle flight with missing terminal information (Test 99)', async ({ page }) => { - const flight = generateFlight({ - direction: 'departure', - cityCode: 'MOW', - departure: { - airportCode: 'SVO', - airportName: 'Sheremetyevo', - cityCode: 'MOW', - cityName: 'Moscow', - terminal: undefined, - time: { scheduled: '2026-04-06T12:13:00+03:00' }, - }, - }); - 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(); - }); - - test('Should handle flight with missing arrival terminal (Test 100)', async ({ page }) => { - const flight = generateFlight({ - direction: 'departure', - cityCode: 'MOW', - arrival: { - airportCode: 'AER', - airportName: 'Adler', - cityCode: 'AER', - cityName: 'Sochi', - terminal: undefined, - time: { scheduled: '2026-04-06T16:05:00+03:00' }, - }, - }); - const slug = `${flight.flightNumber.replace(/\s+/g, '')}-${today.replace(/-/g, '')}`; - - await page.goto(`/ru-ru/${slug}`); - await page.waitForLoadState('networkidle'); - - const arrivalTerminal = page.locator('[data-testid="arrival-terminal"]'); - await expect(arrivalTerminal).toBeVisible(); - }); - }); -}); diff --git a/tests/e2e-angular/integration/12 - flights map.spec.ts b/tests/e2e-angular/integration/12 - flights map.spec.ts deleted file mode 100644 index e93d61d5..00000000 --- a/tests/e2e-angular/integration/12 - flights map.spec.ts +++ /dev/null @@ -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(); - }); - }); -}); diff --git a/tests/e2e-angular/integration/online-board-arrival.spec.ts b/tests/e2e-angular/integration/online-board-arrival.spec.ts deleted file mode 100644 index b4735dc8..00000000 --- a/tests/e2e-angular/integration/online-board-arrival.spec.ts +++ /dev/null @@ -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); - }); - }); -}); diff --git a/tests/e2e-angular/integration/online-board-departure.spec.ts b/tests/e2e-angular/integration/online-board-departure.spec.ts deleted file mode 100644 index 5bed7297..00000000 --- a/tests/e2e-angular/integration/online-board-departure.spec.ts +++ /dev/null @@ -1,1084 +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 - Departure Tests (50+ tests) -// ============================================================================ - -test.describe('Online Board - Departure', () => { - test.describe('Category 1: Basic Departure Search', () => { - test('Should search by city name (manual input) (Test 1)', 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 by city from autocomplete list (Test 2)', 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 search with today date (Test 3)', async ({ page }) => { - await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'AER', today)}`); - await page.waitForLoadState('networkidle'); - - await expect(page).toHaveURL(/departure\/AER-\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 expect(page).toHaveURL(/departure\/MOW-\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 expect(page).toHaveURL(/departure\/MOW-\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 expect(page).toHaveURL(/departure\/MOW-\d{8}/); - }); - - test('Should search with invalid city and show error (Test 7)', 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 search with empty city and show validation (Test 8)', async ({ page }) => { - await page.goto(`/ru-ru/onlineboard/departure/-${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('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'); - - 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', () => { - 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', () => { - test('Should search for non-existent city (Test 26)', 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 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 city name (Test 28)', async ({ page }) => { - const longCityName = 'Москва'.repeat(10); - - 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(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('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('Category 6: Additional Departure Tests', () => { - test('Should navigate to departure 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 boarding gate information (Test 42)', 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('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 (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); - }); - }); - - test.describe('Category 7: Advanced Departure Tests', () => { - test('Should display flight card with all information (Test 51)', 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(); - await expect(flightCard.locator('[data-testid="flight-number"]')).toBeVisible(); - await expect(flightCard.locator('[data-testid="airline-name"]')).toBeVisible(); - await expect(flightCard.locator('[data-testid="departure-city"]')).toBeVisible(); - await expect(flightCard.locator('[data-testid="arrival-city"]')).toBeVisible(); - await expect(flightCard.locator('[data-testid="scheduled-time"]')).toBeVisible(); - }); - - test('Should display flight status badge (Test 52)', 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 statusBadge = flightCard.locator('[data-testid="status-badge"]'); - await expect(statusBadge).toBeVisible(); - }); - - test('Should display aircraft type (Test 53)', async ({ page }) => { - const flight = generateFlight({ - direction: 'departure', - cityCode: 'MOW', - aircraftType: 'Airbus A320', - }); - - 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 aircraftType = flightCard.locator('[data-testid="aircraft-type"]'); - await expect(aircraftType).toBeVisible(); - }); - - test('Should display checkin status (Test 54)', async ({ page }) => { - const flight = generateFlight({ - direction: 'departure', - cityCode: 'MOW', - status: 'checkin', - }); - - 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 checkinStatus = flightCard.locator('[data-testid="checkin-status"]'); - await expect(checkinStatus).toBeVisible(); - }); - - test('Should display flight duration (Test 55)', 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 duration = flightCard.locator('[data-testid="flight-duration"]'); - await expect(duration).toBeVisible(); - }); - - test('Should display flight route on map (Test 56)', 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 routeMap = flightCard.locator('[data-testid="route-map"]'); - await expect(routeMap).toBeVisible(); - }); - - test('Should display flight class information (Test 57)', async ({ page }) => { - const flight = generateFlight({ - direction: 'departure', - cityCode: 'MOW', - status: 'scheduled', - }); - - 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 classInfo = flightCard.locator('[data-testid="class-info"]'); - await expect(classInfo).toBeVisible(); - }); - - test('Should display flight catering information (Test 58)', async ({ page }) => { - const flight = generateFlight({ - direction: 'departure', - cityCode: 'MOW', - status: 'scheduled', - }); - - 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 cateringInfo = flightCard.locator('[data-testid="catering-info"]'); - await expect(cateringInfo).toBeVisible(); - }); - - test('Should display flight seat information (Test 59)', async ({ page }) => { - const flight = generateFlight({ - direction: 'departure', - cityCode: 'MOW', - status: 'scheduled', - }); - - 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 seatInfo = flightCard.locator('[data-testid="seat-info"]'); - await expect(seatInfo).toBeVisible(); - }); - - test('Should display flight operating days (Test 60)', 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 operatingDays = flightCard.locator('[data-testid="operating-days"]'); - await expect(operatingDays).toBeVisible(); - }); - - test('Should display flight week range (Test 61)', 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 weekRange = flightCard.locator('[data-testid="week-range"]'); - await expect(weekRange).toBeVisible(); - }); - - test('Should display flight last updated (Test 62)', async ({ page }) => { - const flight = generateFlight({ - direction: 'departure', - cityCode: 'MOW', - status: 'scheduled', - }); - - 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 lastUpdated = flightCard.locator('[data-testid="last-updated"]'); - await expect(lastUpdated).toBeVisible(); - }); - - test('Should display flight baggage information (Test 63)', async ({ page }) => { - const flight = generateFlight({ - direction: 'departure', - cityCode: 'MOW', - status: 'scheduled', - }); - - 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 baggageInfo = flightCard.locator('[data-testid="baggage-info"]'); - await expect(baggageInfo).toBeVisible(); - }); - - test('Should display flight transfer information (Test 64)', async ({ page }) => { - const flight = generateFlight({ - direction: 'departure', - cityCode: 'MOW', - status: 'scheduled', - }); - - 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 transferInfo = flightCard.locator('[data-testid="transfer-info"]'); - await expect(transferInfo).toBeVisible(); - }); - - test('Should display flight cancellation information (Test 65)', async ({ page }) => { - const flight = generateFlight({ - direction: 'departure', - cityCode: 'MOW', - status: 'cancelled', - }); - - 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 cancellationInfo = flightCard.locator('[data-testid="cancellation-info"]'); - await expect(cancellationInfo).toBeVisible(); - }); - - test('Should display flight gate changed information (Test 66)', async ({ page }) => { - const flight = generateFlight({ - direction: 'departure', - cityCode: 'MOW', - status: 'gateChanged', - }); - - 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 gateChangedInfo = flightCard.locator('[data-testid="gate-changed-info"]'); - await expect(gateChangedInfo).toBeVisible(); - }); - - test('Should display flight landed information (Test 67)', async ({ page }) => { - const flight = generateFlight({ - direction: 'departure', - cityCode: 'MOW', - status: 'landed', - }); - - 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 landedInfo = flightCard.locator('[data-testid="landed-info"]'); - await expect(landedInfo).toBeVisible(); - }); - - test('Should display flight in flight information (Test 68)', async ({ page }) => { - const flight = generateFlight({ - direction: 'departure', - cityCode: 'MOW', - status: 'inFlight', - }); - - 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 inFlightInfo = flightCard.locator('[data-testid="in-flight-info"]'); - await expect(inFlightInfo).toBeVisible(); - }); - - test('Should display flight departed information (Test 69)', 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 departedInfo = flightCard.locator('[data-testid="departed-info"]'); - await expect(departedInfo).toBeVisible(); - }); - - test('Should display flight arrived information (Test 70)', 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 arrivedInfo = flightCard.locator('[data-testid="arrived-info"]'); - await expect(arrivedInfo).toBeVisible(); - }); - - test('Should search by different flight numbers (Test 71)', async ({ page }) => { - await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`); - await page.waitForLoadState('networkidle'); - - const flightNumbers = ['SU 1124', 'SU 1076', 'SU 6170']; - - for (const flightNumber of flightNumbers) { - await searchFlightByNumber(page, flightNumber); - await page.waitForLoadState('networkidle'); - - const searchResults = page.locator('[data-testid="flight-card"]'); - await expect(searchResults).toHaveCount(1); - } - }); - - test('Should search by different routes (Test 72)', async ({ page }) => { - await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`); - await page.waitForLoadState('networkidle'); - - const routes = [ - ['Moscow', 'Sochi'], - ['Moscow', 'Saint Petersburg'], - ['Moscow', 'Novosibirsk'], - ]; - - for (const [departureCity, arrivalCity] of routes) { - await searchFlightByRoute(page, departureCity, arrivalCity); - await page.waitForLoadState('networkidle'); - - const flightCards = page.locator('[data-testid="flight-card"]'); - await expect(flightCards).toHaveCount(20); - } - }); - - test('Should search by different dates (Test 73)', async ({ page }) => { - const dates = [today, tomorrow, futureDate]; - - for (const date of dates) { - await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', date)}`); - await page.waitForLoadState('networkidle'); - - const flightCards = page.locator('[data-testid="flight-card"]'); - await expect(flightCards).toHaveCount(20); - } - }); - - test('Should search by different cities (Test 74)', 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'); - - const flightCards = page.locator('[data-testid="flight-card"]'); - await expect(flightCards).toHaveCount(20); - } - }); - - test('Should search by different airlines (Test 75)', async ({ page }) => { - await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`); - await page.waitForLoadState('networkidle'); - - const airlines = ['SU', 'FV']; - - for (const airlineCode of airlines) { - const airlineFilter = page.locator('[data-testid="airline-filter"]'); - await airlineFilter.click(); - - const airlineOption = page.locator(`[data-testid="filter-option-${airlineCode}"]`); - await airlineOption.click(); - await page.waitForLoadState('networkidle'); - - const filteredFlights = page.locator('[data-testid="flight-card"]'); - await expect(filteredFlights).toHaveCount(20); - } - }); - - test('Should search by different statuses (Test 76)', async ({ page }) => { - await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`); - await page.waitForLoadState('networkidle'); - - const statuses = ['scheduled', 'boarding', 'departed']; - - for (const status of statuses) { - const statusFilter = page.locator('[data-testid="status-filter"]'); - await statusFilter.click(); - - const statusOption = page.locator(`[data-testid="filter-option-${status}"]`); - await statusOption.click(); - await page.waitForLoadState('networkidle'); - - const filteredFlights = page.locator('[data-testid="flight-card"]'); - await expect(filteredFlights).toHaveCount(20); - } - }); - - test('Should search by different aircraft types (Test 77)', async ({ page }) => { - await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`); - await page.waitForLoadState('networkidle'); - - const aircraftTypes = ['Airbus A320', 'Airbus A321', 'Boeing 737-800']; - - for (const aircraftType of aircraftTypes) { - const aircraftFilter = page.locator('[data-testid="aircraft-type-filter"]'); - await aircraftFilter.click(); - - const aircraftOption = page.locator(`[data-testid="filter-option-${aircraftType}"]`); - await aircraftOption.click(); - await page.waitForLoadState('networkidle'); - - const filteredFlights = page.locator('[data-testid="flight-card"]'); - await expect(filteredFlights).toHaveCount(20); - } - }); - - test('Should search by different terminals (Test 78)', async ({ page }) => { - await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`); - await page.waitForLoadState('networkidle'); - - const terminals = ['A', 'B', 'C', 'D']; - - for (const terminal of terminals) { - const terminalFilter = page.locator('[data-testid="terminal-filter"]'); - await terminalFilter.click(); - - const terminalOption = page.locator(`[data-testid="filter-option-${terminal}"]`); - await terminalOption.click(); - await page.waitForLoadState('networkidle'); - - const filteredFlights = page.locator('[data-testid="flight-card"]'); - await expect(filteredFlights).toHaveCount(20); - } - }); - - test('Should search by different baggage belts (Test 79)', async ({ page }) => { - await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`); - await page.waitForLoadState('networkidle'); - - const baggageBelts = ['1', '2', '3']; - - for (const baggageBelt of baggageBelts) { - const baggageFilter = page.locator('[data-testid="baggage-belt-filter"]'); - await baggageFilter.click(); - - const baggageOption = page.locator(`[data-testid="filter-option-${baggageBelt}"]`); - await baggageOption.click(); - await page.waitForLoadState('networkidle'); - - const filteredFlights = page.locator('[data-testid="flight-card"]'); - await expect(filteredFlights).toHaveCount(20); - } - }); - - test('Should search by different gates (Test 80)', async ({ page }) => { - await page.goto(`/ru-ru${buildOnlineBoardPath('departure', 'MOW', today)}`); - await page.waitForLoadState('networkidle'); - - const gates = ['1', '2', '3', '4', '5']; - - for (const gate of gates) { - const gateFilter = page.locator('[data-testid="gate-filter"]'); - await gateFilter.click(); - - const gateOption = page.locator(`[data-testid="filter-option-${gate}"]`); - await gateOption.click(); - await page.waitForLoadState('networkidle'); - - const filteredFlights = page.locator('[data-testid="flight-card"]'); - await expect(filteredFlights).toHaveCount(20); - } - }); - }); -}); diff --git a/tests/e2e-angular/integration/online-board-flight-search.spec.ts b/tests/e2e-angular/integration/online-board-flight-search.spec.ts deleted file mode 100644 index 80e09df9..00000000 --- a/tests/e2e-angular/integration/online-board-flight-search.spec.ts +++ /dev/null @@ -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); - }); - }); -}); diff --git a/tests/e2e-angular/integration/templates/flight-details.template.ts b/tests/e2e-angular/integration/templates/flight-details.template.ts deleted file mode 100644 index f0ff722f..00000000 --- a/tests/e2e-angular/integration/templates/flight-details.template.ts +++ /dev/null @@ -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(); - }); - }); -}); diff --git a/tests/e2e-angular/integration/templates/flights-map.template.ts b/tests/e2e-angular/integration/templates/flights-map.template.ts deleted file mode 100644 index 6b941e4d..00000000 --- a/tests/e2e-angular/integration/templates/flights-map.template.ts +++ /dev/null @@ -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(); - }); - }); -}); diff --git a/tests/e2e-angular/integration/templates/online-board-arrival.template.ts b/tests/e2e-angular/integration/templates/online-board-arrival.template.ts deleted file mode 100644 index 9fe7a381..00000000 --- a/tests/e2e-angular/integration/templates/online-board-arrival.template.ts +++ /dev/null @@ -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(); - }); - }); -}); diff --git a/tests/e2e-angular/integration/templates/online-board-departure.template.ts b/tests/e2e-angular/integration/templates/online-board-departure.template.ts deleted file mode 100644 index 81a81747..00000000 --- a/tests/e2e-angular/integration/templates/online-board-departure.template.ts +++ /dev/null @@ -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(); - }); - }); -}); diff --git a/tests/e2e-angular/integration/templates/online-board-flight.template.ts b/tests/e2e-angular/integration/templates/online-board-flight.template.ts deleted file mode 100644 index ad9c7ba1..00000000 --- a/tests/e2e-angular/integration/templates/online-board-flight.template.ts +++ /dev/null @@ -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(); - }); - }); -}); diff --git a/tests/e2e-angular/integration/templates/online-board-route.template.ts b/tests/e2e-angular/integration/templates/online-board-route.template.ts deleted file mode 100644 index 8d9b7cff..00000000 --- a/tests/e2e-angular/integration/templates/online-board-route.template.ts +++ /dev/null @@ -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(); - }); - }); -}); diff --git a/tests/e2e-angular/integration/templates/popular-requests.template.ts b/tests/e2e-angular/integration/templates/popular-requests.template.ts deleted file mode 100644 index dfbdbc1b..00000000 --- a/tests/e2e-angular/integration/templates/popular-requests.template.ts +++ /dev/null @@ -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(); - }); - }); -}); diff --git a/tests/e2e-angular/integration/templates/schedule-search.template.ts b/tests/e2e-angular/integration/templates/schedule-search.template.ts deleted file mode 100644 index e2864843..00000000 --- a/tests/e2e-angular/integration/templates/schedule-search.template.ts +++ /dev/null @@ -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(); - }); - }); -}); diff --git a/tests/e2e-angular/navigation.spec.ts b/tests/e2e-angular/navigation.spec.ts deleted file mode 100644 index 9ea2218b..00000000 --- a/tests/e2e-angular/navigation.spec.ts +++ /dev/null @@ -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'); - }); -}); diff --git a/tests/e2e-angular/popular-requests.spec.ts b/tests/e2e-angular/popular-requests.spec.ts deleted file mode 100644 index f24125b3..00000000 --- a/tests/e2e-angular/popular-requests.spec.ts +++ /dev/null @@ -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); - }); -}); diff --git a/tests/e2e-angular/responsive.spec.ts b/tests/e2e-angular/responsive.spec.ts deleted file mode 100644 index b8678c74..00000000 --- a/tests/e2e-angular/responsive.spec.ts +++ /dev/null @@ -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(); - } - } - }); -}); diff --git a/tests/e2e-angular/ru-ru/aria-labels.spec.ts b/tests/e2e-angular/ru-ru/aria-labels.spec.ts deleted file mode 100644 index 737419a2..00000000 --- a/tests/e2e-angular/ru-ru/aria-labels.spec.ts +++ /dev/null @@ -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); - }); -}); diff --git a/tests/e2e-angular/ru-ru/caching-refresh.spec.ts b/tests/e2e-angular/ru-ru/caching-refresh.spec.ts deleted file mode 100644 index 48d38f47..00000000 --- a/tests/e2e-angular/ru-ru/caching-refresh.spec.ts +++ /dev/null @@ -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(); - }); -}); diff --git a/tests/e2e-angular/ru-ru/cross-app-validation.spec.ts b/tests/e2e-angular/ru-ru/cross-app-validation.spec.ts deleted file mode 100644 index 56c75ef8..00000000 --- a/tests/e2e-angular/ru-ru/cross-app-validation.spec.ts +++ /dev/null @@ -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'); - }); -}); diff --git a/tests/e2e-angular/ru-ru/form-validation.spec.ts b/tests/e2e-angular/ru-ru/form-validation.spec.ts deleted file mode 100644 index a1afb6be..00000000 --- a/tests/e2e-angular/ru-ru/form-validation.spec.ts +++ /dev/null @@ -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); - }); - }); -}); diff --git a/tests/e2e-angular/ru-ru/history-navigation.spec.ts b/tests/e2e-angular/ru-ru/history-navigation.spec.ts deleted file mode 100644 index 1817d0b6..00000000 --- a/tests/e2e-angular/ru-ru/history-navigation.spec.ts +++ /dev/null @@ -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); - }); -}); diff --git a/tests/e2e-angular/ru-ru/keyboard-navigation.spec.ts b/tests/e2e-angular/ru-ru/keyboard-navigation.spec.ts deleted file mode 100644 index 7d421628..00000000 --- a/tests/e2e-angular/ru-ru/keyboard-navigation.spec.ts +++ /dev/null @@ -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); - }); -}); diff --git a/tests/e2e-angular/ru-ru/large-datasets.spec.ts b/tests/e2e-angular/ru-ru/large-datasets.spec.ts deleted file mode 100644 index 01886f8d..00000000 --- a/tests/e2e-angular/ru-ru/large-datasets.spec.ts +++ /dev/null @@ -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((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(); - }); -}); diff --git a/tests/e2e-angular/ru-ru/persistent-state.spec.ts b/tests/e2e-angular/ru-ru/persistent-state.spec.ts deleted file mode 100644 index 9260b2e5..00000000 --- a/tests/e2e-angular/ru-ru/persistent-state.spec.ts +++ /dev/null @@ -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); - }); -}); diff --git a/tests/e2e-angular/schedule-details.spec.ts b/tests/e2e-angular/schedule-details.spec.ts deleted file mode 100644 index 819b7189..00000000 --- a/tests/e2e-angular/schedule-details.spec.ts +++ /dev/null @@ -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); - }); - }); -}); diff --git a/tests/e2e-angular/schedule-filters.spec.ts b/tests/e2e-angular/schedule-filters.spec.ts deleted file mode 100644 index 9c046670..00000000 --- a/tests/e2e-angular/schedule-filters.spec.ts +++ /dev/null @@ -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(); - }); -}); diff --git a/tests/e2e-angular/schedule-results.spec.ts b/tests/e2e-angular/schedule-results.spec.ts deleted file mode 100644 index 127ac889..00000000 --- a/tests/e2e-angular/schedule-results.spec.ts +++ /dev/null @@ -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(); - } - }); -}); diff --git a/tests/e2e-angular/schedule-search.spec.ts b/tests/e2e-angular/schedule-search.spec.ts deleted file mode 100644 index b3c814c7..00000000 --- a/tests/e2e-angular/schedule-search.spec.ts +++ /dev/null @@ -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'); - }); - }); -}); diff --git a/tests/e2e-angular/search-history.spec.ts b/tests/e2e-angular/search-history.spec.ts deleted file mode 100644 index c4d72c18..00000000 --- a/tests/e2e-angular/search-history.spec.ts +++ /dev/null @@ -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(); - }); -}); diff --git a/tests/e2e-angular/search-panel.spec.ts b/tests/e2e-angular/search-panel.spec.ts deleted file mode 100644 index 5870d11d..00000000 --- a/tests/e2e-angular/search-panel.spec.ts +++ /dev/null @@ -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 }); - }); -}); diff --git a/tests/e2e-angular/seo.spec.ts b/tests/e2e-angular/seo.spec.ts deleted file mode 100644 index a9146eaa..00000000 --- a/tests/e2e-angular/seo.spec.ts +++ /dev/null @@ -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); - }); -}); diff --git a/tests/e2e-angular/visual/flight-board.spec.ts b/tests/e2e-angular/visual/flight-board.spec.ts deleted file mode 100644 index 997ce8aa..00000000 --- a/tests/e2e-angular/visual/flight-board.spec.ts +++ /dev/null @@ -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, - }); - }); -}); diff --git a/tests/e2e-angular/visual/flight-expanded.spec.ts b/tests/e2e-angular/visual/flight-expanded.spec.ts deleted file mode 100644 index cfde2758..00000000 --- a/tests/e2e-angular/visual/flight-expanded.spec.ts +++ /dev/null @@ -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 }); - }); -}); diff --git a/tests/e2e-angular/visual/landing.spec.ts b/tests/e2e-angular/visual/landing.spec.ts deleted file mode 100644 index 086e1044..00000000 --- a/tests/e2e-angular/visual/landing.spec.ts +++ /dev/null @@ -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, - }); - }); -});