Files
flights_web/tests/e2e-angular/cross-app/01-navigation.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

234 lines
9.5 KiB
TypeScript

import { test, expect } from '../support/cross-app-fixtures';
import { mockAllAPIs } from '../support/cross-app-fixtures';
import { S, tid } from '../support/selectors';
test.describe('Navigation & Layout', () => {
test.beforeEach(async ({ page, localePath }) => {
await mockAllAPIs(page);
await page.goto(localePath('onlineboard'));
await page.waitForLoadState('networkidle');
});
test('1: Tab "Online Board" is visible and active by default', async ({ page, app }) => {
const tab = page.locator(tid(S.NAV_ONLINEBOARD_TAB, app));
await expect(tab).toBeVisible();
await expect(tab).toHaveClass(/active|selected/);
});
test('2: Tab "Schedule" navigates to schedule page', async ({ page, app, locale }) => {
const tab = page.locator(tid(S.NAV_SCHEDULE_TAB, app));
await expect(tab).toBeVisible();
await tab.click();
await expect(page).toHaveURL(new RegExp(`/${locale}/schedule`));
});
test('3: Tab "Flights Map" navigates to flights map page', async ({ page, app, locale }) => {
const tab = page.locator(tid(S.NAV_FLIGHTS_MAP_TAB, app));
await expect(tab).toBeVisible();
await tab.click();
await expect(page).toHaveURL(new RegExp(`/${locale}/flights-map`));
});
test('4: Tab active state matches current route', async ({ page, app, locale }) => {
// Navigate to schedule
await page.goto(`/${locale}/schedule`);
await page.waitForLoadState('networkidle');
const scheduleTab = page.locator(tid(S.NAV_SCHEDULE_TAB, app));
await expect(scheduleTab).toHaveClass(/active|selected/);
const boardTab = page.locator(tid(S.NAV_ONLINEBOARD_TAB, app));
await expect(boardTab).not.toHaveClass(/active|selected/);
});
test('5: Breadcrumbs show correct path on landing', async ({ page, app }) => {
const breadcrumbs = page.locator(tid(S.LAYOUT_BREADCRUMBS, app));
// Angular uses PrimeNG p-breadcrumb without data-testid; fall back to tag selector
const fallback = page.locator('p-breadcrumb, nav[aria-label*="bread"]');
const target = (await breadcrumbs.count()) > 0 ? breadcrumbs : fallback;
await expect(target).toBeVisible();
});
test('6: Breadcrumbs show correct path on search results', async ({
page,
app,
locale,
localePath,
}) => {
// Navigate to a departure search
const path = `onlineboard/departure/MOW-${formatToday()}`;
const url = localePath(path);
console.log('Test 6 URL:', url);
await page.goto(url, {
waitUntil: 'domcontentloaded',
});
console.log('Test 6 Current URL:', page.url());
const breadcrumbs = page.locator(tid(S.LAYOUT_BREADCRUMBS, app));
const fallback = page.locator('p-breadcrumb, nav[aria-label*="bread"]');
const target = (await breadcrumbs.count()) > 0 ? breadcrumbs : fallback;
await expect(target).toBeVisible();
// Should have at least 1 link
const links = target.locator('a');
expect(await links.count()).toBeGreaterThanOrEqual(1);
});
test('7: Breadcrumbs show correct path on flight details', async ({ page, app, locale }) => {
// Navigate to a search first, then open details
await page.goto(`/${locale}/onlineboard/departure/MOW-${formatToday()}`);
await page.waitForLoadState('networkidle');
const firstFlight = page.locator(tid(S.BOARD_FLIGHT_RESULT, app)).first();
// If results exist, click through to details
const count = await firstFlight.count();
if (count > 0) {
await firstFlight.click();
await page.waitForLoadState('networkidle');
const breadcrumbs = page.locator(tid(S.LAYOUT_BREADCRUMBS, app));
const fallback = page.locator('p-breadcrumb, nav[aria-label*="bread"]');
const target = (await breadcrumbs.count()) > 0 ? breadcrumbs : fallback;
await expect(target).toBeVisible();
expect(await target.locator('a').count()).toBeGreaterThanOrEqual(2);
}
});
test('8: Breadcrumbs links are clickable and navigate correctly', async ({
page,
app,
locale,
}) => {
await page.goto(`/${locale}/schedule`);
await page.waitForLoadState('networkidle');
const breadcrumbs = page.locator(tid(S.LAYOUT_BREADCRUMBS, app));
const fallback = page.locator('p-breadcrumb, nav[aria-label*="bread"]');
const target = (await breadcrumbs.count()) > 0 ? breadcrumbs : fallback;
const links = target.locator('a');
if ((await links.count()) > 0) {
// The first breadcrumb link typically navigates home or to main section
const firstHref = await links.first().getAttribute('href');
expect(firstHref).toBeTruthy();
}
});
test('9: Locale switcher button shows current locale code', async ({ page, app }) => {
const switcher = page.locator(tid(S.LAYOUT_LOCALE_SWITCHER, app));
if ((await switcher.count()) === 0) {
test.skip(true, 'Locale switcher not present in this app');
return;
}
await expect(switcher).toBeVisible();
});
test('10: Locale switcher dropdown opens on click', async ({ page, app }) => {
const switcher = page.locator(tid(S.LAYOUT_LOCALE_SWITCHER, app));
if ((await switcher.count()) === 0) {
test.skip(true, 'Locale switcher not present in this app');
return;
}
await switcher.click();
const options = page.locator(tid(S.LAYOUT_LOCALE_OPTION, app));
await expect(options.first()).toBeVisible();
});
test('11: Locale switcher shows all available locales', async ({ page, app }) => {
const switcher = page.locator(tid(S.LAYOUT_LOCALE_SWITCHER, app));
if ((await switcher.count()) === 0) {
test.skip(true, 'Locale switcher not present in this app');
return;
}
await switcher.click();
const options = page.locator(tid(S.LAYOUT_LOCALE_OPTION, app));
expect(await options.count()).toBeGreaterThanOrEqual(9);
});
test('12: Locale switcher changes URL prefix on selection', async ({ page, app, locale }) => {
const switcher = page.locator(tid(S.LAYOUT_LOCALE_SWITCHER, app));
if ((await switcher.count()) === 0) {
test.skip(true, 'Locale switcher not present in this app');
return;
}
await switcher.click();
const targetLocale = locale === 'en-us' ? 'ru-ru' : 'en-us';
const option = page.locator(
`${tid(S.LAYOUT_LOCALE_OPTION, app)}[data-locale="${targetLocale}"], ${tid(S.LAYOUT_LOCALE_OPTION, app)}:has-text("${targetLocale === 'en-us' ? 'English' : 'Русский'}")`,
);
if ((await option.count()) > 0) {
await option.first().click();
await expect(page).toHaveURL(new RegExp(`/${targetLocale}/`));
}
});
test('13: Locale switcher closes on outside click', async ({ page, app }) => {
const switcher = page.locator(tid(S.LAYOUT_LOCALE_SWITCHER, app));
if ((await switcher.count()) === 0) {
test.skip(true, 'Locale switcher not present in this app');
return;
}
await switcher.click();
const options = page.locator(tid(S.LAYOUT_LOCALE_OPTION, app));
await expect(options.first()).toBeVisible();
await page.locator('body').click({ position: { x: 0, y: 0 } });
await expect(options.first()).toBeHidden();
});
test('14: Feedback button is visible in layout', async ({ page, app }) => {
const button = page.locator(tid(S.LAYOUT_FEEDBACK_BUTTON, app));
if ((await button.count()) === 0) {
test.skip(true, 'Feedback button not present in this app');
return;
}
await expect(button).toBeVisible();
});
test('15: Feedback button opens feedback form on click', async ({ page, app }) => {
const button = page.locator(tid(S.LAYOUT_FEEDBACK_BUTTON, app));
if ((await button.count()) === 0) {
test.skip(true, 'Feedback button not present in this app');
return;
}
await button.click();
await expect(page.locator('[role="dialog"], .feedback-form, .modal')).toBeVisible({
timeout: 5000,
});
});
test('16: Scroll-to-top button appears after scrolling down', async ({ page, app }) => {
const scrollBtn = page.locator(tid(S.LAYOUT_SCROLL_TOP_BUTTON, app));
// Some apps may not have this feature
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
await page.waitForTimeout(500);
if ((await scrollBtn.count()) === 0) {
test.skip(true, 'Scroll-to-top button not present in this app');
return;
}
await expect(scrollBtn).toBeVisible({ timeout: 5000 });
});
test('17: Scroll-to-top button scrolls page to top on click', async ({ page, app }) => {
const scrollBtn = page.locator(tid(S.LAYOUT_SCROLL_TOP_BUTTON, app));
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
await page.waitForTimeout(500);
if ((await scrollBtn.count()) === 0) {
test.skip(true, 'Scroll-to-top button not present in this app');
return;
}
await expect(scrollBtn).toBeVisible({ timeout: 5000 });
await scrollBtn.click();
await page.waitForTimeout(500);
const scrollY = await page.evaluate(() => window.scrollY);
expect(scrollY).toBeLessThan(100);
});
test('18: Scroll-to-top button hides when at top', async ({ page, app }) => {
const scrollBtn = page.locator(tid(S.LAYOUT_SCROLL_TOP_BUTTON, app));
if ((await scrollBtn.count()) === 0) {
test.skip(true, 'Scroll-to-top button not present in this app');
return;
}
// At top of page, button should be hidden
await expect(scrollBtn).toBeHidden();
});
});
function formatToday(timeFrom = '0000', timeTo = '2359'): string {
const d = new Date();
const dateStr = `${d.getFullYear()}${String(d.getMonth() + 1).padStart(2, '0')}${String(d.getDate()).padStart(2, '0')}`;
return `${dateStr}-${timeFrom}${timeTo}`;
}