20c19d15f4
Modern.js SSR intercepts all routes before any Express middleware, so the API proxy runs as a separate Express server on port 8080. Modern.js runs on 8081. The proxy uses curl subprocesses which go through the system HTTPS proxy (GOST) with a proper TLS fingerprint that the Aeroflot WAF accepts. Usage: node scripts/dev-server.mjs (replaces pnpm dev for full-stack) Also: remove stray e2e-angular test directory, fix env default to same-origin /api.
1085 lines
42 KiB
TypeScript
1085 lines
42 KiB
TypeScript
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);
|
|
}
|
|
});
|
|
});
|
|
});
|