375bcfb0fa
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.
909 lines
34 KiB
TypeScript
909 lines
34 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
|
|
|
const BASE_URL = process.env.BASE_URL || 'http://localhost:5173';
|
|
|
|
test.describe('Flights Map (US-65 to US-69)', () => {
|
|
test.describe('US-65: Flights Map Tab Navigation', () => {
|
|
test('should navigate to flights map page from main board', async ({ page }) => {
|
|
// Navigate to main board
|
|
await page.goto(`${BASE_URL}/ru-ru/onlineboard`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// Look for flights map tab (third tab)
|
|
const flightsMapTab = page.locator('[data-testid="flights-map-tab"]');
|
|
if (await flightsMapTab.isVisible()) {
|
|
await flightsMapTab.click();
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// Verify URL changed to flights map
|
|
expect(page.url()).toContain('flights-map');
|
|
|
|
// Verify page title
|
|
const title = page.locator('h1');
|
|
await expect(title).toContainText(/map|карт/i);
|
|
}
|
|
});
|
|
|
|
test('should display map container on flights map page', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// Check for map container
|
|
const mapContainer = page.locator('[data-testid="map-container"]');
|
|
await expect(mapContainer).toBeVisible();
|
|
});
|
|
|
|
test('should display filter panel', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// Check for filter panel
|
|
const filterPanel = page.locator('[data-testid="flights-map-filter"]');
|
|
await expect(filterPanel).toBeVisible();
|
|
});
|
|
|
|
test('should show loading state initially', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
|
|
|
|
// Page should load and display map
|
|
const mapContainer = page.locator('[data-testid="map-container"]');
|
|
await expect(mapContainer).toBeVisible({ timeout: 10000 });
|
|
});
|
|
|
|
test('should have tab for flights map in navigation', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/ru-ru/onlineboard`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// Check if flights map tab exists in navigation
|
|
const tabNavigation = page.locator('[role="tablist"]');
|
|
if (await tabNavigation.isVisible()) {
|
|
const tabs = tabNavigation.locator('[role="tab"]');
|
|
const tabCount = await tabs.count();
|
|
expect(tabCount).toBeGreaterThanOrEqual(2); // At least Online Board and Schedule
|
|
}
|
|
});
|
|
|
|
test('should render page without console errors', async ({ page }) => {
|
|
const errors: string[] = [];
|
|
page.on('console', (msg) => {
|
|
if (msg.type() === 'error') {
|
|
errors.push(msg.text());
|
|
}
|
|
});
|
|
|
|
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
expect(errors.filter((e) => !e.includes('sourcemap'))).toEqual([]);
|
|
});
|
|
});
|
|
|
|
test.describe('US-66: Route Display on Map', () => {
|
|
test('should display routes after selecting departure city', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// Get departure input
|
|
const departureInput = page.locator('[data-testid="map-departure-input"]');
|
|
if (await departureInput.isVisible()) {
|
|
// Type departure city
|
|
await departureInput.fill('Moscow');
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// Select first suggestion
|
|
const suggestions = page.locator('[data-testid="city-suggestion"]');
|
|
if ((await suggestions.count()) > 0) {
|
|
await suggestions.first().click();
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// Verify map container still visible
|
|
const mapContainer = page.locator('[data-testid="map-container"]');
|
|
await expect(mapContainer).toBeVisible();
|
|
}
|
|
}
|
|
});
|
|
|
|
test('should render polylines for routes', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// Check for map svg (Leaflet renders routes as SVG)
|
|
const svgElements = page.locator('svg');
|
|
const svgCount = await svgElements.count();
|
|
expect(svgCount).toBeGreaterThan(0);
|
|
});
|
|
|
|
test('should apply color to routes', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// Check for colored elements (polylines)
|
|
const svgPaths = page.locator('svg path');
|
|
const pathCount = await svgPaths.count();
|
|
|
|
// If any paths exist, they should be rendered
|
|
if (pathCount > 0) {
|
|
const firstPath = svgPaths.first();
|
|
const stroke = await firstPath.evaluate((el) => window.getComputedStyle(el).stroke);
|
|
expect(stroke).toBeTruthy();
|
|
}
|
|
});
|
|
|
|
test('should handle multiple routes', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
const departureInput = page.locator('[data-testid="map-departure-input"]');
|
|
if (await departureInput.isVisible()) {
|
|
await departureInput.fill('Moscow');
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// Wait for suggestions
|
|
const suggestions = page.locator('[data-testid="city-suggestion"]');
|
|
if ((await suggestions.count()) > 0) {
|
|
await suggestions.first().click();
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// Map should display multiple routes
|
|
const mapContainer = page.locator('[data-testid="map-container"]');
|
|
await expect(mapContainer).toBeVisible();
|
|
}
|
|
}
|
|
});
|
|
|
|
test('should update routes when filters change', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
const mapContainer = page.locator('[data-testid="map-container"]');
|
|
await expect(mapContainer).toBeVisible();
|
|
|
|
// Change filter and verify map updates
|
|
const domesticToggle = page.locator('[data-testid="map-domestic-toggle"]');
|
|
if (await domesticToggle.isVisible()) {
|
|
await domesticToggle.click();
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// Map should still be visible after filter change
|
|
await expect(mapContainer).toBeVisible();
|
|
}
|
|
});
|
|
});
|
|
|
|
test.describe('US-67: Departure City Selection', () => {
|
|
test('should render departure city input', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
const departureInput = page.locator('[data-testid="map-departure-input"]');
|
|
await expect(departureInput).toBeVisible();
|
|
});
|
|
|
|
test('should show suggestions when typing in departure input', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
const departureInput = page.locator('[data-testid="map-departure-input"]');
|
|
await departureInput.fill('Mos');
|
|
await page.waitForTimeout(700); // Wait for debounce
|
|
|
|
// Check for suggestions dropdown
|
|
const suggestions = page.locator('[data-testid="city-suggestion"]');
|
|
const count = await suggestions.count();
|
|
expect(count).toBeGreaterThan(0);
|
|
});
|
|
|
|
test('should filter suggestions by city name', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
const departureInput = page.locator('[data-testid="map-departure-input"]');
|
|
await departureInput.fill('Moscow');
|
|
await page.waitForTimeout(700);
|
|
|
|
const suggestions = page.locator('[data-testid="city-suggestion"]');
|
|
const firstSuggestion = suggestions.first();
|
|
const text = await firstSuggestion.textContent();
|
|
expect(text?.toLowerCase()).toContain('moscow');
|
|
});
|
|
|
|
test('should support city code input', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
const departureInput = page.locator('[data-testid="map-departure-input"]');
|
|
await departureInput.fill('MOW');
|
|
await page.waitForTimeout(700);
|
|
|
|
const suggestions = page.locator('[data-testid="city-suggestion"]');
|
|
const count = await suggestions.count();
|
|
expect(count).toBeGreaterThan(0);
|
|
});
|
|
|
|
test('should select departure city from suggestions', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
const departureInput = page.locator('[data-testid="map-departure-input"]');
|
|
await departureInput.fill('Moscow');
|
|
await page.waitForTimeout(700);
|
|
|
|
const suggestions = page.locator('[data-testid="city-suggestion"]');
|
|
if ((await suggestions.count()) > 0) {
|
|
await suggestions.first().click();
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// Input should contain selected city
|
|
const inputValue = await departureInput.inputValue();
|
|
expect(inputValue.length).toBeGreaterThan(0);
|
|
}
|
|
});
|
|
|
|
test('should require departure city', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
const departureInput = page.locator('[data-testid="map-departure-input"]');
|
|
const required = await departureInput.evaluate((el: HTMLInputElement) => el.required);
|
|
expect(required).toBe(true);
|
|
});
|
|
});
|
|
|
|
test.describe('US-68: Arrival City Selection', () => {
|
|
test('should render arrival city input', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
const arrivalInput = page.locator('[data-testid="map-arrival-input"]');
|
|
await expect(arrivalInput).toBeVisible();
|
|
});
|
|
|
|
test('should show suggestions when typing in arrival input', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// First select departure city
|
|
const departureInput = page.locator('[data-testid="map-departure-input"]');
|
|
await departureInput.fill('Moscow');
|
|
await page.waitForTimeout(700);
|
|
|
|
const depSuggestions = page.locator('[data-testid="city-suggestion"]');
|
|
if ((await depSuggestions.count()) > 0) {
|
|
await depSuggestions.first().click();
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// Now type in arrival input
|
|
const arrivalInput = page.locator('[data-testid="map-arrival-input"]');
|
|
await arrivalInput.fill('Par');
|
|
await page.waitForTimeout(700);
|
|
|
|
const arrivalSuggestions = page.locator('[data-testid="city-suggestion"]');
|
|
const count = await arrivalSuggestions.count();
|
|
expect(count).toBeGreaterThan(0);
|
|
}
|
|
});
|
|
|
|
test('should filter suggestions by arrival city name', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
const departureInput = page.locator('[data-testid="map-departure-input"]');
|
|
await departureInput.fill('Moscow');
|
|
await page.waitForTimeout(700);
|
|
|
|
const suggestions = page.locator('[data-testid="city-suggestion"]');
|
|
if ((await suggestions.count()) > 0) {
|
|
await suggestions.first().click();
|
|
|
|
const arrivalInput = page.locator('[data-testid="map-arrival-input"]');
|
|
await arrivalInput.fill('Paris');
|
|
await page.waitForTimeout(700);
|
|
|
|
const arrivalSuggestions = page.locator('[data-testid="city-suggestion"]');
|
|
if ((await arrivalSuggestions.count()) > 0) {
|
|
const text = await arrivalSuggestions.first().textContent();
|
|
expect(text?.toLowerCase()).toContain('par');
|
|
}
|
|
}
|
|
});
|
|
|
|
test('should support arrival city code', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
const departureInput = page.locator('[data-testid="map-departure-input"]');
|
|
await departureInput.fill('Moscow');
|
|
await page.waitForTimeout(700);
|
|
|
|
const suggestions = page.locator('[data-testid="city-suggestion"]');
|
|
if ((await suggestions.count()) > 0) {
|
|
await suggestions.first().click();
|
|
|
|
const arrivalInput = page.locator('[data-testid="map-arrival-input"]');
|
|
await arrivalInput.fill('CDG');
|
|
await page.waitForTimeout(700);
|
|
|
|
const arrivalSuggestions = page.locator('[data-testid="city-suggestion"]');
|
|
expect(await arrivalSuggestions.count()).toBeGreaterThan(0);
|
|
}
|
|
});
|
|
|
|
test('should select arrival city and display route', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
const departureInput = page.locator('[data-testid="map-departure-input"]');
|
|
await departureInput.fill('Moscow');
|
|
await page.waitForTimeout(700);
|
|
|
|
const depSuggestions = page.locator('[data-testid="city-suggestion"]');
|
|
if ((await depSuggestions.count()) > 0) {
|
|
await depSuggestions.first().click();
|
|
|
|
const arrivalInput = page.locator('[data-testid="map-arrival-input"]');
|
|
await arrivalInput.fill('Paris');
|
|
await page.waitForTimeout(700);
|
|
|
|
const arrivalSuggestions = page.locator('[data-testid="city-suggestion"]');
|
|
if ((await arrivalSuggestions.count()) > 0) {
|
|
await arrivalSuggestions.first().click();
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
const mapContainer = page.locator('[data-testid="map-container"]');
|
|
await expect(mapContainer).toBeVisible();
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
test.describe('US-69: Swap Cities Button', () => {
|
|
test('should display swap button between city inputs', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
const swapButton = page.locator('[data-testid="swap-button"]');
|
|
await expect(swapButton).toBeVisible();
|
|
});
|
|
|
|
test('should swap cities when button is clicked', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
const departureInput = page.locator('[data-testid="map-departure-input"]');
|
|
const arrivalInput = page.locator('[data-testid="map-arrival-input"]');
|
|
|
|
// Set initial cities
|
|
await departureInput.fill('Moscow');
|
|
await page.waitForTimeout(700);
|
|
|
|
const depSuggestions = page.locator('[data-testid="city-suggestion"]');
|
|
if ((await depSuggestions.count()) > 0) {
|
|
await depSuggestions.first().click();
|
|
|
|
await arrivalInput.fill('Paris');
|
|
await page.waitForTimeout(700);
|
|
|
|
const arrivalSuggestions = page.locator('[data-testid="city-suggestion"]');
|
|
if ((await arrivalSuggestions.count()) > 0) {
|
|
await arrivalSuggestions.first().click();
|
|
|
|
// Click swap button
|
|
const swapButton = page.locator('[data-testid="swap-button"]');
|
|
await swapButton.click();
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
const depAfter = await departureInput.inputValue();
|
|
const arrAfter = await arrivalInput.inputValue();
|
|
|
|
// Cities should be swapped (approximately)
|
|
expect(depAfter.length).toBeGreaterThan(0);
|
|
expect(arrAfter.length).toBeGreaterThan(0);
|
|
}
|
|
}
|
|
});
|
|
|
|
test('should be keyboard accessible', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
const swapButton = page.locator('[data-testid="swap-button"]');
|
|
|
|
// Focus on button
|
|
await swapButton.focus();
|
|
|
|
// Press Enter
|
|
await page.keyboard.press('Enter');
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// Page should still be functional
|
|
const mapContainer = page.locator('[data-testid="map-container"]');
|
|
await expect(mapContainer).toBeVisible();
|
|
});
|
|
|
|
test('should be mobile-friendly size', async ({ page }) => {
|
|
await page.setViewportSize({ width: 375, height: 667 });
|
|
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
const swapButton = page.locator('[data-testid="swap-button"]');
|
|
await expect(swapButton).toBeVisible();
|
|
|
|
// Get button size
|
|
const box = await swapButton.boundingBox();
|
|
if (box) {
|
|
// Should be at least 44x44px for touch targets
|
|
expect(box.width).toBeGreaterThanOrEqual(40);
|
|
expect(box.height).toBeGreaterThanOrEqual(40);
|
|
}
|
|
});
|
|
|
|
test('should have accessible label', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
const swapButton = page.locator('[data-testid="swap-button"]');
|
|
const ariaLabel = await swapButton.getAttribute('aria-label');
|
|
expect(ariaLabel).toBeTruthy();
|
|
});
|
|
});
|
|
|
|
test.describe('US-65-69: Responsive Design', () => {
|
|
test('should be responsive on mobile (375px)', async ({ page }) => {
|
|
await page.setViewportSize({ width: 375, height: 667 });
|
|
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
const mapContainer = page.locator('[data-testid="map-container"]');
|
|
await expect(mapContainer).toBeVisible();
|
|
|
|
const filterPanel = page.locator('[data-testid="flights-map-filter"]');
|
|
await expect(filterPanel).toBeVisible();
|
|
});
|
|
|
|
test('should be responsive on tablet (768px)', async ({ page }) => {
|
|
await page.setViewportSize({ width: 768, height: 1024 });
|
|
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
const mapContainer = page.locator('[data-testid="map-container"]');
|
|
await expect(mapContainer).toBeVisible();
|
|
});
|
|
|
|
test('should be responsive on desktop (1440px)', async ({ page }) => {
|
|
await page.setViewportSize({ width: 1440, height: 900 });
|
|
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
const mapContainer = page.locator('[data-testid="map-container"]');
|
|
await expect(mapContainer).toBeVisible();
|
|
});
|
|
});
|
|
|
|
test.describe('US-65-69: Localization', () => {
|
|
test('should work in Russian locale (ru-ru)', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
const mapContainer = page.locator('[data-testid="map-container"]');
|
|
await expect(mapContainer).toBeVisible();
|
|
});
|
|
|
|
test('should work in English locale (en-us)', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/en-us/flights-map`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
const mapContainer = page.locator('[data-testid="map-container"]');
|
|
await expect(mapContainer).toBeVisible();
|
|
});
|
|
});
|
|
|
|
test.describe('US-65-69: Accessibility', () => {
|
|
test('should render without accessibility violations', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// Check for proper heading hierarchy
|
|
const heading = page.locator('h1');
|
|
await expect(heading).toBeVisible();
|
|
});
|
|
|
|
test('should be keyboard navigable', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// Tab through interactive elements
|
|
await page.keyboard.press('Tab');
|
|
let focusedElement = await page.evaluate(() => document.activeElement?.tagName);
|
|
expect(['INPUT', 'BUTTON', 'A', 'SELECT']).toContain(focusedElement);
|
|
|
|
await page.keyboard.press('Tab');
|
|
focusedElement = await page.evaluate(() => document.activeElement?.tagName);
|
|
expect(['INPUT', 'BUTTON', 'A', 'SELECT']).toContain(focusedElement);
|
|
});
|
|
|
|
test('should have proper labels', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
const departureInput = page.locator('[data-testid="map-departure-input"]');
|
|
const hasLabel = await departureInput.evaluate(
|
|
(el: HTMLInputElement) =>
|
|
el.placeholder || el.getAttribute('aria-label') || el.parentElement?.textContent,
|
|
);
|
|
expect(hasLabel).toBeTruthy();
|
|
});
|
|
});
|
|
|
|
test.describe('US-70: Zoom Functionality', () => {
|
|
test('should display zoom controls on map', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
const zoomControl = page.locator('[data-testid="zoom-control"]');
|
|
await expect(zoomControl).toBeVisible();
|
|
|
|
const zoomInBtn = page.locator('[data-testid="zoom-in-button"]');
|
|
const zoomOutBtn = page.locator('[data-testid="zoom-out-button"]');
|
|
await expect(zoomInBtn).toBeVisible();
|
|
await expect(zoomOutBtn).toBeVisible();
|
|
});
|
|
|
|
test('should allow zooming in and out', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
const zoomLevel = page.locator('[data-testid="zoom-level-display"]');
|
|
const initialZoom = await zoomLevel.textContent();
|
|
|
|
const zoomInBtn = page.locator('[data-testid="zoom-in-button"]');
|
|
await zoomInBtn.click();
|
|
await page.waitForTimeout(200);
|
|
|
|
const newZoom = await zoomLevel.textContent();
|
|
expect(parseInt(newZoom!)).toBeGreaterThan(parseInt(initialZoom!));
|
|
});
|
|
|
|
test('should enforce minimum zoom level (3)', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
const zoomOutBtn = page.locator('[data-testid="zoom-out-button"]');
|
|
|
|
// Keep clicking zoom out until disabled
|
|
for (let i = 0; i < 5; i++) {
|
|
if (await zoomOutBtn.isDisabled()) {
|
|
break;
|
|
}
|
|
await zoomOutBtn.click();
|
|
await page.waitForTimeout(100);
|
|
}
|
|
|
|
// Button should be disabled at minimum zoom
|
|
await expect(zoomOutBtn).toBeDisabled();
|
|
});
|
|
|
|
test('should enforce maximum zoom level (6)', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
const zoomInBtn = page.locator('[data-testid="zoom-in-button"]');
|
|
|
|
// Keep clicking zoom in until disabled
|
|
for (let i = 0; i < 5; i++) {
|
|
if (await zoomInBtn.isDisabled()) {
|
|
break;
|
|
}
|
|
await zoomInBtn.click();
|
|
await page.waitForTimeout(100);
|
|
}
|
|
|
|
// Button should be disabled at maximum zoom
|
|
await expect(zoomInBtn).toBeDisabled();
|
|
});
|
|
});
|
|
|
|
test.describe('US-71: Domestic Flights Filter', () => {
|
|
test('should display domestic filter toggle', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
const domesticToggle = page.locator('[data-testid="toggle-domestic"]');
|
|
await expect(domesticToggle).toBeVisible();
|
|
});
|
|
|
|
test('should toggle domestic flights filter', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
const domesticToggle = page.locator('[data-testid="toggle-domestic"]');
|
|
const isCheckedBefore = await domesticToggle.isChecked();
|
|
|
|
await domesticToggle.click();
|
|
await page.waitForTimeout(300);
|
|
|
|
const isCheckedAfter = await domesticToggle.isChecked();
|
|
expect(isCheckedAfter).toBe(!isCheckedBefore);
|
|
});
|
|
});
|
|
|
|
test.describe('US-72: International Flights Filter', () => {
|
|
test('should display international filter toggle', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
const internationalToggle = page.locator('[data-testid="toggle-international"]');
|
|
await expect(internationalToggle).toBeVisible();
|
|
});
|
|
|
|
test('should toggle international flights filter', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
const internationalToggle = page.locator('[data-testid="toggle-international"]');
|
|
const isCheckedBefore = await internationalToggle.isChecked();
|
|
|
|
await internationalToggle.click();
|
|
await page.waitForTimeout(300);
|
|
|
|
const isCheckedAfter = await internationalToggle.isChecked();
|
|
expect(isCheckedAfter).toBe(!isCheckedBefore);
|
|
});
|
|
});
|
|
|
|
test.describe('US-73: Connecting Flights Filter', () => {
|
|
test('should display connecting filter toggle', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
const connectingToggle = page.locator('[data-testid="toggle-connecting"]');
|
|
await expect(connectingToggle).toBeVisible();
|
|
});
|
|
|
|
test('should toggle connecting flights filter', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
const connectingToggle = page.locator('[data-testid="toggle-connecting"]');
|
|
const isCheckedBefore = await connectingToggle.isChecked();
|
|
|
|
await connectingToggle.click();
|
|
await page.waitForTimeout(300);
|
|
|
|
const isCheckedAfter = await connectingToggle.isChecked();
|
|
expect(isCheckedAfter).toBe(!isCheckedBefore);
|
|
});
|
|
});
|
|
|
|
test.describe('US-74: Panning', () => {
|
|
test('should allow map panning with mouse drag', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
const mapContainer = page.locator('[data-testid="map-container"]');
|
|
const box = await mapContainer.boundingBox();
|
|
|
|
if (box) {
|
|
// Drag from center right to center left
|
|
await page.mouse.move(box.x + box.width * 0.7, box.y + box.height * 0.5);
|
|
await page.mouse.down();
|
|
await page.mouse.move(box.x + box.width * 0.3, box.y + box.height * 0.5);
|
|
await page.mouse.up();
|
|
await page.waitForTimeout(200);
|
|
|
|
// Map should still be visible (not broken by pan)
|
|
await expect(mapContainer).toBeVisible();
|
|
}
|
|
});
|
|
|
|
test('should prevent panning beyond world bounds', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
const mapContainer = page.locator('[data-testid="map-container"]');
|
|
await expect(mapContainer).toBeVisible();
|
|
|
|
// Map should maintain bounds
|
|
const pageContent = await page.content();
|
|
expect(pageContent).toContain('map-container');
|
|
});
|
|
});
|
|
|
|
test.describe('US-75: Route Popup on Selection', () => {
|
|
test('should display route popup when route is clicked', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// Search for a route first
|
|
const departureInput = page.locator('[data-testid="map-departure-input"]');
|
|
if (await departureInput.isVisible()) {
|
|
await departureInput.fill('Moscow');
|
|
await page.waitForTimeout(500);
|
|
|
|
const suggestions = page.locator('[data-testid="city-suggestion"]');
|
|
if ((await suggestions.count()) > 0) {
|
|
await suggestions.first().click();
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// Check if popup appears after searching
|
|
const popup = page.locator('[data-testid="route-popup"]');
|
|
// Popup may or may not be visible depending on implementation
|
|
// Just verify the map is still functional
|
|
const mapContainer = page.locator('[data-testid="map-container"]');
|
|
await expect(mapContainer).toBeVisible();
|
|
}
|
|
}
|
|
});
|
|
|
|
test('should display route details in popup', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
const mapContainer = page.locator('[data-testid="map-container"]');
|
|
await expect(mapContainer).toBeVisible();
|
|
|
|
// Popup structure should exist in DOM
|
|
const popup = page.locator('[data-testid="route-popup"]');
|
|
// Verify popup structure exists
|
|
if (await popup.isVisible()) {
|
|
const departure = popup.locator('[data-testid="popup-departure"]');
|
|
const arrival = popup.locator('[data-testid="popup-arrival"]');
|
|
await expect(departure).toBeTruthy();
|
|
await expect(arrival).toBeTruthy();
|
|
}
|
|
});
|
|
|
|
test('should close popup with close button', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
const popup = page.locator('[data-testid="route-popup"]');
|
|
const closeButton = page.locator('[data-testid="popup-close-button"]');
|
|
|
|
// If popup is visible, close button should work
|
|
if (await popup.isVisible()) {
|
|
await closeButton.click();
|
|
await page.waitForTimeout(200);
|
|
|
|
// Popup should be hidden
|
|
const isHidden = await popup.evaluate(
|
|
(el) => window.getComputedStyle(el).display === 'none',
|
|
);
|
|
expect(isHidden || !(await popup.isVisible())).toBeTruthy();
|
|
}
|
|
});
|
|
|
|
test('should close popup on ESC key', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
const popup = page.locator('[data-testid="route-popup"]');
|
|
|
|
if (await popup.isVisible()) {
|
|
await page.keyboard.press('Escape');
|
|
await page.waitForTimeout(200);
|
|
|
|
// Popup should be hidden
|
|
const isHidden = await popup.evaluate(
|
|
(el) => window.getComputedStyle(el).display === 'none',
|
|
);
|
|
expect(isHidden || !(await popup.isVisible())).toBeTruthy();
|
|
}
|
|
});
|
|
|
|
test('should display flight count in popup', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
const popup = page.locator('[data-testid="route-popup"]');
|
|
if (await popup.isVisible()) {
|
|
const flightCount = popup.locator('[data-testid="popup-flight-count"]');
|
|
const text = await flightCount.textContent();
|
|
// Should contain a number
|
|
expect(text).toMatch(/\d+/);
|
|
}
|
|
});
|
|
|
|
test('should maintain popup position on map', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
const popup = page.locator('[data-testid="route-popup"]');
|
|
const mapContainer = page.locator('[data-testid="map-container"]');
|
|
|
|
if (await popup.isVisible()) {
|
|
const popupBox = await popup.boundingBox();
|
|
const mapBox = await mapContainer.boundingBox();
|
|
|
|
if (popupBox && mapBox) {
|
|
// Popup should be within or near map container
|
|
expect(popupBox.x).toBeGreaterThanOrEqual(mapBox.x - 100);
|
|
expect(popupBox.y).toBeGreaterThanOrEqual(mapBox.y - 100);
|
|
}
|
|
}
|
|
});
|
|
});
|
|
|
|
test.describe('US-70-75: Integration Tests', () => {
|
|
test('should maintain zoom level when filters change', async ({ page }) => {
|
|
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
const zoomLevel = page.locator('[data-testid="zoom-level-display"]');
|
|
const initialZoom = await zoomLevel.textContent();
|
|
|
|
const domesticToggle = page.locator('[data-testid="toggle-domestic"]');
|
|
await domesticToggle.click();
|
|
await page.waitForTimeout(300);
|
|
|
|
const zoomAfterFilter = await zoomLevel.textContent();
|
|
expect(zoomAfterFilter).toBe(initialZoom);
|
|
});
|
|
|
|
test('should work across locales (ru-ru and en-us)', async ({ page }) => {
|
|
// Test ru-ru locale
|
|
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
let zoomControl = page.locator('[data-testid="zoom-control"]');
|
|
await expect(zoomControl).toBeVisible();
|
|
|
|
// Test en-us locale
|
|
await page.goto(`${BASE_URL}/en-us/flights-map`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
zoomControl = page.locator('[data-testid="zoom-control"]');
|
|
await expect(zoomControl).toBeVisible();
|
|
|
|
const filters = page.locator('[data-testid="filter-toggles"]');
|
|
await expect(filters).toBeVisible();
|
|
});
|
|
|
|
test('should render without console errors', async ({ page }) => {
|
|
const errors: string[] = [];
|
|
page.on('console', (msg) => {
|
|
if (msg.type() === 'error') {
|
|
errors.push(msg.text());
|
|
}
|
|
});
|
|
|
|
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
// Perform zoom action
|
|
const zoomInBtn = page.locator('[data-testid="zoom-in-button"]');
|
|
await zoomInBtn.click();
|
|
|
|
// Toggle a filter
|
|
const domesticToggle = page.locator('[data-testid="toggle-domestic"]');
|
|
await domesticToggle.click();
|
|
|
|
await page.waitForTimeout(300);
|
|
|
|
const filteredErrors = errors.filter((e) => !e.includes('sourcemap'));
|
|
expect(filteredErrors).toEqual([]);
|
|
});
|
|
|
|
test('should be responsive on mobile', async ({ page }) => {
|
|
await page.setViewportSize({ width: 375, height: 667 });
|
|
await page.goto(`${BASE_URL}/ru-ru/flights-map`);
|
|
await page.waitForLoadState('networkidle');
|
|
|
|
const zoomControl = page.locator('[data-testid="zoom-control"]');
|
|
const filters = page.locator('[data-testid="filter-toggles"]');
|
|
|
|
await expect(zoomControl).toBeVisible();
|
|
await expect(filters).toBeVisible();
|
|
|
|
// Verify zoom buttons work on mobile
|
|
const zoomInBtn = page.locator('[data-testid="zoom-in-button"]');
|
|
await zoomInBtn.click();
|
|
|
|
// Verify filters work on mobile
|
|
const domesticToggle = page.locator('[data-testid="toggle-domestic"]');
|
|
await domesticToggle.click();
|
|
|
|
await page.waitForTimeout(300);
|
|
});
|
|
});
|
|
});
|