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}`; }