Files
flights_web/tests/e2e-angular/schedule-results.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

671 lines
24 KiB
TypeScript

import { test, expect } from '@playwright/test';
test.describe('Schedule Results - Document 4 (US-35 to US-39)', () => {
test.beforeEach(async ({ page }) => {
// Navigate to schedule page and perform a search to get results
await page.goto('http://localhost:3000/schedule');
await page.waitForLoadState('networkidle');
// Fill in search form
const departureInput = page.locator('[data-testid="schedule-departure-input"] input');
const arrivalInput = page.locator('[data-testid="schedule-arrival-input"] input');
await departureInput.fill('SVO');
await arrivalInput.fill('AER');
// Set date and submit
const dateInputs = page.locator('[data-testid="schedule-date-input"]');
await dateInputs.first().click();
await page.waitForTimeout(500);
// Look for a search button to submit
const searchButton = page.locator('button:has-text("Search")');
if (await searchButton.isVisible()) {
await searchButton.click();
}
// Wait for results to load
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000);
});
test.describe('US-35: Schedule Results Page', () => {
test('should display results page with flight list', async ({ page }) => {
const resultsList = page.locator('[data-testid="schedule-flight-day"]');
await expect(resultsList).toBeVisible();
});
test('should display flight items with flight information', async ({ page }) => {
const flightItems = page.locator('[data-testid="schedule-flight-item"]');
const count = await flightItems.count();
expect(count).toBeGreaterThan(0);
});
test('should display flight times in each item', async ({ page }) => {
const flightItems = page.locator('[data-testid="schedule-flight-item"]');
const firstFlight = flightItems.first();
// Check for time elements (departure and arrival time)
const times = firstFlight.locator('[class*="time"]');
const timeCount = await times.count();
expect(timeCount).toBeGreaterThan(0);
});
test('should display flight numbers', async ({ page }) => {
const flightNumbers = page.locator('[class*="flightNumber"]');
const count = await flightNumbers.count();
expect(count).toBeGreaterThan(0);
});
test('should display aircraft information', async ({ page }) => {
const aircraftElements = page.locator('[class*="flightAircraft"]');
const count = await aircraftElements.count();
expect(count).toBeGreaterThan(0);
});
test('should display prices for flights', async ({ page }) => {
const priceElements = page.locator('[class*="flightPrice"]');
const count = await priceElements.count();
expect(count).toBeGreaterThan(0);
});
test('should be responsive on mobile viewport', async ({ page }) => {
await page.setViewportSize({ width: 375, height: 667 });
const resultsList = page.locator('[data-testid="schedule-flight-day"]');
await expect(resultsList).toBeVisible();
});
test('should be responsive on tablet viewport', async ({ page }) => {
await page.setViewportSize({ width: 768, height: 1024 });
const resultsList = page.locator('[data-testid="schedule-flight-day"]');
await expect(resultsList).toBeVisible();
});
});
test.describe('US-36: Switch Between Days', () => {
test('should display previous week button', async ({ page }) => {
const prevButton = page.locator('[data-testid="schedule-week-prev"]');
await expect(prevButton).toBeVisible();
});
test('should display next week button', async ({ page }) => {
const nextButton = page.locator('[data-testid="schedule-week-next"]');
await expect(nextButton).toBeVisible();
});
test('should have previous/next buttons with proper accessibility', async ({ page }) => {
const prevButton = page.locator('[data-testid="schedule-week-prev"]');
const nextButton = page.locator('[data-testid="schedule-week-next"]');
const prevLabel = await prevButton.getAttribute('aria-label');
const nextLabel = await nextButton.getAttribute('aria-label');
expect(prevLabel).toBeTruthy();
expect(nextLabel).toBeTruthy();
});
test('should respond to day tab clicks', async ({ page }) => {
const dayTabs = page.locator('[data-testid="schedule-week-tab"]');
const count = await dayTabs.count();
expect(count).toBeGreaterThan(0);
// Click a different day
if (count > 1) {
await dayTabs.nth(1).click();
await page.waitForLoadState('networkidle');
// Verify the clicked tab is now active
const activeTab = page.locator('[data-testid="schedule-week-tab"][aria-selected="true"]');
await expect(activeTab).toBeVisible();
}
});
});
test.describe('US-37: Week Navigation Tabs', () => {
test('should display week tabs (7 days)', async ({ page }) => {
const tabs = page.locator('[data-testid="schedule-week-tab"]');
const count = await tabs.count();
expect(count).toBe(7);
});
test('should display day names in tabs', async ({ page }) => {
const tabs = page.locator('[data-testid="schedule-week-tab"]');
const firstTab = tabs.first();
const dayName = firstTab.locator('[class*="dayName"]');
await expect(dayName).toBeVisible();
});
test('should display dates in tabs', async ({ page }) => {
const tabs = page.locator('[data-testid="schedule-week-tab"]');
const firstTab = tabs.first();
const dayDate = firstTab.locator('[class*="dayDate"]');
await expect(dayDate).toBeVisible();
});
test('should highlight the active day tab', async ({ page }) => {
const activeTab = page.locator('[data-testid="schedule-week-tab"][aria-selected="true"]');
await expect(activeTab).toBeVisible();
// Verify it has the active class
const className = await activeTab.getAttribute('class');
expect(className).toContain('weekTabActive');
});
test('should allow navigation between weeks with prev button', async ({ page }) => {
const prevButton = page.locator('[data-testid="schedule-week-prev"]');
await prevButton.click();
await page.waitForLoadState('networkidle');
await page.waitForTimeout(500);
// Verify we're still on the schedule results page
const resultsList = page.locator('[data-testid="schedule-flight-day"]');
await expect(resultsList).toBeVisible();
});
test('should allow navigation between weeks with next button', async ({ page }) => {
const nextButton = page.locator('[data-testid="schedule-week-next"]');
await nextButton.click();
await page.waitForLoadState('networkidle');
await page.waitForTimeout(500);
// Verify we're still on the schedule results page
const resultsList = page.locator('[data-testid="schedule-flight-day"]');
await expect(resultsList).toBeVisible();
});
test('should update displayed results when changing weeks', async ({ page }) => {
const flightItems = page.locator('[data-testid="schedule-flight-item"]');
const initialCount = await flightItems.count();
// Click next week
const nextButton = page.locator('[data-testid="schedule-week-next"]');
await nextButton.click();
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000);
// Results should still be visible (may be empty or different)
const resultsList = page.locator('[data-testid="schedule-flight-day"]');
await expect(resultsList).toBeVisible();
});
});
test.describe('US-38: Flight Detail Expansion', () => {
test('should expand flight details on click', async ({ page }) => {
const flightItems = page.locator('[data-testid="schedule-flight-item"]');
if ((await flightItems.count()) > 0) {
const firstFlight = flightItems.first();
await firstFlight.click();
await page.waitForTimeout(300);
// Check if expanded class is applied
const className = await firstFlight.getAttribute('class');
expect(className).toContain('flightItemExpanded');
}
});
test('should display flight details when expanded', async ({ page }) => {
const flightItems = page.locator('[data-testid="schedule-flight-item"]');
if ((await flightItems.count()) > 0) {
const firstFlight = flightItems.first();
await firstFlight.click();
await page.waitForTimeout(300);
// Look for detail rows
const detailsRow = firstFlight.locator('[class*="detailsRow"]');
const count = await detailsRow.count();
expect(count).toBeGreaterThan(0);
}
});
test('should show duration in expanded details', async ({ page }) => {
const flightItems = page.locator('[data-testid="schedule-flight-item"]');
if ((await flightItems.count()) > 0) {
const firstFlight = flightItems.first();
await firstFlight.click();
await page.waitForTimeout(300);
// Look for duration label
const durationLabel = firstFlight.locator('text=Duration');
await expect(durationLabel).toBeVisible();
}
});
test('should show aircraft in expanded details', async ({ page }) => {
const flightItems = page.locator('[data-testid="schedule-flight-item"]');
if ((await flightItems.count()) > 0) {
const firstFlight = flightItems.first();
await firstFlight.click();
await page.waitForTimeout(300);
// Look for aircraft label
const aircraftLabel = firstFlight.locator('text=Aircraft');
await expect(aircraftLabel).toBeVisible();
}
});
test('should show price in expanded details', async ({ page }) => {
const flightItems = page.locator('[data-testid="schedule-flight-item"]');
if ((await flightItems.count()) > 0) {
const firstFlight = flightItems.first();
await firstFlight.click();
await page.waitForTimeout(300);
// Look for price label
const priceLabel = firstFlight.locator('text=Price');
await expect(priceLabel).toBeVisible();
}
});
test('should show status in expanded details', async ({ page }) => {
const flightItems = page.locator('[data-testid="schedule-flight-item"]');
if ((await flightItems.count()) > 0) {
const firstFlight = flightItems.first();
await firstFlight.click();
await page.waitForTimeout(300);
// Look for status label
const statusLabel = firstFlight.locator('text=Status');
await expect(statusLabel).toBeVisible();
}
});
test('should collapse flight when clicking again', async ({ page }) => {
const flightItems = page.locator('[data-testid="schedule-flight-item"]');
if ((await flightItems.count()) > 0) {
const firstFlight = flightItems.first();
// Expand
await firstFlight.click();
await page.waitForTimeout(300);
let className = await firstFlight.getAttribute('class');
expect(className).toContain('flightItemExpanded');
// Collapse
await firstFlight.click();
await page.waitForTimeout(300);
className = await firstFlight.getAttribute('class');
expect(className).not.toContain('flightItemExpanded');
}
});
test('should show smooth animation when expanding', async ({ page }) => {
const flightItems = page.locator('[data-testid="schedule-flight-item"]');
if ((await flightItems.count()) > 0) {
const firstFlight = flightItems.first();
const initialHeight = await firstFlight.evaluate((el) => el.offsetHeight);
await firstFlight.click();
await page.waitForTimeout(500);
const expandedHeight = await firstFlight.evaluate((el) => el.offsetHeight);
// Height should increase when expanded
expect(expandedHeight).toBeGreaterThan(initialHeight);
}
});
});
test.describe('US-39: Result Sorting', () => {
test('should display sorting menu', async ({ page }) => {
const sortingMenu = page.locator('[data-testid="schedule-sorting-menu"]');
await expect(sortingMenu).toBeVisible();
});
test('should have sort buttons', async ({ page }) => {
const sortButtons = page.locator('button[data-testid*="schedule-sort-button"]');
const count = await sortButtons.count();
expect(count).toBeGreaterThan(0);
});
test('should have one active sort button', async ({ page }) => {
const activeButtons = page.locator('button[aria-pressed="true"]');
const count = await activeButtons.count();
expect(count).toBeGreaterThanOrEqual(1);
});
test('should allow switching sort modes', async ({ page }) => {
const sortButtons = page.locator('button[data-testid*="schedule-sort-button"]');
const count = await sortButtons.count();
if (count > 1) {
const initialActive = page.locator('button[aria-pressed="true"]');
const initialId = await initialActive.first().getAttribute('data-testid');
// Click a different sort button
const secondButton = sortButtons.nth(1);
await secondButton.click();
await page.waitForLoadState('networkidle');
await page.waitForTimeout(500);
// Verify the active button changed
const newActive = page.locator('button[aria-pressed="true"]');
const newId = await newActive.first().getAttribute('data-testid');
expect(newId).not.toBe(initialId);
}
});
test('should re-sort flights when sort option changes', async ({ page }) => {
const flightItems = page.locator('[data-testid="schedule-flight-item"]');
if ((await flightItems.count()) >= 2) {
// Get initial order
const initialFirstFlightTime = await flightItems
.first()
.locator('[class*="time"]')
.first()
.textContent();
// Click sort button
const sortButtons = page.locator('button[data-testid*="schedule-sort-button"]');
const count = await sortButtons.count();
if (count > 1) {
await sortButtons.nth(1).click();
await page.waitForLoadState('networkidle');
await page.waitForTimeout(500);
// Verify flights are still displayed
const updatedFlightItems = page.locator('[data-testid="schedule-flight-item"]');
const updatedCount = await updatedFlightItems.count();
expect(updatedCount).toBeGreaterThan(0);
}
}
});
test('should highlight active sort option', async ({ page }) => {
const activeButton = page.locator('button[aria-pressed="true"]');
const severity = await activeButton.first().getAttribute('severity');
// Active button should have 'info' severity (or similar highlighting)
expect(severity).toBeTruthy();
});
test('should have accessible sort controls', async ({ page }) => {
const sortButtons = page.locator('button[data-testid*="schedule-sort-button"]');
const count = await sortButtons.count();
for (let i = 0; i < Math.min(count, 3); i++) {
const button = sortButtons.nth(i);
const ariaPressed = await button.getAttribute('aria-pressed');
expect(ariaPressed).toBeTruthy();
}
});
test('should persist sort selection during interaction', async ({ page }) => {
const sortButtons = page.locator('button[data-testid*="schedule-sort-button"]');
const count = await sortButtons.count();
if (count > 1) {
// Select a sort mode
const secondButton = sortButtons.nth(1);
await secondButton.click();
await page.waitForLoadState('networkidle');
await page.waitForTimeout(500);
// Expand a flight
const flightItems = page.locator('[data-testid="schedule-flight-item"]');
if ((await flightItems.count()) > 0) {
await flightItems.first().click();
await page.waitForTimeout(300);
// Verify sort is still active
const activeButton = page.locator('button[aria-pressed="true"]');
const activeId = await activeButton.first().getAttribute('data-testid');
expect(activeId).toBeTruthy();
}
}
});
});
test.describe('Round Trip Support (US-36 Integration)', () => {
test('should show direction switch for round trip', async ({ page }) => {
// Check if direction switch exists (may not exist for one-way flights)
const directionSwitch = page.locator('[data-testid="direction-switch"]');
const exists = await directionSwitch.isVisible().catch(() => false);
// If it exists, it should be visible
if (exists) {
await expect(directionSwitch).toBeVisible();
}
});
test('should allow switching between outbound and inbound', async ({ page }) => {
const directionSwitch = page.locator('[data-testid="direction-switch"]');
const exists = await directionSwitch.isVisible().catch(() => false);
if (exists) {
const inboundButton = page.locator('[data-testid="direction-inbound"]');
if (await inboundButton.isVisible()) {
await inboundButton.click();
await page.waitForLoadState('networkidle');
await page.waitForTimeout(500);
// Verify results are still displayed
const resultsList = page.locator('[data-testid="schedule-flight-day"]');
await expect(resultsList).toBeVisible();
}
}
});
});
test.describe('Accessibility', () => {
test('should have proper ARIA labels on navigation buttons', async ({ page }) => {
const prevButton = page.locator('[data-testid="schedule-week-prev"]');
const nextButton = page.locator('[data-testid="schedule-week-next"]');
const prevLabel = await prevButton.getAttribute('aria-label');
const nextLabel = await nextButton.getAttribute('aria-label');
expect(prevLabel).toBeTruthy();
expect(nextLabel).toBeTruthy();
});
test('should have proper ARIA attributes on tabs', async ({ page }) => {
const tabs = page.locator('[data-testid="schedule-week-tab"]');
if ((await tabs.count()) > 0) {
const firstTab = tabs.first();
const ariaSelected = await firstTab.getAttribute('aria-selected');
expect(ariaSelected).toBeTruthy();
}
});
test('should have proper ARIA attributes on sort buttons', async ({ page }) => {
const sortButtons = page.locator('button[data-testid*="schedule-sort-button"]');
if ((await sortButtons.count()) > 0) {
const firstButton = sortButtons.first();
const ariaPressed = await firstButton.getAttribute('aria-pressed');
expect(ariaPressed).toBeTruthy();
}
});
test('should maintain keyboard navigation', async ({ page }) => {
const prevButton = page.locator('[data-testid="schedule-week-prev"]');
await prevButton.focus();
// Button should be focused
const focused = await page.evaluate(() =>
document.activeElement?.getAttribute('data-testid'),
);
expect(focused).toBe('schedule-week-prev');
});
});
test.describe('Localization (ru-ru)', () => {
test('should display results in Russian locale', async ({ page }) => {
// Check for Russian text (common words in schedule)
const pageContent = await page.textContent('body');
expect(pageContent).toBeTruthy();
});
test('should use Russian date format', async ({ page }) => {
const tabs = page.locator('[data-testid="schedule-week-tab"]');
if ((await tabs.count()) > 0) {
const tabText = await tabs.first().textContent();
// Russian day names and date format
expect(tabText).toBeTruthy();
}
});
});
test.describe('Localization (en-us)', () => {
test('should display results in English locale', async ({ page, context }) => {
// Set English locale
await context.addInitScript(() => {
localStorage.setItem('preferredLocale', 'en-us');
});
// Navigate to schedule
await page.goto('http://localhost:3000/schedule?locale=en-us');
await page.waitForLoadState('networkidle');
// Perform search
const departureInput = page.locator('[data-testid="schedule-departure-input"] input');
const arrivalInput = page.locator('[data-testid="schedule-arrival-input"] input');
await departureInput.fill('SVO');
await arrivalInput.fill('AER');
const searchButton = page.locator('button:has-text("Search")');
if (await searchButton.isVisible()) {
await searchButton.click();
}
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000);
// Verify results are displayed
const resultsList = page.locator('[data-testid="schedule-flight-day"]');
await expect(resultsList).toBeVisible();
});
});
test.describe('Error Handling', () => {
test('should display empty state when no flights found', async ({ page }) => {
// Try searching for an impossible route
await page.goto('http://localhost:3000/schedule');
await page.waitForLoadState('networkidle');
const departureInput = page.locator('[data-testid="schedule-departure-input"] input');
const arrivalInput = page.locator('[data-testid="schedule-arrival-input"] input');
// Use unlikely city codes
await departureInput.fill('AAA');
await arrivalInput.fill('ZZZ');
const searchButton = page.locator('button:has-text("Search")');
if (await searchButton.isVisible()) {
await searchButton.click();
}
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000);
// Should show either empty state or error message
const emptyState = page.locator('[data-testid="schedule-empty-list"]');
const resultsList = page.locator('[data-testid="schedule-flight-day"]');
const hasEmptyState = await emptyState.isVisible().catch(() => false);
const hasResults = await resultsList.isVisible().catch(() => false);
expect(hasEmptyState || hasResults).toBeTruthy();
});
});
});
test.describe('Console Audit - Schedule Results', () => {
test('should have no console errors on results page', async ({ page }) => {
const errors: string[] = [];
page.on('console', (message) => {
if (message.type() === 'error') {
errors.push(message.text());
}
});
await page.goto('http://localhost:3000/schedule');
await page.waitForLoadState('networkidle');
// Perform search
const departureInput = page.locator('[data-testid="schedule-departure-input"] input');
const arrivalInput = page.locator('[data-testid="schedule-arrival-input"] input');
await departureInput.fill('SVO');
await arrivalInput.fill('AER');
const searchButton = page.locator('button:has-text("Search")');
if (await searchButton.isVisible()) {
await searchButton.click();
}
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000);
// Interact with results
const flightItems = page.locator('[data-testid="schedule-flight-item"]');
if ((await flightItems.count()) > 0) {
await flightItems.first().click();
await page.waitForTimeout(300);
}
// Check for errors (excluding known non-critical warnings)
const criticalErrors = errors.filter(
(e) =>
!e.includes('hydration') &&
!e.includes('useLayoutEffect') &&
!e.includes('act()') &&
!e.includes('warning') &&
e.length > 0,
);
expect(criticalErrors).toHaveLength(0);
});
test('should have no accessibility violations', async ({ page }) => {
await page.goto('http://localhost:3000/schedule');
await page.waitForLoadState('networkidle');
// Perform search
const departureInput = page.locator('[data-testid="schedule-departure-input"] input');
const arrivalInput = page.locator('[data-testid="schedule-arrival-input"] input');
await departureInput.fill('SVO');
await arrivalInput.fill('AER');
const searchButton = page.locator('button:has-text("Search")');
if (await searchButton.isVisible()) {
await searchButton.click();
}
await page.waitForLoadState('networkidle');
await page.waitForTimeout(1000);
// Check that all interactive elements are keyboard accessible
const buttons = page.locator('button');
const count = await buttons.count();
for (let i = 0; i < Math.min(count, 5); i++) {
const button = buttons.nth(i);
await button.focus();
const focused = await page.evaluate(() => {
const el = document.activeElement as HTMLElement;
return el.tagName === 'BUTTON';
});
expect(focused).toBeTruthy();
}
});
});