Files
flights_web/tests/e2e-angular/cross-app/17-board-schedule-view.spec.ts
T
gnezim 375bcfb0fa Add e2e test suite from flights-front with Angular API mocks
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.
2026-04-15 23:07:44 +03:00

472 lines
22 KiB
TypeScript

import { test, expect } from '../support/cross-app-fixtures';
import { mockAllAPIs } from '../support/cross-app-fixtures';
import { S, tid } from '../support/selectors';
/**
* User Stories 161-180: Board & Schedule View Scenarios
*
* 161: User views departure board
* 162: User views arrival board
* 163: User views route board
* 164: User views flight board with filters
* 165: User views flight board with date tabs
* 166: User views weekly schedule
* 167: User switches week tabs
* 168: User views daily schedule
* 169: User views schedule with filters
* 170: User views schedule with sort
* 171-175: Map view scenarios (covered in existing tests)
* 176-180: Flight details scenarios (covered in existing tests)
*/
test.describe('User Stories 161-180: Board & Schedule View Scenarios', () => {
test.beforeEach(async ({ page, localePath }) => {
await mockAllAPIs(page);
await page.goto(localePath('onlineboard'));
await page.waitForLoadState('networkidle');
});
// ─────────────────────────────────────────────────────────────────────────
// Story 161: User views departure board
// ─────────────────────────────────────────────────────────────────────────
test('161.1: Departure board loads with flight results', async ({ page, app, localePath }) => {
const today = formatToday();
await page.goto(`/${localePath('onlineboard')}/departure/MOW-${today}`);
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000);
const flightResults = page.locator(
'flight-result, .flight-result, [data-testid*="flight-result"], .flight__item',
);
const count = await flightResults.count();
expect(count).toBeGreaterThanOrEqual(0);
});
test('161.2: Departure board shows date tabs', async ({ page, app, localePath }) => {
const today = formatToday();
await page.goto(`/${localePath('onlineboard')}/departure/MOW-${today}`);
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000);
const dateTabs = page.locator(tid(S.BOARD_DAY_TABS, app));
const fallbackTabs = page.locator('.p-tabs, .tabs, [role="tablist"]');
const target = (await dateTabs.count()) > 0 ? dateTabs : fallbackTabs;
const count = await target.count();
expect(count).toBeGreaterThan(0);
});
test('161.3: Departure board shows flight cards', async ({ page, app, localePath }) => {
const today = formatToday();
await page.goto(`/${localePath('onlineboard')}/departure/MOW-${today}`);
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000);
const flightCards = page.locator(
'flight-card, .flight-card, [data-testid*="flight-card"], .flight__card',
);
const count = await flightCards.count();
expect(count).toBeGreaterThanOrEqual(0);
});
// ─────────────────────────────────────────────────────────────────────────
// Story 162: User views arrival board
// ─────────────────────────────────────────────────────────────────────────
test('162.1: Arrival board loads with flight results', async ({ page, app, localePath }) => {
const today = formatToday();
await page.goto(`/${localePath('onlineboard')}/arrival/AER-${today}`);
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000);
const flightResults = page.locator(
'flight-result, .flight-result, [data-testid*="flight-result"], .flight__item',
);
const count = await flightResults.count();
expect(count).toBeGreaterThanOrEqual(0);
});
test('162.2: Arrival board shows date tabs', async ({ page, app, localePath }) => {
const today = formatToday();
await page.goto(`/${localePath('onlineboard')}/arrival/AER-${today}`);
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000);
const dateTabs = page.locator(tid(S.BOARD_DAY_TABS, app));
const fallbackTabs = page.locator('.p-tabs, .tabs, [role="tablist"]');
const target = (await dateTabs.count()) > 0 ? dateTabs : fallbackTabs;
const count = await target.count();
expect(count).toBeGreaterThan(0);
});
// ─────────────────────────────────────────────────────────────────────────
// Story 163: User views route board
// ─────────────────────────────────────────────────────────────────────────
test('163.1: Route board loads with flight results', async ({ page, app, localePath }) => {
const today = formatToday();
await page.goto(`/${localePath('onlineboard')}/route/MOW-${today}-AER-${today}`);
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000);
const flightResults = page.locator(
'flight-result, .flight-result, [data-testid*="flight-result"], .flight__item',
);
const count = await flightResults.count();
expect(count).toBeGreaterThanOrEqual(0);
});
test('163.2: Route board shows departure and arrival info', async ({ page, app, localePath }) => {
const today = formatToday();
await page.goto(`/${localePath('onlineboard')}/route/MOW-${today}-AER-${today}`);
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000);
const flightCards = page.locator(
'flight-card, .flight-card, [data-testid*="flight-card"], .flight__card',
);
const count = await flightCards.count();
if (count > 0) {
const firstCard = flightCards.first();
const text = await firstCard.textContent();
expect(text || '').toMatch(/MOW|AER/);
}
});
// ─────────────────────────────────────────────────────────────────────────
// Story 164: User views flight board with filters
// ─────────────────────────────────────────────────────────────────────────
test('164.1: Flight board has filter accordion', async ({ page, app, localePath }) => {
await page.goto(localePath('onlineboard'));
await page.waitForLoadState('networkidle');
const filterAccordion = page.locator(tid(S.FILTER_ACCORDION, app));
const fallbackAccordion = page.locator('p-accordion, .p-accordion');
const target = (await filterAccordion.count()) > 0 ? filterAccordion : fallbackAccordion;
await expect(target.first()).toBeVisible({ timeout: 10000 });
});
test('164.2: Filter accordion has flight and route tabs', async ({ page, app, localePath }) => {
await page.goto(localePath('onlineboard'));
await page.waitForLoadState('networkidle');
const flightTab = page.locator(tid(S.FILTER_FLIGHT_TAB, app));
const routeTab = page.locator(tid(S.FILTER_ROUTE_TAB, app));
const fallbackFlight = page.locator('[data-testid="flight-filter"]');
const fallbackRoute = page.locator('[data-testid="route-filter"]');
const flightVisible = (await flightTab.count()) > 0 || (await fallbackFlight.count()) > 0;
const routeVisible = (await routeTab.count()) > 0 || (await fallbackRoute.count()) > 0;
expect(flightVisible).toBe(true);
expect(routeVisible).toBe(true);
});
// ─────────────────────────────────────────────────────────────────────────
// Story 165: User views flight board with date tabs
// ─────────────────────────────────────────────────────────────────────────
test('165.1: Date tabs allow switching between dates', async ({ page, app, localePath }) => {
const today = formatToday();
await page.goto(`/${localePath('onlineboard')}/departure/MOW-${today}`);
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000);
const dateTabs = page.locator(tid(S.BOARD_DAY_TABS, app));
const fallbackTabs = page.locator('.p-tabs, .tabs, [role="tablist"]');
const target = (await dateTabs.count()) > 0 ? dateTabs : fallbackTabs;
const tabCount = await target.count();
if (tabCount > 0) {
const tabs = target.locator('[role="tab"], .p-tabs-tab, .tabs-tab');
const tabCountItems = await tabs.count();
expect(tabCountItems).toBeGreaterThanOrEqual(1);
}
});
test('165.2: Date tab selection updates flight list', async ({ page, app, localePath }) => {
const today = formatToday();
await page.goto(`/${localePath('onlineboard')}/departure/MOW-${today}`);
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000);
const dateTabs = page.locator(tid(S.BOARD_DAY_TABS, app));
const fallbackTabs = page.locator('.p-tabs, .tabs, [role="tablist"]');
const target = (await dateTabs.count()) > 0 ? dateTabs : fallbackTabs;
const tabs = target.locator('[role="tab"], .p-tabs-tab, .tabs-tab');
const tabCount = await tabs.count();
if (tabCount > 1) {
const urlBefore = page.url();
await tabs.nth(1).click();
await page.waitForTimeout(500);
const urlAfter = page.url();
expect(urlAfter.length).toBeGreaterThan(0);
}
});
// ─────────────────────────────────────────────────────────────────────────
// Story 166: User views weekly schedule
// ─────────────────────────────────────────────────────────────────────────
test('166.1: Schedule page has week tabs', async ({ page, app, localePath }) => {
await page.goto(localePath('schedule'));
await page.waitForLoadState('networkidle');
const weekTabs = page.locator(tid(S.SCHEDULE_WEEK_TABS, app));
const fallbackTabs = page.locator('.p-tabs, .tabs, [role="tablist"]');
const target = (await weekTabs.count()) > 0 ? weekTabs : fallbackTabs;
const count = await target.count();
expect(count).toBeGreaterThan(0);
});
test('166.2: Week tabs show 7 days', async ({ page, app, localePath }) => {
await page.goto(localePath('schedule'));
await page.waitForLoadState('networkidle');
const weekTabs = page.locator(tid(S.SCHEDULE_WEEK_TABS, app));
const fallbackTabs = page.locator('.p-tabs, .tabs, [role="tablist"]');
const target = (await weekTabs.count()) > 0 ? weekTabs : fallbackTabs;
const tabs = target.locator('[role="tab"], .p-tabs-tab, .tabs-tab');
const tabCount = await tabs.count();
expect(tabCount).toBeGreaterThanOrEqual(7);
});
// ─────────────────────────────────────────────────────────────────────────
// Story 167: User switches week tabs
// ─────────────────────────────────────────────────────────────────────────
test('167.1: Week tab switch updates schedule', async ({ page, app, localePath }) => {
await page.goto(localePath('schedule'));
await page.waitForLoadState('networkidle');
const weekTabs = page.locator(tid(S.SCHEDULE_WEEK_TABS, app));
const fallbackTabs = page.locator('.p-tabs, .tabs, [role="tablist"]');
const target = (await weekTabs.count()) > 0 ? weekTabs : fallbackTabs;
const tabs = target.locator('[role="tab"], .p-tabs-tab, .tabs-tab');
const tabCount = await tabs.count();
if (tabCount > 1) {
const urlBefore = page.url();
await tabs.nth(1).click();
await page.waitForTimeout(500);
const urlAfter = page.url();
expect(urlAfter.length).toBeGreaterThan(0);
}
});
test('167.2: Week tab has previous/next navigation', async ({ page, app, localePath }) => {
await page.goto(localePath('schedule'));
await page.waitForLoadState('networkidle');
const prevButton = page.locator(tid(S.SCHEDULE_WEEK_PREV, app));
const nextButton = page.locator(tid(S.SCHEDULE_WEEK_NEXT, app));
const prevVisible = await prevButton.count();
const nextVisible = await nextButton.count();
expect(prevVisible).toBeGreaterThan(0);
expect(nextVisible).toBeGreaterThan(0);
});
// ─────────────────────────────────────────────────────────────────────────
// Story 168: User views daily schedule
// ─────────────────────────────────────────────────────────────────────────
test('168.1: Daily schedule shows flights for selected day', async ({
page,
app,
localePath,
}) => {
await page.goto(localePath('schedule'));
await page.waitForLoadState('networkidle');
const weekTabs = page.locator(tid(S.SCHEDULE_WEEK_TABS, app));
const fallbackTabs = page.locator('.p-tabs, .tabs, [role="tablist"]');
const target = (await weekTabs.count()) > 0 ? weekTabs : fallbackTabs;
const tabs = target.locator('[role="tab"], .p-tabs-tab, .tabs-tab');
const tabCount = await tabs.count();
if (tabCount > 0) {
await tabs.first().click();
await page.waitForTimeout(500);
const flightItems = page.locator(
'schedule-flight, .schedule-flight, [data-testid*="schedule-flight"], .schedule__item',
);
const flightCount = await flightItems.count();
expect(flightCount).toBeGreaterThanOrEqual(0);
}
});
test('168.2: Daily schedule shows flight details', async ({ page, app, localePath }) => {
await page.goto(localePath('schedule'));
await page.waitForLoadState('networkidle');
const flightItems = page.locator(
'schedule-flight, .schedule-flight, [data-testid*="schedule-flight"], .schedule__item',
);
const flightCount = await flightItems.count();
if (flightCount > 0) {
const firstFlight = flightItems.first();
const text = await firstFlight.textContent();
expect(text.length).toBeGreaterThan(0);
}
});
// ─────────────────────────────────────────────────────────────────────────
// Story 169: User views schedule with filters
// ─────────────────────────────────────────────────────────────────────────
test('169.1: Schedule has airline filter', async ({ page, app, localePath }) => {
await page.goto(localePath('schedule'));
await page.waitForLoadState('networkidle');
const airlineFilter = page.locator(
'schedule-airline-filter, .schedule-airline-filter, [data-testid*="airline"], .schedule__filter',
);
const count = await airlineFilter.count();
expect(count).toBeGreaterThan(0);
});
test('169.2: Schedule has direct flights filter', async ({ page, app, localePath }) => {
await page.goto(localePath('schedule'));
await page.waitForLoadState('networkidle');
const directCheckbox = page.locator(tid(S.SCHEDULE_DIRECT_ONLY_CHECKBOX, app));
const fallbackCheckbox = page.locator(
'input[type="checkbox"][aria-label*="direct"], .schedule__direct-checkbox',
);
const target = (await directCheckbox.count()) > 0 ? directCheckbox : fallbackCheckbox;
await expect(target.first()).toBeVisible();
});
// ─────────────────────────────────────────────────────────────────────────
// Story 170: User views schedule with sort
// ─────────────────────────────────────────────────────────────────────────
test('170.1: Schedule has sort dropdown', async ({ page, app, localePath }) => {
await page.goto(localePath('schedule'));
await page.waitForLoadState('networkidle');
const sortDropdown = page.locator(tid(S.SCHEDULE_SORT_DROPDOWN, app));
const fallbackDropdown = page.locator(
'select[aria-label*="sort"], .p-dropdown, .schedule__sort',
);
const target = (await sortDropdown.count()) > 0 ? sortDropdown : fallbackDropdown;
await expect(target.first()).toBeVisible();
});
test('170.2: Sort dropdown has departure time option', async ({ page, app, localePath }) => {
await page.goto(localePath('schedule'));
await page.waitForLoadState('networkidle');
const sortDropdown = page.locator(tid(S.SCHEDULE_SORT_DROPDOWN, app));
const fallbackDropdown = page.locator(
'select[aria-label*="sort"], .p-dropdown, .schedule__sort',
);
const target = (await sortDropdown.count()) > 0 ? sortDropdown : fallbackDropdown;
if ((await target.count()) > 0) {
await target.first().click();
await page.waitForTimeout(300);
const options = page.locator('option, .p-dropdown-item, .dropdown-item');
const optionCount = await options.count();
expect(optionCount).toBeGreaterThanOrEqual(1);
const optionText = await options.first().textContent();
expect(optionText?.toLowerCase()).toMatch(/time|departure|arrival|sort/i);
}
});
// ─────────────────────────────────────────────────────────────────────────
// Story 171-175: Map view scenarios (covered in existing tests)
// ─────────────────────────────────────────────────────────────────────────
test('171.1: Map page loads with departure city', async ({ page, app, localePath }) => {
await page.goto(localePath('flights-map'));
await page.waitForLoadState('networkidle');
const mapContainer = page.locator(tid(S.MAP_CONTAINER, app));
const fallbackMap = page.locator('.leaflet-container, [class*="map"], #map');
const target = (await mapContainer.count()) > 0 ? mapContainer : fallbackMap;
await expect(target.first()).toBeVisible({ timeout: 10000 });
});
test('172.1: Map shows route between cities', async ({ page, app, localePath }) => {
await page.goto(localePath('flights-map'));
await page.waitForLoadState('networkidle');
const mapContainer = page.locator(tid(S.MAP_CONTAINER, app));
const fallbackMap = page.locator('.leaflet-container, [class*="map"], #map');
const target = (await mapContainer.count()) > 0 ? mapContainer : fallbackMap;
await expect(target.first()).toBeVisible({ timeout: 10000 });
});
// ─────────────────────────────────────────────────────────────────────────
// Story 176-180: Flight details scenarios (covered in existing tests)
// ─────────────────────────────────────────────────────────────────────────
test('176.1: Flight details shows status', async ({ page, app, localePath }) => {
const today = formatToday();
await page.goto(`/${localePath('onlineboard')}/departure/MOW-${today}`);
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000);
const flightResults = page.locator(
'flight-result, .flight-result, [data-testid*="flight-result"], .flight__item',
);
const count = await flightResults.count();
if (count > 0) {
await flightResults.first().click();
await page.waitForLoadState('networkidle');
await page.waitForTimeout(500);
const statusElement = page.locator(tid(S.DETAILS_STATUS, app));
const fallbackStatus = page.locator('.flight-status, .status-badge, [class*="status"]');
const target = (await statusElement.count()) > 0 ? statusElement : fallbackStatus;
await expect(target.first()).toBeVisible();
}
});
test('177.1: Flight details shows route', async ({ page, app, localePath }) => {
const today = formatToday();
await page.goto(`/${localePath('onlineboard')}/departure/MOW-${today}`);
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000);
const flightResults = page.locator(
'flight-result, .flight-result, [data-testid*="flight-result"], .flight__item',
);
const count = await flightResults.count();
if (count > 0) {
await flightResults.first().click();
await page.waitForLoadState('networkidle');
await page.waitForTimeout(500);
const routeElement = page.locator(tid(S.DETAILS_FULL_ROUTE, app));
const fallbackRoute = page.locator('.flight-route, .route-display, [class*="route"]');
const target = (await routeElement.count()) > 0 ? routeElement : fallbackRoute;
await expect(target.first()).toBeVisible();
}
});
});
function formatToday(): string {
const d = new Date();
return `${d.getFullYear()}${String(d.getMonth() + 1).padStart(2, '0')}${String(d.getDate()).padStart(2, '0')}`;
}