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.
472 lines
22 KiB
TypeScript
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')}`;
|
|
}
|