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.
348 lines
14 KiB
TypeScript
348 lines
14 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
|
|
|
test.describe('Schedule Search - Document 3 (US-23 to US-27)', () => {
|
|
test.beforeEach(async ({ page }) => {
|
|
await page.goto('http://localhost:3000/schedule');
|
|
await page.waitForLoadState('networkidle');
|
|
});
|
|
|
|
test.describe('US-23: Schedule Tab Navigation', () => {
|
|
test('should render schedule search form', async ({ page }) => {
|
|
const form = page.locator('[data-testid="schedule-search-form"]');
|
|
await expect(form).toBeVisible();
|
|
});
|
|
|
|
test('should render search form with proper role', async ({ page }) => {
|
|
const form = page.locator('[role="search"]');
|
|
await expect(form).toBeVisible();
|
|
});
|
|
|
|
test('should have proper ARIA label', async ({ page }) => {
|
|
const form = page.locator('[role="search"]');
|
|
const ariaLabel = await form.getAttribute('aria-label');
|
|
expect(ariaLabel).toBeTruthy();
|
|
});
|
|
});
|
|
|
|
test.describe('US-24: Departure City Input', () => {
|
|
test('should render departure city input', async ({ page }) => {
|
|
const input = page.locator('[data-testid="schedule-departure-input"]');
|
|
await expect(input).toBeVisible();
|
|
});
|
|
|
|
test('should have From label', async ({ page }) => {
|
|
const label = page.getByText('From', { exact: true });
|
|
await expect(label).toBeVisible();
|
|
});
|
|
|
|
test('should accept text input for departure city', async ({ page }) => {
|
|
const input = page.locator('[data-testid="schedule-departure-input"] input');
|
|
await input.fill('Moscow');
|
|
await expect(input).toHaveValue('Moscow');
|
|
});
|
|
|
|
test('should allow clearing departure city', async ({ page }) => {
|
|
const input = page.locator('[data-testid="schedule-departure-input"] input');
|
|
await input.fill('Moscow');
|
|
await input.clear();
|
|
await expect(input).toHaveValue('');
|
|
});
|
|
|
|
test('should support autocomplete suggestions', async ({ page }) => {
|
|
const input = page.locator('[data-testid="schedule-departure-input"] input');
|
|
await input.focus();
|
|
await input.type('Mos', { delay: 100 });
|
|
// Wait for autocomplete to potentially appear
|
|
await page.waitForTimeout(500);
|
|
expect(input).toBeVisible();
|
|
});
|
|
});
|
|
|
|
test.describe('US-25: Arrival City Input', () => {
|
|
test('should render arrival city input', async ({ page }) => {
|
|
const input = page.locator('[data-testid="schedule-arrival-input"]');
|
|
await expect(input).toBeVisible();
|
|
});
|
|
|
|
test('should have To label', async ({ page }) => {
|
|
const label = page.getByText('To', { exact: true });
|
|
await expect(label).toBeVisible();
|
|
});
|
|
|
|
test('should accept text input for arrival city', async ({ page }) => {
|
|
const input = page.locator('[data-testid="schedule-arrival-input"] input');
|
|
await input.fill('Saint Petersburg');
|
|
await expect(input).toHaveValue('Saint Petersburg');
|
|
});
|
|
|
|
test('should allow clearing arrival city', async ({ page }) => {
|
|
const input = page.locator('[data-testid="schedule-arrival-input"] input');
|
|
await input.fill('Saint Petersburg');
|
|
await input.clear();
|
|
await expect(input).toHaveValue('');
|
|
});
|
|
|
|
test('should support independent entry from departure', async ({ page }) => {
|
|
const departureInput = page.locator('[data-testid="schedule-departure-input"] input');
|
|
const arrivalInput = page.locator('[data-testid="schedule-arrival-input"] input');
|
|
|
|
await departureInput.fill('Moscow');
|
|
await arrivalInput.fill('SPB');
|
|
|
|
await expect(departureInput).toHaveValue('Moscow');
|
|
await expect(arrivalInput).toHaveValue('SPB');
|
|
});
|
|
});
|
|
|
|
test.describe('US-26: Swap Cities Button (Exchange)', () => {
|
|
test('should have both departure and arrival inputs for exchange', async ({ page }) => {
|
|
const departureInput = page.locator('[data-testid="schedule-departure-input"]');
|
|
const arrivalInput = page.locator('[data-testid="schedule-arrival-input"]');
|
|
|
|
await expect(departureInput).toBeVisible();
|
|
await expect(arrivalInput).toBeVisible();
|
|
});
|
|
|
|
test('should allow switching focus between city inputs', async ({ page }) => {
|
|
const departureInput = page.locator('[data-testid="schedule-departure-input"] input');
|
|
const arrivalInput = page.locator('[data-testid="schedule-arrival-input"] input');
|
|
|
|
await departureInput.focus();
|
|
await expect(departureInput).toBeFocused();
|
|
|
|
await arrivalInput.focus();
|
|
await expect(arrivalInput).toBeFocused();
|
|
});
|
|
|
|
test('should support entering different cities', async ({ page }) => {
|
|
const departureInput = page.locator('[data-testid="schedule-departure-input"] input');
|
|
const arrivalInput = page.locator('[data-testid="schedule-arrival-input"] input');
|
|
|
|
await departureInput.fill('Moscow');
|
|
await arrivalInput.fill('Saint Petersburg');
|
|
|
|
await expect(departureInput).toHaveValue('Moscow');
|
|
await expect(arrivalInput).toHaveValue('Saint Petersburg');
|
|
});
|
|
});
|
|
|
|
test.describe('US-27: Week Selection', () => {
|
|
test('should render date from input', async ({ page }) => {
|
|
const dateFromInput = page.locator('[data-testid="schedule-calendar"] input');
|
|
await expect(dateFromInput).toBeVisible();
|
|
});
|
|
|
|
test('should render date to input', async ({ page }) => {
|
|
const dateToInput = page.locator('[data-testid="schedule-outbound-date-input"]');
|
|
await expect(dateToInput).toBeVisible();
|
|
});
|
|
|
|
test('should have Depart label for date from', async ({ page }) => {
|
|
const label = page.getByText('Depart', { exact: true });
|
|
await expect(label).toBeVisible();
|
|
});
|
|
|
|
test('should have Return label for date to', async ({ page }) => {
|
|
const label = page.getByText('Return', { exact: true });
|
|
await expect(label).toBeVisible();
|
|
});
|
|
|
|
test('should initialize with date values', async ({ page }) => {
|
|
const dateFromInput = page.locator('[data-testid="schedule-calendar"] input');
|
|
const dateToInput = page.locator('[data-testid="schedule-outbound-date-input"]');
|
|
|
|
const dateFromValue = await dateFromInput.inputValue();
|
|
const dateToValue = await dateToInput.inputValue();
|
|
|
|
// Should match YYYY-MM-DD format
|
|
expect(dateFromValue).toMatch(/\d{4}-\d{2}-\d{2}/);
|
|
expect(dateToValue).toMatch(/\d{4}-\d{2}-\d{2}/);
|
|
});
|
|
|
|
test('should have date input type', async ({ page }) => {
|
|
const dateFromInput = page.locator('[data-testid="schedule-calendar"] input');
|
|
const dateToInput = page.locator('[data-testid="schedule-outbound-date-input"]');
|
|
|
|
const dateFromType = await dateFromInput.getAttribute('type');
|
|
const dateToType = await dateToInput.getAttribute('type');
|
|
|
|
expect(dateFromType).toBe('date');
|
|
expect(dateToType).toBe('date');
|
|
});
|
|
|
|
test('should allow changing departure date', async ({ page }) => {
|
|
const dateFromInput = page.locator('[data-testid="schedule-calendar"] input');
|
|
const initialValue = await dateFromInput.inputValue();
|
|
|
|
// The date input should be functional
|
|
await dateFromInput.focus();
|
|
await expect(dateFromInput).toBeFocused();
|
|
});
|
|
|
|
test('should support week date range selection', async ({ page }) => {
|
|
const dateFromInput = page.locator('[data-testid="schedule-calendar"] input');
|
|
const dateToInput = page.locator('[data-testid="schedule-outbound-date-input"]');
|
|
|
|
// Both should be visible and functional for date range
|
|
await expect(dateFromInput).toBeVisible();
|
|
await expect(dateToInput).toBeVisible();
|
|
|
|
const dateFromValue = await dateFromInput.inputValue();
|
|
const dateToValue = await dateToInput.inputValue();
|
|
|
|
// Both should have dates
|
|
expect(dateFromValue).toBeTruthy();
|
|
expect(dateToValue).toBeTruthy();
|
|
});
|
|
});
|
|
|
|
test.describe('Schedule Search Form Integration', () => {
|
|
test('should have all search inputs visible', async ({ page }) => {
|
|
const departureInput = page.locator('[data-testid="schedule-departure-input"]');
|
|
const arrivalInput = page.locator('[data-testid="schedule-arrival-input"]');
|
|
const dateFromInput = page.locator('[data-testid="schedule-calendar"] input');
|
|
const dateToInput = page.locator('[data-testid="schedule-outbound-date-input"]');
|
|
|
|
await expect(departureInput).toBeVisible();
|
|
await expect(arrivalInput).toBeVisible();
|
|
await expect(dateFromInput).toBeVisible();
|
|
await expect(dateToInput).toBeVisible();
|
|
});
|
|
|
|
test('should have search button', async ({ page }) => {
|
|
const searchButton = page.locator('[data-testid="schedule-search-button"]');
|
|
await expect(searchButton).toBeVisible();
|
|
await expect(searchButton).toContainText('Search', { ignoreCase: true });
|
|
});
|
|
|
|
test('should have checkbox for direct flights only', async ({ page }) => {
|
|
const directCheckbox = page.locator('[data-testid="schedule-direct-only-checkbox"]');
|
|
await expect(directCheckbox).toBeVisible();
|
|
});
|
|
|
|
test('should have checkbox for return flight', async ({ page }) => {
|
|
const returnCheckbox = page.locator('[data-testid="schedule-return-checkbox"]');
|
|
await expect(returnCheckbox).toBeVisible();
|
|
});
|
|
|
|
test('should show validation error when trying to search without cities', async ({ page }) => {
|
|
const searchButton = page.locator('[data-testid="schedule-search-button"]');
|
|
await searchButton.click();
|
|
|
|
const error = page.locator('[data-testid="schedule-validation-error"]');
|
|
await expect(error).toBeVisible();
|
|
});
|
|
|
|
test('should toggle return date fields when return flight is enabled', async ({ page }) => {
|
|
const returnCheckbox = page.locator('[data-testid="schedule-return-checkbox"]');
|
|
const returnCalendar = page.locator('[data-testid="schedule-return-calendar"]');
|
|
|
|
// Initially hidden
|
|
await expect(returnCalendar).not.toBeVisible();
|
|
|
|
// Click to enable return flight
|
|
await returnCheckbox.click();
|
|
|
|
// Now visible
|
|
await expect(returnCalendar).toBeVisible();
|
|
});
|
|
});
|
|
|
|
test.describe('Schedule Search Workflow', () => {
|
|
test('should allow complete search form interaction', async ({ page }) => {
|
|
const departureInput = page.locator('[data-testid="schedule-departure-input"] input');
|
|
const arrivalInput = page.locator('[data-testid="schedule-arrival-input"] input');
|
|
const directCheckbox = page.locator('[data-testid="schedule-direct-only-checkbox"]');
|
|
|
|
// Fill departure city
|
|
await departureInput.fill('Moscow');
|
|
await expect(departureInput).toHaveValue('Moscow');
|
|
|
|
// Fill arrival city
|
|
await arrivalInput.fill('Saint Petersburg');
|
|
await expect(arrivalInput).toHaveValue('Saint Petersburg');
|
|
|
|
// Toggle direct only
|
|
const isChecked = await directCheckbox.isChecked();
|
|
await directCheckbox.click();
|
|
const newChecked = await directCheckbox.isChecked();
|
|
expect(newChecked).toBe(!isChecked);
|
|
});
|
|
|
|
test('should maintain form state during interaction', async ({ page }) => {
|
|
const departureInput = page.locator('[data-testid="schedule-departure-input"] input');
|
|
const arrivalInput = page.locator('[data-testid="schedule-arrival-input"] input');
|
|
const dateFromInput = page.locator('[data-testid="schedule-calendar"] input');
|
|
|
|
// Enter data
|
|
await departureInput.fill('Moscow');
|
|
await arrivalInput.fill('SPB');
|
|
const originalDate = await dateFromInput.inputValue();
|
|
|
|
// Verify all data is still there
|
|
await expect(departureInput).toHaveValue('Moscow');
|
|
await expect(arrivalInput).toHaveValue('SPB');
|
|
const newDate = await dateFromInput.inputValue();
|
|
expect(newDate).toBe(originalDate);
|
|
});
|
|
|
|
test('should allow toggling between one-way and round trip', async ({ page }) => {
|
|
const returnCheckbox = page.locator('[data-testid="schedule-return-checkbox"]');
|
|
const returnCalendar = page.locator('[data-testid="schedule-return-calendar"]');
|
|
|
|
// Initially one-way
|
|
const isCheckedInitial = await returnCheckbox.isChecked();
|
|
expect(isCheckedInitial).toBe(false);
|
|
|
|
// Toggle to round trip
|
|
await returnCheckbox.click();
|
|
await expect(returnCalendar).toBeVisible();
|
|
|
|
// Toggle back to one-way
|
|
await returnCheckbox.click();
|
|
await expect(returnCalendar).not.toBeVisible();
|
|
});
|
|
});
|
|
|
|
test.describe('Accessibility', () => {
|
|
test('should have form with proper role and label', async ({ page }) => {
|
|
const form = page.locator('[role="search"]');
|
|
const ariaLabel = await form.getAttribute('aria-label');
|
|
|
|
await expect(form).toBeVisible();
|
|
expect(ariaLabel).toBeTruthy();
|
|
});
|
|
|
|
test('should have properly associated labels', async ({ page }) => {
|
|
const fromLabel = page.getByText('From', { exact: true });
|
|
const toLabel = page.getByText('To', { exact: true });
|
|
const departLabel = page.getByText('Depart', { exact: true });
|
|
const returnLabel = page.getByText('Return', { exact: true });
|
|
|
|
await expect(fromLabel).toBeVisible();
|
|
await expect(toLabel).toBeVisible();
|
|
await expect(departLabel).toBeVisible();
|
|
await expect(returnLabel).toBeVisible();
|
|
});
|
|
|
|
test('should support keyboard navigation', async ({ page }) => {
|
|
const departureInput = page.locator('[data-testid="schedule-departure-input"] input');
|
|
const arrivalInput = page.locator('[data-testid="schedule-arrival-input"] input');
|
|
const searchButton = page.locator('[data-testid="schedule-search-button"]');
|
|
|
|
// Start at departure
|
|
await departureInput.focus();
|
|
await expect(departureInput).toBeFocused();
|
|
|
|
// Tab to next element
|
|
await page.keyboard.press('Tab');
|
|
|
|
// Should be on next focusable element
|
|
const focusedElement = await page.evaluate(() =>
|
|
document.activeElement?.getAttribute('data-testid'),
|
|
);
|
|
expect(focusedElement).not.toBe('schedule-departure-input');
|
|
});
|
|
});
|
|
});
|