Files
flights_web/tests/e2e-angular/cross-app/13-locale-switching.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

641 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';
/**
* Locale Switching Cross-App Test Suite (Tests 298-315)
*
* 18 tests covering:
* - Locale switcher visibility and accessibility
* - Dropdown display and interaction
* - Locale selection and navigation
* - Content translation verification
* - Locale persistence across pages and reloads
*/
// Map of supported locales to their display names
const LOCALE_NAMES: Record<string, string> = {
'ru-ru': 'Русский',
'en-us': 'English',
'es-es': 'Español',
'fr-fr': 'Français',
'it-it': 'Italiano',
'ja-jp': '日本語',
'ko-kr': '한국어',
'zh-cn': '中文',
'de-de': 'Deutsch',
};
// Sample translation keys and their expected values per locale
// Reference data for translation testing (some tests may use subset of these)
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const _TRANSLATION_SAMPLES: Record<string, Record<string, string>> = {
'ru-ru': {
'nav.flightBoard': 'Онлайн табло',
'nav.schedule': 'Расписание',
'search.find': 'Поиск',
},
'en-us': {
'nav.flightBoard': 'Online Board',
'nav.schedule': 'Schedule',
'search.find': 'Search',
},
'es-es': {
'nav.flightBoard': 'Tablero en línea',
'nav.schedule': 'Horario',
'search.find': 'Buscar',
},
'fr-fr': {
'nav.flightBoard': "Tableau d'affichage en direct",
'nav.schedule': 'Horaire',
'search.find': 'Rechercher',
},
'it-it': {
'nav.flightBoard': 'Scheda Online',
'nav.schedule': 'Programma',
'search.find': 'Cerca',
},
'ja-jp': {
'nav.flightBoard': 'オンラインボード',
'nav.schedule': 'スケジュール',
'search.find': '検索',
},
'ko-kr': {
'nav.flightBoard': '온라인 보드',
'nav.schedule': '일정',
'search.find': '검색',
},
'zh-cn': {
'nav.flightBoard': '在线板',
'nav.schedule': '航班时刻表',
'search.find': '搜索',
},
'de-de': {
'nav.flightBoard': 'Online-Tafel',
'nav.schedule': 'Fahrplan',
'search.find': 'Suchen',
},
};
test.describe('Locale Switching (Cross-App)', () => {
test.beforeEach(async ({ page, localePath }) => {
await mockAllAPIs(page);
await page.goto(localePath('/'));
await page.waitForLoadState('networkidle');
});
// ====================================================================
// SECTION 1: Locale Switcher Visibility & Accessibility (Tests 298-300)
// ====================================================================
test('298: Locale switcher button is visible in layout', 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('299: Locale switcher button shows current locale code', 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;
}
// Switcher should display the current locale code (e.g., "RU-RU", "EN-US")
const localeCode = locale.toUpperCase();
await expect(switcher).toContainText(localeCode);
});
test('300: Locale switcher button is clickable', 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).toBeEnabled();
// Verify it's actually a button or has button-like properties
const role = await switcher.getAttribute('role');
const isButton =
(await switcher.evaluate((el) => el instanceof HTMLButtonElement)) ||
role === 'button' ||
(await switcher.locator('button').count()) > 0;
expect(isButton).toBe(true);
});
// ====================================================================
// SECTION 2: Locale Dropdown Display (Tests 301-303)
// ====================================================================
test('301: Clicking switcher opens dropdown menu', 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({ timeout: 5000 });
});
test('302: Dropdown shows all 9 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));
const optionCount = await options.count();
// Should have at least the 9 tested locales
expect(optionCount).toBeGreaterThanOrEqual(9);
});
test('303: Dropdown displays locale names/codes correctly', 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));
// Verify options display native names or codes
const optionTexts: string[] = [];
for (let i = 0; i < Math.min(await options.count(), 9); i++) {
const text = await options.nth(i).textContent();
if (text) optionTexts.push(text);
}
// Should have some recognizable locale text
const hasLocaleText = optionTexts.some(
(text) =>
text.includes('English') ||
text.includes('Русский') ||
text.includes('Español') ||
text.includes('Français') ||
text.includes('en-us') ||
text.includes('ru-ru'),
);
expect(hasLocaleText).toBe(true);
});
// ====================================================================
// SECTION 3: Locale Selection & Navigation (Tests 304-307)
// ====================================================================
test('304: Selecting different locale closes dropdown', 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 options = page.locator(tid(S.LAYOUT_LOCALE_OPTION, app));
await expect(options.first()).toBeVisible();
// Click first visible option (if not current locale)
const firstOption = options.first();
const firstLocaleCode = await firstOption.getAttribute('data-locale');
if (firstLocaleCode && firstLocaleCode !== locale.split('-')[0]) {
await firstOption.click();
// Wait for navigation and dropdown to close
await page.waitForLoadState('networkidle');
await page.waitForTimeout(300);
// Dropdown should be hidden
await expect(options.first()).toBeHidden();
} else {
test.skip(true, 'No alternate locale available to test');
}
});
test('305: Selecting locale changes URL prefix', 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;
}
const targetLocale = locale === 'en-us' ? 'ru-ru' : 'en-us';
await switcher.click();
const options = page.locator(tid(S.LAYOUT_LOCALE_OPTION, app));
// Find option matching target locale by data-locale or text content
let targetOption = options
.filter({ has: page.locator(`[data-locale="${targetLocale.split('-')[0]}"]`) })
.first();
if ((await targetOption.count()) === 0) {
// Try by native name
const targetName = LOCALE_NAMES[targetLocale];
targetOption = options.filter({ hasText: new RegExp(targetName) }).first();
}
if ((await targetOption.count()) > 0) {
await targetOption.click();
await page.waitForLoadState('networkidle');
// URL should contain new locale prefix
await expect(page).toHaveURL(new RegExp(`/${targetLocale}/`));
} else {
test.skip(true, 'Target locale option not found');
}
});
test('306: Page content reloads after locale change', 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;
}
// Get initial content
const initialLocale = locale;
const targetLocale = initialLocale === 'en-us' ? 'ru-ru' : 'en-us';
await switcher.click();
const options = page.locator(tid(S.LAYOUT_LOCALE_OPTION, app));
let targetOption = options
.filter({ has: page.locator(`[data-locale="${targetLocale.split('-')[0]}"]`) })
.first();
if ((await targetOption.count()) === 0) {
const targetName = LOCALE_NAMES[targetLocale];
targetOption = options.filter({ hasText: new RegExp(targetName) }).first();
}
if ((await targetOption.count()) > 0) {
await targetOption.click();
await page.waitForLoadState('networkidle');
const contentAfter = await page.locator('body').textContent();
// Content should have changed (some text different between locales)
// This is a loose check - exact comparison may be fragile
expect(contentAfter).toBeTruthy();
} else {
test.skip(true, 'Target locale option not found');
}
});
test('307: Selected locale is highlighted in dropdown', 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 options = page.locator(tid(S.LAYOUT_LOCALE_OPTION, app));
// Find current locale option
let currentOption = options
.filter({ has: page.locator(`[data-locale="${locale.split('-')[0]}"]`) })
.first();
if ((await currentOption.count()) === 0) {
const localeName = LOCALE_NAMES[locale];
currentOption = options.filter({ hasText: new RegExp(localeName) }).first();
}
if ((await currentOption.count()) > 0) {
// Current option should have active class or aria-selected
const hasActiveClass = await currentOption.evaluate(
(el) => el.className.includes('active') || el.className.includes('selected'),
);
const isAriaSelected = await currentOption.getAttribute('aria-selected');
expect(hasActiveClass || isAriaSelected === 'true').toBe(true);
} else {
test.skip(true, 'Current locale option not found');
}
});
// ====================================================================
// SECTION 4: Content Translation Verification (Tests 308-312)
// ====================================================================
test('308: Page titles translate after locale change', 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;
}
const targetLocale = locale === 'en-us' ? 'ru-ru' : 'en-us';
const targetLocaleCode = targetLocale.split('-')[0];
await switcher.click();
const options = page.locator(tid(S.LAYOUT_LOCALE_OPTION, app));
let targetOption = options
.filter({ has: page.locator(`[data-locale="${targetLocaleCode}"]`) })
.first();
if ((await targetOption.count()) === 0) {
const targetName = LOCALE_NAMES[targetLocale];
targetOption = options.filter({ hasText: new RegExp(targetName) }).first();
}
if ((await targetOption.count()) > 0) {
await targetOption.click();
await page.waitForLoadState('networkidle');
// Verify page title or h1 contains translated content
const pageTitle = await page.title();
const h1 = await page.locator('h1').first().textContent();
const hasContent = pageTitle || h1;
expect(hasContent).toBeTruthy();
} else {
test.skip(true, 'Target locale option not found');
}
});
test('309: Button labels translate after locale change', 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;
}
const targetLocale = locale === 'en-us' ? 'ru-ru' : 'en-us';
const targetLocaleCode = targetLocale.split('-')[0];
await switcher.click();
const options = page.locator(tid(S.LAYOUT_LOCALE_OPTION, app));
let targetOption = options
.filter({ has: page.locator(`[data-locale="${targetLocaleCode}"]`) })
.first();
if ((await targetOption.count()) === 0) {
const targetName = LOCALE_NAMES[targetLocale];
targetOption = options.filter({ hasText: new RegExp(targetName) }).first();
}
if ((await targetOption.count()) > 0) {
await targetOption.click();
await page.waitForLoadState('networkidle');
// Get sample button text after
const buttonsAfter = await page.locator('button').first().textContent();
// At least some button text should exist and be localized
expect(buttonsAfter).toBeTruthy();
} else {
test.skip(true, 'Target locale option not found');
}
});
test('310: Placeholder text translates after locale change', 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;
}
const targetLocale = locale === 'en-us' ? 'ru-ru' : 'en-us';
const targetLocaleCode = targetLocale.split('-')[0];
await switcher.click();
const options = page.locator(tid(S.LAYOUT_LOCALE_OPTION, app));
let targetOption = options
.filter({ has: page.locator(`[data-locale="${targetLocaleCode}"]`) })
.first();
if ((await targetOption.count()) === 0) {
const targetName = LOCALE_NAMES[targetLocale];
targetOption = options.filter({ hasText: new RegExp(targetName) }).first();
}
if ((await targetOption.count()) > 0) {
await targetOption.click();
await page.waitForLoadState('networkidle');
// Look for input with placeholder
const inputWithPlaceholder = page.locator('input[placeholder]').first();
const placeholder = await inputWithPlaceholder.getAttribute('placeholder');
// Just verify placeholders exist and are not empty
if (placeholder) {
expect(placeholder.length).toBeGreaterThan(0);
}
} else {
test.skip(true, 'Target locale option not found');
}
});
test('311: Tab names translate after locale change', 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;
}
const targetLocale = locale === 'en-us' ? 'ru-ru' : 'en-us';
const targetLocaleCode = targetLocale.split('-')[0];
await switcher.click();
const options = page.locator(tid(S.LAYOUT_LOCALE_OPTION, app));
let targetOption = options
.filter({ has: page.locator(`[data-locale="${targetLocaleCode}"]`) })
.first();
if ((await targetOption.count()) === 0) {
const targetName = LOCALE_NAMES[targetLocale];
targetOption = options.filter({ hasText: new RegExp(targetName) }).first();
}
if ((await targetOption.count()) > 0) {
await targetOption.click();
await page.waitForLoadState('networkidle');
// Check for navigation tabs
const navTabs = page.locator('[role="tab"], [data-testid*="tab"]').first();
const tabText = await navTabs.textContent();
// Navigation should have tab labels
expect(tabText).toBeTruthy();
} else {
test.skip(true, 'Target locale option not found');
}
});
test('312: Error messages translate after locale change', 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;
}
const targetLocale = locale === 'en-us' ? 'ru-ru' : 'en-us';
const targetLocaleCode = targetLocale.split('-')[0];
await switcher.click();
const options = page.locator(tid(S.LAYOUT_LOCALE_OPTION, app));
let targetOption = options
.filter({ has: page.locator(`[data-locale="${targetLocaleCode}"]`) })
.first();
if ((await targetOption.count()) === 0) {
const targetName = LOCALE_NAMES[targetLocale];
targetOption = options.filter({ hasText: new RegExp(targetName) }).first();
}
if ((await targetOption.count()) > 0) {
await targetOption.click();
await page.waitForLoadState('networkidle');
// Look for error message or alert if present
const errorMsg = page.locator('[role="alert"], .error, [data-testid*="error"]').first();
const errorText = await errorMsg.textContent().catch(() => '');
// Error messages should be translatable (just verify they exist or are properly structured)
expect(typeof errorText).toBe('string');
} else {
test.skip(true, 'Target locale option not found');
}
});
// ====================================================================
// SECTION 5: Locale Persistence (Tests 313-315)
// ====================================================================
test('313: Selected locale persists on page reload', 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;
}
const targetLocale = locale === 'en-us' ? 'ru-ru' : 'en-us';
const targetLocaleCode = targetLocale.split('-')[0];
// Switch locale
await switcher.click();
const options = page.locator(tid(S.LAYOUT_LOCALE_OPTION, app));
let targetOption = options
.filter({ has: page.locator(`[data-locale="${targetLocaleCode}"]`) })
.first();
if ((await targetOption.count()) === 0) {
const targetName = LOCALE_NAMES[targetLocale];
targetOption = options.filter({ hasText: new RegExp(targetName) }).first();
}
if ((await targetOption.count()) > 0) {
await targetOption.click();
await page.waitForLoadState('networkidle');
// Verify URL changed
await expect(page).toHaveURL(new RegExp(`/${targetLocale}/`));
// Reload page
await page.reload();
await page.waitForLoadState('networkidle');
// Locale should persist in URL
await expect(page).toHaveURL(new RegExp(`/${targetLocale}/`));
// Switcher should show the same locale
const localeCode = targetLocale.toUpperCase();
await expect(switcher).toContainText(localeCode);
} else {
test.skip(true, 'Target locale option not found');
}
});
test('314: Switching pages maintains current locale', 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;
}
const targetLocale = locale === 'en-us' ? 'ru-ru' : 'en-us';
const targetLocaleCode = targetLocale.split('-')[0];
// Switch locale
await switcher.click();
const options = page.locator(tid(S.LAYOUT_LOCALE_OPTION, app));
let targetOption = options
.filter({ has: page.locator(`[data-locale="${targetLocaleCode}"]`) })
.first();
if ((await targetOption.count()) === 0) {
const targetName = LOCALE_NAMES[targetLocale];
targetOption = options.filter({ hasText: new RegExp(targetName) }).first();
}
if ((await targetOption.count()) > 0) {
await targetOption.click();
await page.waitForLoadState('networkidle');
// Current URL in target locale
const currentUrl = page.url();
expect(currentUrl).toContain(`/${targetLocale}/`);
// Try to navigate to schedule (or another page)
const scheduleTab = page.locator(tid(S.NAV_SCHEDULE_TAB, app));
if ((await scheduleTab.count()) > 0) {
await scheduleTab.click();
await page.waitForLoadState('networkidle');
// Should still be in target locale
await expect(page).toHaveURL(new RegExp(`/${targetLocale}/schedule`));
}
} else {
test.skip(true, 'Target locale option not found');
}
});
test('315: Browser history preserves locale context', 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;
}
const targetLocale = locale === 'en-us' ? 'ru-ru' : 'en-us';
const targetLocaleCode = targetLocale.split('-')[0];
// Switch locale
await switcher.click();
const options = page.locator(tid(S.LAYOUT_LOCALE_OPTION, app));
let targetOption = options
.filter({ has: page.locator(`[data-locale="${targetLocaleCode}"]`) })
.first();
if ((await targetOption.count()) === 0) {
const targetName = LOCALE_NAMES[targetLocale];
targetOption = options.filter({ hasText: new RegExp(targetName) }).first();
}
if ((await targetOption.count()) > 0) {
await targetOption.click();
await page.waitForLoadState('networkidle');
const newUrl = page.url();
expect(newUrl).toContain(`/${targetLocale}/`);
// Navigate back
await page.goBack();
await page.waitForLoadState('networkidle');
// Should be on previous locale
await expect(page).toHaveURL(new RegExp(`/${locale}/`));
// Navigate forward
await page.goForward();
await page.waitForLoadState('networkidle');
// Should be back to target locale
await expect(page).toHaveURL(new RegExp(`/${targetLocale}/`));
} else {
test.skip(true, 'Target locale option not found');
}
});
});