375bcfb0fa
Copies Playwright e2e tests (58 specs, 300+ tests) designed for cross-app testing. Adapts API mocks to match real Aeroflot dictionary format (title objects with multilingual keys), adds board/schedule/days endpoint mocks, and provides Angular-specific Playwright config on port 4203.
428 lines
16 KiB
TypeScript
428 lines
16 KiB
TypeScript
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();
|
|
});
|
|
});
|
|
});
|