24 KiB
E2E Test Suite Design: Aeroflot Flights Web (Angular → React)
Date: 2026-04-04
Status: Design Approved
Scope: 200-300 comprehensive e2e tests covering all UI elements and interactions
1. Overview
This document specifies a comprehensive end-to-end test suite for the Aeroflot Flights Web application, covering both the current Angular implementation (ClientApp/) and the new React implementation (react-app/). The tests verify feature parity between the two versions and ensure all UI elements, interactions, and edge cases function correctly.
Approach: Write all tests against Angular first, validate 100% pass rate, then adapt and run against React (with both mocked and real APIs).
Total Test Count: 200-300 tests
Execution Time: 8-15 minutes per suite (mocked API), 10-20 minutes (real API)
2. Test Architecture & File Organization
2.1 Directory Structure
ClientApp/cypress/
├── integration/
│ ├── features/
│ │ ├── online-board.cy.ts (~60-70 tests)
│ │ ├── schedule.cy.ts (~50-60 tests)
│ │ ├── flights-map.cy.ts (~30-40 tests)
│ │ ├── popular-requests.cy.ts (~20-30 tests)
│ │ ├── i18n.cy.ts (~15-20 tests)
│ │ └── error-states.cy.ts (~25-30 tests)
│ └── responsive.cy.ts (~30-40 tests)
├── support/
│ ├── commands.ts (custom Cypress commands)
│ ├── page-objects/
│ │ ├── online-board.po.ts
│ │ ├── schedule.po.ts
│ │ ├── flights-map.po.ts
│ │ ├── common.po.ts
│ │ └── index.ts
│ ├── fixtures.ts (mock data, test cities, flights)
│ └── index.ts
└── tsconfig.json
react-app/cypress/
├── integration/
│ ├── features/
│ │ ├── online-board.cy.ts (adapted from Angular)
│ │ ├── schedule.cy.ts
│ │ ├── flights-map.cy.ts
│ │ ├── popular-requests.cy.ts
│ │ ├── i18n.cy.ts
│ │ └── error-states.cy.ts
│ └── responsive.cy.ts
├── support/
│ ├── commands.ts (same as Angular)
│ ├── page-objects/ (may differ from Angular if DOM differs)
│ └── fixtures.ts (same as Angular)
└── tsconfig.json
2.2 Test Organization
Each spec file is organized as:
// Example: online-board.cy.ts
describe('Online Board Feature', () => {
describe('Arrival Tab', () => {
describe('City Input', () => {
it('should accept manual city entry');
it('should show validation error for empty input');
it('should handle special characters gracefully');
// ... more tests
});
describe('Date Picker', () => {
it('should accept valid future dates');
it('should reject past dates');
// ... more tests
});
describe('Search & Results', () => {
it('should display flight results after successful search');
it('should show loading state during API call');
// ... more tests
});
});
describe('Departure Tab', () => {
// ... similar structure
});
// ... more features
});
3. Test Scope by Feature
3.1 Online Board (~60-70 tests)
Arrival Tab (~20 tests):
- City input: manual entry, dropdown selection, validation errors, empty input, special characters
- Date picker: valid dates, future dates, past dates, invalid formats, today, edge dates
- Search button: valid search, missing fields, network error, loading state, disabled state
- Results display: flight list rendering, correct count, flight details modal, pagination
Departure Tab (~20 tests):
- Same test coverage as Arrival (mirror feature)
Flight Search/Filter (~15 tests):
- Flight number input: valid format (e.g., "SU 123"), invalid format, special characters, clear button
- Autocomplete behavior: suggestions appear, filtering works, arrow key navigation, tab navigation
- Form submission with partial filters
Flight Details Panel (~10 tests):
- Modal opens on flight click, displays all correct flight info
- Modal closes on X button, escape key, outside click
- Links (airline, gate, terminal) navigate correctly
- Back button returns to results with filters preserved
3.2 Schedule (~50-60 tests)
Search Page (~25 tests):
- Origin autocomplete: manual entry, dropdown, validation
- Destination autocomplete: same as origin
- Date range picker: start date, end date, single day, full range, invalid ranges
- Passenger count: input, increment/decrement, max/min bounds
- Form submission: valid, incomplete, network error, empty results
Flight Details Page (~20 tests):
- Flight info displays: departure, arrival, duration, airline, flight number
- Timing details: gate, terminal, check-in time, boarding time
- Seat map: renders, interactive, can select seat
- Price info: base price, taxes, total, currency formatting
- Navigation: previous flight, next flight, back to search
Filters & Sorting (~10-15 tests):
- Time range filter: apply, clear, validate boundaries
- Airline filter: select multiple, deselect all, apply
- Price range filter: min/max sliders, apply
- Sorting: by departure time, duration, price (ascending/descending)
3.3 Flights Map (~30-40 tests)
Map Rendering (~15 tests):
- Map loads and is interactive
- Flight destination markers display
- Marker clustering at certain zoom levels
- Pan and zoom work correctly
- Geolocation button works (if enabled)
Destination List (~15 tests):
- List items render with destination name, flight count
- Click destination: highlights on map, center map on marker
- Search/filter in list: filters by city name
- Empty state when no destinations
Map Interactions (~10 tests):
- Click marker: shows popup with flight info
- Click destination in list: highlights on map, centers view
- Hover effects on markers and list items
- Popup contains: destination name, flight count, quick link to search
3.4 Popular Requests Widget (~20-30 tests)
- Widget renders on initial page load
- Displays all popular request items (mock data)
- Click item: navigates to search with correct parameters
- API fallback to mock data works
- Empty state handling (no popular requests)
- Widget positioning and styling correct
3.5 Internationalization (i18n) (~15-20 tests)
- Language switcher visible and functional (all 9 languages: ru, en, es, fr, it, ja, ko, zh, de)
- Switching language: page updates all text, no hard-coded strings visible
- Language persistence: localStorage remembers selection
- Date formats match locale (e.g., DD.MM.YYYY for ru, MM/DD/YYYY for en)
- Number formatting matches locale (decimal separator, thousands separator)
- Text truncation on narrow screens doesn't break layout
- All UI elements have translations (no missing keys)
3.6 Error States (~25-30 tests)
- Network errors: 404 (not found), 500 (server error), 503 (service unavailable)
- Timeout handling: API call exceeds timeout threshold
- Empty results: no flights found for search
- Validation errors: required fields missing, invalid input format
- Loading states: loader visible, correct messaging
- Recovery: retry button works, clears error after successful retry
- SignalR connection failures: connection lost message, auto-reconnect attempt, manual reconnect button
3.7 Responsive/Mobile (~30-40 tests)
Mobile Viewport (375x667 - iPhone SE):
- All text is readable (no overflow, proper line breaks)
- Touch targets are at least 44x44px
- Forms are usable (inputs accessible, not hidden behind keyboard)
- Navigation: hamburger menu or drawer opens/closes
- Accordion sections: collapse/expand works on tap
- Carousel/swipes: work with touch events
Tablet Viewport (768x1024 - iPad):
- Layout is optimized for tablet (not stretched, not too narrow)
- Multi-column layouts work correctly
- Touch interactions work
Desktop Viewport (1920x1080 - Large screen):
- Layout scales correctly
- No horizontal scrolling
- All content is accessible without zooming
4. Test Categories & Patterns
4.1 Happy Path Tests (~30-40% of total: 60-120 tests)
User performs the intended action and succeeds. Example:
it('should search flights by arrival city and date', () => {
cy.selectArrivalCity('Москва');
cy.setArrivalDate('15.04.2026');
cy.clickSearchButton();
cy.getFlightResults().should('have.length.greaterThan', 0);
cy.getFirstFlightResult().should('be.visible');
});
4.2 Edge Case Tests (~20-25% of total: 40-75 tests)
Boundary conditions, extreme inputs, special characters. Examples:
it('should handle special characters in flight number', () => {
cy.typeFlightNumber('SU-123@#$%');
cy.shouldShowValidationError('Invalid format');
});
it('should allow searching 1 year in the future', () => {
cy.setDepartureDate(moment().add(365, 'days').format('DD.MM.YYYY'));
cy.clickSearch();
cy.shouldLoadResults();
});
it('should handle empty autocomplete results', () => {
cy.typeArrivalCity('ZZZZZZ');
cy.shouldShowEmptyState('No cities found');
});
4.3 Error Handling Tests (~15-20% of total: 30-60 tests)
Network failures, invalid responses, timeouts, server errors. Examples:
it('should show error message on 500 API failure', () => {
cy.intercept('GET', '**/api/flights/**', { statusCode: 500 });
cy.clickSearch();
cy.getErrorMessage().should('contain', 'Server error');
});
it('should handle network timeout gracefully', () => {
cy.intercept('GET', '**/api/flights/**', { delay: 10000 });
cy.clickSearch();
cy.getLoader().should('be.visible');
cy.getRetryButton().should('be.visible');
});
it('should recover from SignalR connection loss', () => {
cy.window().then(win => {
win.signalRConnection.stop();
});
cy.getConnectionStatusBanner().should('be.visible');
cy.getReconnectButton().click();
cy.getConnectionStatusBanner().should('not.exist');
});
4.4 State Management Tests (~10-15% of total: 20-45 tests)
Form state persistence, navigation state, localStorage/sessionStorage. Examples:
it('should preserve search filters when navigating back', () => {
cy.selectArrivalCity('Москва');
cy.setDate('15.04.2026');
cy.clickFlightResult(0);
cy.goBack();
cy.getArrivalCityInput().should('have.value', 'Москва');
cy.getDateInput().should('have.value', '15.04.2026');
});
it('should remember language selection after page reload', () => {
cy.selectLanguage('en');
cy.reload();
cy.getCurrentLanguage().should('equal', 'en');
});
4.5 Accessibility & Interaction Tests (~10-15% of total: 20-45 tests)
Keyboard navigation, screen reader support, ARIA attributes, touch interactions. Examples:
it('should navigate autocomplete with arrow keys', () => {
cy.typeInAutocomplete('Мос');
cy.get('body').type('{downarrow}');
cy.getFirstAutocompleteOption().should('have.focus');
cy.get('body').type('{enter}');
cy.shouldSelectOption('Москва');
});
it('should be keyboard navigable without mouse', () => {
cy.get('body').type('{tab}{tab}'); // Focus search button
cy.focused().should('have.attr', 'data-testid', 'search-button');
cy.get('body').type('{enter}');
cy.shouldLoadResults();
});
it('should be swipeable on mobile (right swipe)', () => {
cy.viewport('iphone-x');
cy.swipeRight();
cy.getDateInput().should('have.value', moment().subtract(1, 'day').format('DD.MM.YYYY'));
});
4.6 Localization Tests (~5-10% of total: 10-30 tests)
Language switching, date formats, number formatting. Examples:
it('should display correct date format for Russian locale', () => {
cy.selectLanguage('ru');
cy.setDate('15.04.2026');
cy.getDateDisplay().should('contain', '15 апреля 2026');
});
it('should format currency for German locale', () => {
cy.selectLanguage('de');
cy.getFlightPrice().should('contain', '€');
});
it('should format numbers with correct decimal separator', () => {
cy.selectLanguage('de');
cy.getPrice().should('contain', ','); // German decimal separator
cy.selectLanguage('en');
cy.getPrice().should('contain', '.'); // English decimal separator
});
5. Data, Fixtures & Mocking Strategy
5.1 Fixture Architecture
// cypress/support/fixtures.ts (shared between Angular & React)
export const CITIES = {
arrival: [
{ name: 'Москва', code: 'SVO', lat: 55.7558, lng: 37.62 },
{ name: 'Санкт-Петербург', code: 'LED', lat: 59.8011, lng: 30.2625 },
{ name: 'Анапа', code: 'AAQ', lat: 44.8972, lng: 37.3369 },
{ name: 'Екатеринбург', code: 'SVX', lat: 56.7431, lng: 60.8022 },
// ... 9+ cities total
],
departure: [
{ name: 'Москва', code: 'VKO', lat: 55.5917, lng: 37.2750 },
// ...
]
};
export const MOCK_FLIGHTS = {
arrival: [
{
id: 'SU123',
airline: 'Aeroflot',
number: 'SU 123',
departure: '10:15',
arrival: '11:45',
duration: '1h 30m',
status: 'landed',
gate: 'A5',
terminal: '1'
},
// ... 10+ flights for variety
],
departure: [ /* ... */ ]
};
export const TEST_USERS = {
default: { email: 'test@example.com' },
};
5.2 Intercept Strategy (Approach C: Real + Mocked)
Test Suite 1: Mocked API (deterministic, fast, runs in CI)
beforeEach(() => {
cy.intercept('GET', '**/api/flights/v1.1/**', {
statusCode: 200,
body: MOCK_FLIGHTS.arrival
}).as('getFlights');
cy.intercept('GET', '**/api/cities/**', {
statusCode: 200,
body: CITIES.arrival
}).as('getCities');
cy.visit('http://localhost:4200'); // Angular
// OR cy.visit('http://localhost:3000'); // React
});
Test Suite 2: Real API (integration test, validates actual backend)
beforeEach(() => {
// No intercepts — hits real backend
cy.visit('https://test.aeroflot.ru');
});
5.3 State Reset (Approach A: Full Reset per Test)
beforeEach(() => {
// Clear all browser state
cy.clearAllLocalStorage();
cy.clearAllSessionStorage();
cy.clearCookies();
// Close any open WebSocket connections (SignalR)
cy.window().then(win => {
if (win.signalRConnection) {
win.signalRConnection.stop().catch(() => {});
}
});
// Reset to baseline URL
cy.visit('/');
cy.wait(500); // Allow page to fully load
});
afterEach(() => {
// Optional: take screenshot on failure
// (Cypress does this automatically by default)
});
5.4 Common Cypress Commands
// cypress/support/commands.ts
Cypress.Commands.add('selectArrivalCity', (cityName: string) => {
cy.get('[data-testid="arrival-city-input"]').type(cityName);
cy.get(`[data-testid="city-option-${cityName}"]`).click();
});
Cypress.Commands.add('setArrivalDate', (date: string) => {
cy.get('[data-testid="arrival-date-input"]').clear().type(date);
});
Cypress.Commands.add('clickSearchButton', () => {
cy.get('[data-testid="search-button"]').click();
cy.get('[data-testid="loader"]').should('be.visible');
cy.get('[data-testid="loader"]').should('not.exist');
});
Cypress.Commands.add('getFlightResults', () => {
return cy.get('[data-testid="flight-result"]');
});
Cypress.Commands.add('shouldShowValidationError', (message: string) => {
cy.get('[data-testid="error-message"]').should('contain', message);
});
Cypress.Commands.add('swipeRight', () => {
cy.get('body').trigger('touchstart', { touches: [{ clientX: 0, clientY: 100 }] });
cy.get('body').trigger('touchmove', { touches: [{ clientX: 100, clientY: 100 }] });
cy.get('body').trigger('touchend');
});
6. Execution Strategy & Tooling
6.1 Cypress Configuration
// cypress.config.ts (both Angular & React)
import { defineConfig } from 'cypress';
export default defineConfig({
e2e: {
baseUrl: process.env.BASE_URL || 'http://localhost:4200',
viewportWidth: 1280,
viewportHeight: 720,
defaultCommandTimeout: 5000,
requestTimeout: 10000,
responseTimeout: 10000,
pageLoadTimeout: 30000,
chromeWebSecurity: false, // for Module Federation
video: true,
videoUploadOnPasses: false,
screenshotOnRunFailure: true,
specPattern: 'cypress/integration/**/*.cy.ts',
supportFile: 'cypress/support/index.ts',
setupNodeEvents(on, config) {
// Example: video recording configuration
return config;
},
},
});
6.2 NPM Scripts
Add to both ClientApp/package.json and react-app/package.json:
{
"cypress:open": "cypress open",
"cypress:run": "cypress run",
"cypress:run:all": "cypress run --spec 'cypress/integration/**/*.cy.ts'",
"cypress:run:feature": "cypress run --spec 'cypress/integration/features/*.cy.ts'",
"cypress:run:responsive": "cypress run --spec 'cypress/integration/responsive.cy.ts'",
"cypress:report": "npm run cypress:run && npx mochawesome-report-generator",
"test:e2e": "npm run cypress:run -- --env API_MODE=mocked",
"test:e2e:real": "npm run cypress:run -- --env API_MODE=real BASE_URL=https://test.aeroflot.ru"
}
6.3 Execution Flow
Phase 1: Write Angular Tests (2-3 hours)
- Create all spec files with describe/it structure
- Implement helper functions in
cypress/support/commands.ts - Implement Page Object Models in
cypress/support/page-objects/ - Run incrementally:
npm run cypress:open - Validate all tests pass:
npm run cypress:run:all
Phase 2: Validate Full Angular Suite (30 mins)
- Full headless run with video/screenshots:
npm run cypress:run:all - Generate HTML report:
npm run cypress:report - Fix any flaky tests
Phase 3: Adapt to React (2-3 hours)
- Copy spec files to
react-app/cypress/integration/ - Update selectors in page-objects if React DOM differs
- Update
cypress.config.tsbaseUrl to:3000 - Run against React with mocked API:
cd react-app && npm run test:e2e - Fix failures (likely selector or navigation changes)
Phase 4: Run React Suite with Real API (1-2 hours)
- Run full suite against staging:
npm run test:e2e:real - Validate all tests pass
- Fix any integration issues (timing, data, network)
6.4 CI/CD Integration
# .github/workflows/e2e-tests.yml
name: E2E Tests
on: [push, pull_request]
jobs:
test-angular:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
- run: cd ClientApp && npm ci
- run: npm start > /dev/null 2>&1 &
- run: npx wait-on http://localhost:4200 --timeout 30000
- run: npm run cypress:run:all
- uses: actions/upload-artifact@v3
if: always()
with:
name: cypress-videos-angular
path: ClientApp/cypress/videos
test-react:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '18'
- run: cd react-app && npm ci
- run: npm start > /dev/null 2>&1 &
- run: npx wait-on http://localhost:3000 --timeout 30000
- run: npm run test:e2e
- uses: actions/upload-artifact@v3
if: always()
with:
name: cypress-videos-react
path: react-app/cypress/videos
7. Success Criteria & Validation
7.1 Definition of Done
Angular Tests:
- ✅ All 200-300 tests written and organized by feature
- ✅ All tests pass against Angular app (100% pass rate)
- ✅ No test takes >10 seconds (performance gate)
- ✅ All test categories represented: happy path, edge case, error handling, state, accessibility, i18n, responsive
- ✅ Code coverage: 80%+ for tested components
React Tests:
- ✅ All Angular tests adapted to React (selector/navigation updates only)
- ✅ All tests pass against React app with mocked API (100% pass rate)
- ✅ All tests pass against React app with real API (staging backend)
- ✅ No test takes >10 seconds
- ✅ Feature parity verified: React UI behaves identically to Angular
7.2 Metrics to Track
| Metric | Target |
|---|---|
| Total Tests (Angular) | 200-300 |
| Total Tests (React) | 200-300 (same) |
| Pass Rate (Angular) | 100% |
| Pass Rate (React, mocked) | 100% |
| Pass Rate (React, real API) | 100% |
| Avg Test Duration | 2-3 seconds |
| Total Suite Time (mocked) | 8-15 minutes |
| Total Suite Time (real API) | 10-20 minutes |
| Code Coverage (tested components) | 80%+ |
| Flaky Test Count | 0 |
7.3 Stopping Condition
Work continues until:
- ✅ All 200-300 tests pass on Angular
- ✅ All 200-300 tests pass on React (mocked API)
- ✅ All 200-300 tests pass on React (real API)
- ✅ No test takes >10 seconds
- ✅ Re-running test suite 3x produces consistent results (no flaky tests)
- ✅ Angular and React behavior is identical for all covered features
8. Implementation Notes
8.1 DOM Structure Assumptions
Tests assume the following data-testid attributes exist on UI elements (both Angular and React must implement these):
Online Board:
- data-testid="arrival-city-input"
- data-testid="arrival-date-input"
- data-testid="search-button"
- data-testid="flight-result"
- data-testid="loader"
- data-testid="error-message"
- data-testid="flight-details-modal"
Schedule:
- data-testid="origin-input"
- data-testid="destination-input"
- data-testid="date-range-picker"
- data-testid="passenger-count"
- data-testid="search-button"
And so on...
If either implementation uses different selectors, Page Object Models will be updated to translate.
8.2 Test Data Lifecycle
- Setup: Full state reset before each test (localStorage, cookies, connections)
- Execution: Test runs against isolated mocked data
- Teardown: Browser state cleared automatically
No test data persists between tests.
8.3 Handling Flaky Tests
If a test is flaky:
- Add explicit waits for async operations
- Retry the specific assertion (Cypress built-in)
- Check for race conditions in test logic
- If unsolvable, mark as skipped with comment explaining issue
8.4 Performance Constraints
- Each test: <10 seconds (includes setup, execution, teardown)
- Full suite: <30 minutes (8-15 min for mocked, 10-20 min for real API)
- If a test exceeds 10 seconds, it's split or optimized
9. Risks & Mitigation
| Risk | Probability | Impact | Mitigation |
|---|---|---|---|
| React selectors differ from Angular | High | Medium | Page Object Model abstraction; update POM for React |
| Flaky network-dependent tests | Medium | High | Use mocked API for primary suite; real API as secondary |
| Test explosion (200-300 is large) | Medium | High | Phased execution; monitor suite time; parallelize if needed |
| Timing issues (async operations) | Medium | Medium | Explicit waits, retry logic, proper Cypress commands |
| Mobile tests on CI | Medium | Low | Use Cypress viewport, skip on CI if needed, test locally |
10. Timeline & Ownership
| Phase | Estimate | Owner |
|---|---|---|
| 1. Write Angular Tests | 2-3 hours | Claude Code |
| 2. Validate Angular | 30 mins | Claude Code |
| 3. Adapt to React | 2-3 hours | Claude Code |
| 4. Validate React (mocked) | 1 hour | Claude Code |
| 5. Validate React (real API) | 1-2 hours | Claude Code |
| Total | 7-10 hours |
Work continues until all tests pass on both versions.
Appendix: Reference
A.1 Cypress Best Practices Used
- ✅ Page Object Model for selector abstraction
- ✅ Custom commands for common actions
- ✅ Explicit waits over implicit
- ✅ Data attributes (data-testid) for element selection
- ✅ Full state reset between tests
- ✅ Feature-based organization
- ✅ Mocking + real API testing
A.2 Languages Supported (i18n)
- Russian (ru)
- English (en)
- Spanish (es)
- French (fr)
- Italian (it)
- Japanese (ja)
- Korean (ko)
- Chinese (zh)
- German (de)
All 9 languages must have passing tests.
A.3 Key Features Tested
- Online Board (departure/arrival tabs, search, filters, flight details)
- Schedule (search page, flight details, filters, sorting)
- Flights Map (map rendering, markers, destination list, interactions)
- Popular Requests (widget load, navigation, fallback)
- Internationalization (language switching, formatting, persistence)
- Error States (network failures, validation, loading states, recovery)
- Responsive Design (mobile, tablet, desktop viewports)