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.
671 lines
24 KiB
TypeScript
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();
|
|
}
|
|
});
|
|
});
|