Add popular requests behavioral cross-app tests
Adds POPULAR_REQUESTS_PANEL and POPULAR_REQUEST_ITEM selectors with Angular overrides, and 6 behavioral tests covering panel visibility, item count, flight/route click navigation, schedule page presence, and keyboard accessibility.
This commit is contained in:
@@ -0,0 +1,169 @@
|
||||
import { test, expect } from '../support/cross-app-fixtures';
|
||||
import { S, tid } from '../support/selectors';
|
||||
|
||||
test.describe('Popular Requests Behavior', () => {
|
||||
test.beforeEach(async ({ page, localePath }) => {
|
||||
await page.goto(localePath('onlineboard'));
|
||||
await page.waitForLoadState('networkidle');
|
||||
});
|
||||
|
||||
test('1: Popular requests panel is visible on onlineboard start page', async ({ page, app }) => {
|
||||
const panel = page.locator(tid(S.POPULAR_REQUESTS_PANEL, app));
|
||||
const fallback = page.locator(
|
||||
'.popular-requests, popular-requests, [data-testid="landing-popular-request"]',
|
||||
);
|
||||
const target = (await panel.count()) > 0 ? panel : fallback.first();
|
||||
if ((await target.count()) === 0) {
|
||||
test.skip(true, 'Popular requests panel not present in this app');
|
||||
return;
|
||||
}
|
||||
await expect(target).toBeVisible({ timeout: 10000 });
|
||||
});
|
||||
|
||||
test('2: Popular requests panel shows up to 4 items', async ({ page, app }) => {
|
||||
const items = page.locator(tid(S.POPULAR_REQUEST_ITEM, app));
|
||||
const fallback = page.locator(
|
||||
'popular-request, .popular-requests__item, [data-testid="landing-popular-request"]',
|
||||
);
|
||||
const target = (await items.count()) > 0 ? items : fallback;
|
||||
const count = await target.count();
|
||||
if (count === 0) {
|
||||
test.skip(true, 'No popular request items found');
|
||||
return;
|
||||
}
|
||||
expect(count).toBeGreaterThanOrEqual(1);
|
||||
expect(count).toBeLessThanOrEqual(4);
|
||||
});
|
||||
|
||||
test('3: Clicking a flight number request navigates to flight search', async ({
|
||||
page,
|
||||
app,
|
||||
locale,
|
||||
}) => {
|
||||
const items = page.locator(tid(S.POPULAR_REQUEST_ITEM, app));
|
||||
const fallback = page.locator(
|
||||
'popular-request, .popular-requests__item, [data-testid="landing-popular-request"]',
|
||||
);
|
||||
const target = (await items.count()) > 0 ? items : fallback;
|
||||
const count = await target.count();
|
||||
if (count === 0) {
|
||||
test.skip(true, 'No popular request items found');
|
||||
return;
|
||||
}
|
||||
|
||||
// Find a flight-number request item (contains a flight number pattern like SU1234 or SU 1234)
|
||||
let flightItem = null;
|
||||
for (let i = 0; i < count; i++) {
|
||||
const text = await target.nth(i).textContent();
|
||||
if (text && /[A-Z]{2}\s*\d{1,4}/i.test(text)) {
|
||||
flightItem = target.nth(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!flightItem) {
|
||||
test.skip(true, 'No flight-number popular request found');
|
||||
return;
|
||||
}
|
||||
|
||||
const urlBefore = page.url();
|
||||
await flightItem.click();
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const urlAfter = page.url();
|
||||
// Should have navigated away from the landing page
|
||||
expect(urlAfter).not.toBe(urlBefore);
|
||||
// URL should indicate a flight search (flight number in path or query)
|
||||
expect(urlAfter).toMatch(/onlineboard\/(departure|arrival|flight)|flight/i);
|
||||
});
|
||||
|
||||
test('4: Clicking a route request navigates to route search', async ({ page, app, locale }) => {
|
||||
const items = page.locator(tid(S.POPULAR_REQUEST_ITEM, app));
|
||||
const fallback = page.locator(
|
||||
'popular-request, .popular-requests__item, [data-testid="landing-popular-request"]',
|
||||
);
|
||||
const target = (await items.count()) > 0 ? items : fallback;
|
||||
const count = await target.count();
|
||||
if (count === 0) {
|
||||
test.skip(true, 'No popular request items found');
|
||||
return;
|
||||
}
|
||||
|
||||
// Find a route request item (contains city names or route pattern like MOW-LED, or has a dash/arrow between cities)
|
||||
let routeItem = null;
|
||||
for (let i = 0; i < count; i++) {
|
||||
const text = await target.nth(i).textContent();
|
||||
// Route items typically contain an arrow, dash between cities, or two city codes
|
||||
if (text && (/[A-Z]{3}\s*[-\u2013\u2014\u2192]\s*[A-Z]{3}/.test(text) || /\u2014|\u2192|->/.test(text))) {
|
||||
routeItem = target.nth(i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!routeItem) {
|
||||
// Fallback: just click the last item (routes are often listed after flight numbers)
|
||||
routeItem = target.last();
|
||||
}
|
||||
|
||||
const urlBefore = page.url();
|
||||
await routeItem.click();
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const urlAfter = page.url();
|
||||
expect(urlAfter).not.toBe(urlBefore);
|
||||
// URL should indicate a route/departure search
|
||||
expect(urlAfter).toMatch(/onlineboard\/(departure|arrival)|schedule/i);
|
||||
});
|
||||
|
||||
test('5: Popular requests visible on schedule start page', async ({ page, app, localePath }) => {
|
||||
await page.goto(localePath('schedule'));
|
||||
await page.waitForLoadState('networkidle');
|
||||
|
||||
const panel = page.locator(tid(S.POPULAR_REQUESTS_PANEL, app));
|
||||
const fallback = page.locator(
|
||||
'.popular-requests, popular-requests, [data-testid="landing-popular-request"]',
|
||||
);
|
||||
const target = (await panel.count()) > 0 ? panel : fallback.first();
|
||||
if ((await target.count()) === 0) {
|
||||
test.skip(true, 'Popular requests panel not present on schedule page');
|
||||
return;
|
||||
}
|
||||
await expect(target).toBeVisible({ timeout: 10000 });
|
||||
});
|
||||
|
||||
test('6: Popular request items are keyboard accessible', async ({ page, app }) => {
|
||||
const items = page.locator(tid(S.POPULAR_REQUEST_ITEM, app));
|
||||
const fallback = page.locator(
|
||||
'popular-request, .popular-requests__item, [data-testid="landing-popular-request"]',
|
||||
);
|
||||
const target = (await items.count()) > 0 ? items : fallback;
|
||||
const count = await target.count();
|
||||
if (count === 0) {
|
||||
test.skip(true, 'No popular request items found');
|
||||
return;
|
||||
}
|
||||
|
||||
const firstItem = target.first();
|
||||
await expect(firstItem).toBeVisible();
|
||||
|
||||
// Check that the item or its child link/button is focusable
|
||||
const focusable = firstItem.locator('a, button, [tabindex="0"]');
|
||||
if ((await focusable.count()) > 0) {
|
||||
await focusable.first().focus();
|
||||
await expect(focusable.first()).toBeFocused();
|
||||
|
||||
// Press Enter and verify it triggers navigation
|
||||
const urlBefore = page.url();
|
||||
await page.keyboard.press('Enter');
|
||||
await page.waitForLoadState('networkidle');
|
||||
const urlAfter = page.url();
|
||||
expect(urlAfter).not.toBe(urlBefore);
|
||||
} else {
|
||||
// The item itself might be focusable
|
||||
const tabindex = await firstItem.getAttribute('tabindex');
|
||||
const role = await firstItem.getAttribute('role');
|
||||
const tagName = await firstItem.evaluate((el) => el.tagName.toLowerCase());
|
||||
const isFocusable =
|
||||
tabindex !== null || role === 'link' || role === 'button' || tagName === 'a';
|
||||
expect(isFocusable).toBe(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -69,6 +69,10 @@ export const S = {
|
||||
LANDING_SEARCH_HISTORY: 'landing-search-history',
|
||||
LANDING_SEARCH_HISTORY_ITEM: 'landing-search-history-item',
|
||||
|
||||
// Popular Requests
|
||||
POPULAR_REQUESTS_PANEL: 'popular-requests-panel',
|
||||
POPULAR_REQUEST_ITEM: 'popular-request-item',
|
||||
|
||||
// Schedule - Filter
|
||||
SCHEDULE_DEPARTURE_INPUT: 'schedule-departure-input',
|
||||
SCHEDULE_ARRIVAL_INPUT: 'schedule-arrival-input',
|
||||
@@ -154,6 +158,8 @@ const ANGULAR_OVERRIDES: Partial<Record<string, string>> = {
|
||||
[S.MAP_DEPARTURE_INPUT]: 'route-departure-city-input',
|
||||
[S.MAP_ARRIVAL_INPUT]: 'route-arrival-city-input',
|
||||
[S.MAP_CALENDAR]: 'route-calendar-input',
|
||||
[S.POPULAR_REQUESTS_PANEL]: 'popular-requests',
|
||||
[S.POPULAR_REQUEST_ITEM]: 'popular-request',
|
||||
[S.BOARD_LOADER]: 'loader',
|
||||
[S.BOARD_SEARCH_RESULT]: 'board-search-result',
|
||||
[S.BOARD_FLIGHT_RESULT]: 'flight-result',
|
||||
|
||||
Reference in New Issue
Block a user