/** * Canonical data-testid selector map for cross-app e2e tests. * * Both Angular and React apps must implement these testids. * Where Angular uses a different testid, add an entry to ANGULAR_OVERRIDES. */ export const S = { // Navigation & Layout NAV_ONLINEBOARD_TAB: 'nav-onlineboard-tab', NAV_SCHEDULE_TAB: 'nav-schedule-tab', NAV_FLIGHTS_MAP_TAB: 'nav-flights-map-tab', LAYOUT_BREADCRUMBS: 'layout-breadcrumbs', LAYOUT_FEEDBACK_BUTTON: 'layout-feedback-button', LAYOUT_SCROLL_TOP_BUTTON: 'layout-scroll-top-button', LAYOUT_LOCALE_SWITCHER: 'layout-locale-switcher', LAYOUT_LOCALE_OPTION: 'layout-locale-option', // Online Board - Filter FILTER_ACCORDION: 'filter-accordion', FILTER_FLIGHT_TAB: 'filter-flight-tab', FILTER_ROUTE_TAB: 'filter-route-tab', FILTER_FLIGHT_NUMBER_INPUT: 'filter-flight-number-input', FILTER_FLIGHT_NUMBER_CLEAR: 'filter-flight-number-clear', FILTER_FLIGHT_NUMBER_CALENDAR: 'filter-flight-number-calendar', FILTER_FLIGHT_NUMBER_SEARCH: 'filter-flight-number-search', FILTER_ROUTE_DEPARTURE_INPUT: 'filter-route-departure-input', FILTER_ROUTE_ARRIVAL_INPUT: 'filter-route-arrival-input', FILTER_ROUTE_SWAP_BUTTON: 'filter-route-swap-button', FILTER_ROUTE_CALENDAR: 'filter-route-calendar', FILTER_ROUTE_TIME_SELECTOR: 'filter-route-time-selector', FILTER_ROUTE_SEARCH: 'filter-route-search', // Online Board - Results BOARD_DAY_TABS: 'board-day-tabs', BOARD_DAY_TAB: 'board-day-tab', BOARD_TIME_SELECTOR: 'board-time-selector', BOARD_SEARCH_RESULT: 'board-search-result', BOARD_FLIGHT_RESULT: 'board-flight-result', BOARD_FLIGHT_NUMBER: 'board-flight-number', BOARD_FLIGHT_STATUS: 'board-flight-status', BOARD_FLIGHT_EXPAND: 'board-flight-expand', BOARD_LOADER: 'board-loader', BOARD_EMPTY_LIST: 'board-empty-list', BOARD_CANCEL_BUTTON: 'board-cancel-button', // Flight Details DETAILS_FLIGHT_NUMBER: 'details-flight-number', DETAILS_DEPARTURE_STATION: 'details-departure-station', DETAILS_ARRIVAL_STATION: 'details-arrival-station', DETAILS_DEPARTURE_TIME: 'details-departure-time', DETAILS_ARRIVAL_TIME: 'details-arrival-time', DETAILS_STATUS: 'details-status', DETAILS_DURATION: 'details-duration', DETAILS_OPERATOR_LOGO: 'details-operator-logo', DETAILS_AIRCRAFT_MODEL: 'details-aircraft-model', DETAILS_PRINT_BUTTON: 'details-print-button', DETAILS_SHARE_BUTTON: 'details-share-button', DETAILS_BUY_TICKET_BUTTON: 'details-buy-ticket-button', DETAILS_REGISTRATION_BUTTON: 'details-registration-button', DETAILS_FLIGHT_STATUS_BUTTON: 'details-flight-status-button', DETAILS_TRANSFER_SECTION: 'details-transfer-section', DETAILS_FULL_ROUTE: 'details-full-route', DETAILS_TERMINAL_LINK: 'details-terminal-link', // Landing Page LANDING_SECTION: 'landing-section', LANDING_POPULAR_REQUEST: 'landing-popular-request', 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', SCHEDULE_SWAP_BUTTON: 'schedule-swap-button', SCHEDULE_CALENDAR: 'schedule-calendar', SCHEDULE_RETURN_CALENDAR: 'schedule-return-calendar', SCHEDULE_TIME_SELECTOR: 'schedule-time-selector', SCHEDULE_RETURN_TIME_SELECTOR: 'schedule-return-time-selector', SCHEDULE_DIRECT_ONLY_CHECKBOX: 'schedule-direct-only-checkbox', SCHEDULE_RETURN_CHECKBOX: 'schedule-return-checkbox', SCHEDULE_SEARCH_BUTTON: 'schedule-search-button', // Schedule - Results SCHEDULE_WEEK_TABS: 'schedule-week-tabs', SCHEDULE_WEEK_TAB: 'schedule-week-tab', SCHEDULE_WEEK_PREV: 'schedule-week-prev', SCHEDULE_WEEK_NEXT: 'schedule-week-next', SCHEDULE_DIRECTION_SWITCH: 'schedule-direction-switch', SCHEDULE_SORT_DROPDOWN: 'schedule-sort-dropdown', SCHEDULE_FLIGHT_DAY: 'schedule-flight-day', SCHEDULE_FLIGHT_ITEM: 'schedule-flight-item', SCHEDULE_LOADER: 'schedule-loader', // Schedule - Details SCHEDULE_DETAILS_BACK_BUTTON: 'schedule-details-back-button', SCHEDULE_DETAILS_DAY_TABS: 'schedule-details-day-tabs', SCHEDULE_DETAILS_FLIGHT_MINI: 'schedule-details-flight-mini', SCHEDULE_DETAILS_TRANSFER: 'schedule-details-transfer', // Flights Map MAP_CONTAINER: 'map-container', MAP_DEPARTURE_INPUT: 'map-departure-input', MAP_ARRIVAL_INPUT: 'map-arrival-input', MAP_SWAP_BUTTON: 'map-swap-button', MAP_CALENDAR: 'map-calendar', MAP_DOMESTIC_TOGGLE: 'map-domestic-toggle', MAP_INTERNATIONAL_TOGGLE: 'map-international-toggle', MAP_CONNECTING_TOGGLE: 'map-connecting-toggle', MAP_MARKER: 'map-marker', MAP_MARKER_CLUSTER: 'map-marker-cluster', // Shared Components CITY_AUTOCOMPLETE_INPUT: 'city-autocomplete-input', CITY_AUTOCOMPLETE_POPUP: 'city-autocomplete-popup', CITY_AUTOCOMPLETE_CLEAR: 'city-autocomplete-clear', CITY_AUTOCOMPLETE_OPTION: 'city-autocomplete-option', CITY_CODE_DISPLAY: 'city-code-display', TIME_SELECTOR_FROM: 'time-selector-from', TIME_SELECTOR_TO: 'time-selector-to', TIME_SELECTOR_TRACK: 'time-selector-track', CALENDAR_INPUT: 'calendar-input', CALENDAR_CLEAR: 'calendar-clear', // Error Pages ERROR_PAGE_404: 'error-page-404', ERROR_PAGE_GENERIC: 'error-page-generic', ERROR_PAGE_HOME_LINK: 'error-page-home-link', } as const; export type SelectorKey = keyof typeof S; /** * Angular app uses different testid names in some places. * This map translates canonical names to Angular-specific ones. */ const ANGULAR_OVERRIDES: Partial> = { [S.NAV_ONLINEBOARD_TAB]: 'onlineboard-tab', [S.NAV_SCHEDULE_TAB]: 'schedule-tab', [S.NAV_FLIGHTS_MAP_TAB]: 'flights-map-tab', [S.FILTER_FLIGHT_TAB]: 'flight-filter', [S.FILTER_ROUTE_TAB]: 'route-filter', [S.FILTER_FLIGHT_NUMBER_INPUT]: 'flight-number-input', [S.FILTER_FLIGHT_NUMBER_CLEAR]: 'flight-number-clear-button', [S.FILTER_FLIGHT_NUMBER_CALENDAR]: 'flight-number-calendar', [S.FILTER_FLIGHT_NUMBER_SEARCH]: 'flight-number-search-button', [S.FILTER_ROUTE_DEPARTURE_INPUT]: 'route-departure-city-input', [S.FILTER_ROUTE_ARRIVAL_INPUT]: 'route-arrival-city-input', [S.FILTER_ROUTE_CALENDAR]: 'route-calendar-input', [S.FILTER_ROUTE_SEARCH]: 'route-search-button', [S.SCHEDULE_DEPARTURE_INPUT]: 'schedule-departure-city-input', [S.SCHEDULE_ARRIVAL_INPUT]: 'schedule-arrival-city-input', [S.SCHEDULE_SEARCH_BUTTON]: 'schedule-search-button', [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', [S.BOARD_FLIGHT_NUMBER]: 'flight-carrier-number', [S.DETAILS_FLIGHT_NUMBER]: 'flight-details-number', [S.CITY_AUTOCOMPLETE_INPUT]: 'city-autocomplete-input', [S.CITY_AUTOCOMPLETE_CLEAR]: 'autocomplete-clear-input', [S.CITY_AUTOCOMPLETE_POPUP]: 'autocomplete-popup-button', [S.CITY_CODE_DISPLAY]: 'city-code', [S.CALENDAR_INPUT]: 'calendar-input', }; /** * Get the data-testid selector string for a given app. * Returns `[data-testid="..."]` ready for use with page.locator(). */ export function tid(name: string, app: 'angular' | 'react'): string { const testid = app === 'angular' && ANGULAR_OVERRIDES[name] ? ANGULAR_OVERRIDES[name] : name; return `[data-testid="${testid}"]`; } /** * Shorthand: get locator from page using canonical testid. */ export function byTestId( page: { locator: (s: string) => unknown }, name: string, app: 'angular' | 'react', ) { return page.locator(tid(name, app)); }