Add schedule test suites for Tasks 26-30

Tasks 26-30: Schedule Tests
- Task 26: Schedule Search tests (40 tests) - search form, results, filters, history
- Task 27: Schedule Results Display tests (40 tests) - display, sorting, filtering, pagination
- Task 28: Download Schedule tests (50 tests) - CSV/PDF/Excel export, batch download, validation
- Task 29: Schedule Filtering tests (50 tests) - operating days, time range, airline, multi-filter
- Task 30: Route Display tests (45 tests) - route info, map visualization, multi-leg routes

All tests follow AAA pattern and use data-testid selectors.
Total: 225 tests for schedule feature area.
This commit is contained in:
gnezim
2026-04-05 19:26:33 +03:00
parent 9174cae406
commit 15028e0210
5 changed files with 1269 additions and 0 deletions
@@ -0,0 +1,266 @@
import { uiHelpers } from '../../support/helpers/ui-helpers'
describe('Schedule - Download Functionality', () => {
beforeEach(() => {
cy.visit('http://localhost:3001/schedule')
cy.intercept('GET', '**/api/schedule/search**', {
statusCode: 200,
body: { schedules: [{ id: '1', flight: 'SU123' }] },
}).as('scheduleSearch')
uiHelpers.fillInput('schedule-departure-input', 'SVO')
uiHelpers.fillInput('schedule-arrival-input', 'LED')
cy.getByTestId('schedule-search-button').click()
cy.wait('@scheduleSearch')
})
describe('Download Options', () => {
it('should display download button', () => {
cy.getByTestId('download-schedule-button').should('be.visible')
})
it('should show download format options', () => {
cy.getByTestId('download-schedule-button').click()
cy.getByTestId('download-format-menu').should('be.visible')
})
it('should allow CSV export', () => {
cy.getByTestId('download-csv-option').should('be.visible')
})
it('should allow PDF export', () => {
cy.getByTestId('download-pdf-option').should('be.visible')
})
it('should allow Excel export', () => {
cy.getByTestId('download-excel-option').should('be.visible')
})
it('should allow JSON export', () => {
cy.getByTestId('download-json-option').should('be.visible')
})
})
describe('CSV Download', () => {
it('should download schedule as CSV', () => {
cy.getByTestId('download-csv-option').click()
cy.readFile('cypress/downloads/schedule.csv').should('exist')
})
it('should include headers in CSV', () => {
cy.getByTestId('download-csv-option').click()
cy.readFile('cypress/downloads/schedule.csv').then(content => {
expect(content).to.contain('Flight')
expect(content).to.contain('Departure')
expect(content).to.contain('Arrival')
})
})
it('should include all schedule data in CSV', () => {
cy.getByTestId('download-csv-option').click()
cy.readFile('cypress/downloads/schedule.csv').then(content => {
expect(content).to.contain('SU')
})
})
it('should format dates correctly in CSV', () => {
cy.getByTestId('download-csv-option').click()
cy.readFile('cypress/downloads/schedule.csv').then(content => {
expect(content).to.match(/\d{4}-\d{2}-\d{2}/)
})
})
})
describe('PDF Download', () => {
it('should download schedule as PDF', () => {
cy.getByTestId('download-pdf-option').click()
cy.readFile('cypress/downloads/schedule.pdf').should('exist')
})
it('should include header in PDF', () => {
cy.getByTestId('download-pdf-option').click()
// PDF validation would be more complex
cy.readFile('cypress/downloads/schedule.pdf', 'binary')
.should('include', 'PDF')
})
it('should include schedule table in PDF', () => {
cy.getByTestId('download-pdf-option').click()
cy.readFile('cypress/downloads/schedule.pdf').should('exist')
})
it('should format PDF for printing', () => {
cy.getByTestId('download-pdf-option').click()
cy.readFile('cypress/downloads/schedule.pdf').should('exist')
})
})
describe('Excel Download', () => {
it('should download schedule as Excel', () => {
cy.getByTestId('download-excel-option').click()
cy.readFile('cypress/downloads/schedule.xlsx').should('exist')
})
it('should create properly formatted Excel file', () => {
cy.getByTestId('download-excel-option').click()
cy.readFile('cypress/downloads/schedule.xlsx', 'binary').should('exist')
})
})
describe('Download Settings', () => {
it('should allow selecting columns to export', () => {
cy.getByTestId('download-settings-button').click()
cy.getByTestId('select-columns-option').should('be.visible')
})
it('should allow date range selection', () => {
cy.getByTestId('download-settings-button').click()
cy.getByTestId('date-range-picker').should('be.visible')
})
it('should allow filtering routes before download', () => {
cy.getByTestId('download-settings-button').click()
cy.getByTestId('filter-routes-option').should('be.visible')
})
it('should apply custom filename', () => {
cy.getByTestId('download-settings-button').click()
cy.getByTestId('filename-input').clear().type('my-schedule')
cy.getByTestId('download-csv-option').click()
cy.readFile('cypress/downloads/my-schedule.csv').should('exist')
})
})
describe('Batch Download', () => {
it('should select multiple schedules', () => {
cy.getByTestId('select-all-checkbox').click()
cy.getByTestId('schedule-item').each($item => {
cy.wrap($item).should('have.class', 'selected')
})
})
it('should download selected schedules', () => {
cy.getByTestId('schedule-item').first().find('input[type="checkbox"]').click()
cy.getByTestId('download-selected-button').click()
cy.readFile('cypress/downloads/schedule.csv').should('exist')
})
it('should show selection count', () => {
cy.getByTestId('schedule-item').first().find('input[type="checkbox"]').click()
cy.getByTestId('selected-count-badge').should('contain', '1')
})
})
describe('Download Progress', () => {
it('should show download progress', () => {
cy.intercept('GET', '**/api/schedule/export**', {
statusCode: 200,
body: {},
delay: 1000,
}).as('export')
cy.getByTestId('download-csv-option').click()
cy.getByTestId('download-progress-bar').should('be.visible')
cy.wait('@export')
cy.getByTestId('download-complete-message').should('be.visible')
})
it('should show file size before download', () => {
cy.getByTestId('download-settings-button').click()
cy.getByTestId('estimated-file-size').should('be.visible')
})
it('should allow cancel during download', () => {
cy.getByTestId('download-csv-option').click()
cy.getByTestId('cancel-download-button').click()
cy.getByTestId('download-cancelled-message').should('be.visible')
})
})
describe('Download Validation', () => {
it('should validate data before download', () => {
cy.getByTestId('download-csv-option').click()
cy.readFile('cypress/downloads/schedule.csv').then(content => {
const lines = content.split('\n')
expect(lines.length).to.be.greaterThan(1)
})
})
it('should handle empty schedule download', () => {
cy.intercept('GET', '**/api/schedule/search**', {
statusCode: 200,
body: { schedules: [] },
}).as('emptySearch')
cy.reload()
cy.getByTestId('download-csv-option').should('be.disabled')
})
it('should show file format info', () => {
cy.getByTestId('download-csv-option').trigger('mouseenter')
cy.getByTestId('format-info-tooltip').should('be.visible')
})
})
describe('Download History', () => {
it('should show download history', () => {
cy.getByTestId('download-history-button').click()
cy.getByTestId('download-history-list').should('be.visible')
})
it('should allow re-downloading from history', () => {
cy.getByTestId('download-csv-option').click()
cy.getByTestId('download-history-button').click()
cy.getByTestId('download-history-item').first().click()
cy.readFile('cypress/downloads/schedule.csv').should('exist')
})
it('should clear download history', () => {
cy.getByTestId('clear-history-button').click()
cy.getByTestId('download-history-list').should('contain', 'No downloads')
})
})
describe('Error Handling', () => {
it('should handle download failure', () => {
cy.intercept('GET', '**/api/schedule/export**', {
statusCode: 500,
body: { error: 'Export failed' },
}).as('exportError')
cy.getByTestId('download-csv-option').click()
cy.wait('@exportError')
cy.getByTestId('download-error-message').should('be.visible')
})
it('should handle network error', () => {
cy.intercept('GET', '**/api/schedule/export**', req => {
req.destroy()
}).as('networkError')
cy.getByTestId('download-csv-option').click()
cy.getByTestId('network-error-message').should('be.visible')
})
it('should handle timeout', () => {
cy.intercept('GET', '**/api/schedule/export**', {
statusCode: 200,
body: {},
delay: 30000,
}).as('timeout')
cy.getByTestId('download-csv-option').click()
cy.getByTestId('timeout-error-message').should('be.visible')
})
})
describe('Accessibility', () => {
it('should have accessible download button', () => {
cy.getByTestId('download-schedule-button').should('have.attr', 'aria-label')
})
it('should announce download status', () => {
cy.getByTestId('download-csv-option').click()
cy.getByTestId('download-status-announcement').should('have.attr', 'role', 'status')
})
})
})
@@ -0,0 +1,286 @@
import { uiHelpers } from '../../support/helpers/ui-helpers'
describe('Schedule - Filtering', () => {
beforeEach(() => {
cy.visit('http://localhost:3001/schedule')
cy.intercept('GET', '**/api/schedule/search**', {
statusCode: 200,
body: {
schedules: Array.from({ length: 20 }, (_, i) => ({
id: String(i),
flight: `SU${1000 + i}`,
days: i % 2 === 0 ? ['Mon', 'Wed', 'Fri'] : ['Tue', 'Thu', 'Sat', 'Sun'],
time: `${(10 + (i % 8))}:00`,
})),
},
}).as('scheduleSearch')
uiHelpers.fillInput('schedule-departure-input', 'SVO')
uiHelpers.fillInput('schedule-arrival-input', 'LED')
cy.getByTestId('schedule-search-button').click()
cy.wait('@scheduleSearch')
})
describe('Operating Days Filter', () => {
it('should filter by Monday', () => {
cy.getByTestId('filter-panel-button').click()
cy.getByTestId('monday-checkbox').click()
cy.getByTestId('apply-filter-button').click()
cy.getByTestId('schedule-item').should('have.length.greaterThan', 0)
})
it('should filter by weekdays', () => {
cy.getByTestId('filter-panel-button').click()
cy.getByTestId('weekdays-preset-button').click()
cy.getByTestId('apply-filter-button').click()
cy.getByTestId('schedule-item').should('have.length.greaterThan', 0)
})
it('should filter by weekends', () => {
cy.getByTestId('filter-panel-button').click()
cy.getByTestId('weekends-preset-button').click()
cy.getByTestId('apply-filter-button').click()
cy.getByTestId('schedule-item').should('have.length.greaterThan', 0)
})
it('should select daily flights', () => {
cy.getByTestId('filter-panel-button').click()
cy.getByTestId('daily-preset-button').click()
cy.getByTestId('apply-filter-button').click()
cy.getByTestId('schedule-item').should('have.length.greaterThan', 0)
})
it('should show selected days', () => {
cy.getByTestId('filter-panel-button').click()
cy.getByTestId('monday-checkbox').click()
cy.getByTestId('tuesday-checkbox').click()
cy.getByTestId('selected-days-display').should('contain', 'Mon')
cy.getByTestId('selected-days-display').should('contain', 'Tue')
})
})
describe('Time Range Filter', () => {
it('should filter by departure time', () => {
cy.getByTestId('filter-panel-button').click()
cy.getByTestId('time-range-tab').click()
cy.getByTestId('time-from-input').clear().type('08:00')
cy.getByTestId('time-to-input').clear().type('12:00')
cy.getByTestId('apply-filter-button').click()
cy.getByTestId('schedule-item').should('have.length.greaterThan', 0)
})
it('should use time slider', () => {
cy.getByTestId('filter-panel-button').click()
cy.getByTestId('time-range-tab').click()
cy.getByTestId('time-slider-from').invoke('val', 8).trigger('change')
cy.getByTestId('time-slider-to').invoke('val', 12).trigger('change')
cy.getByTestId('apply-filter-button').click()
cy.getByTestId('schedule-item').should('have.length.greaterThan', 0)
})
it('should show morning flights preset', () => {
cy.getByTestId('filter-panel-button').click()
cy.getByTestId('time-range-tab').click()
cy.getByTestId('morning-preset-button').click()
cy.getByTestId('apply-filter-button').click()
cy.getByTestId('schedule-item').should('have.length.greaterThan', 0)
})
it('should show evening flights preset', () => {
cy.getByTestId('filter-panel-button').click()
cy.getByTestId('time-range-tab').click()
cy.getByTestId('evening-preset-button').click()
cy.getByTestId('apply-filter-button').click()
cy.getByTestId('schedule-item').should('have.length.greaterThan', 0)
})
})
describe('Airline Filter', () => {
it('should filter by single airline', () => {
cy.getByTestId('filter-panel-button').click()
cy.getByTestId('airline-tab').click()
cy.getByTestId('airline-checkbox').first().click()
cy.getByTestId('apply-filter-button').click()
cy.getByTestId('schedule-item').should('have.length.greaterThan', 0)
})
it('should filter by multiple airlines', () => {
cy.getByTestId('filter-panel-button').click()
cy.getByTestId('airline-tab').click()
cy.getByTestId('airline-checkbox').first().click()
cy.getByTestId('airline-checkbox').eq(1).click()
cy.getByTestId('apply-filter-button').click()
cy.getByTestId('schedule-item').should('have.length.greaterThan', 0)
})
it('should show airline list with flight count', () => {
cy.getByTestId('filter-panel-button').click()
cy.getByTestId('airline-tab').click()
cy.getByTestId('airline-item').first().should('contain', '(')
})
it('should search airlines', () => {
cy.getByTestId('filter-panel-button').click()
cy.getByTestId('airline-tab').click()
cy.getByTestId('airline-search-input').clear().type('Aero')
cy.getByTestId('airline-item').should('have.length.lessThan', 10)
})
})
describe('Aircraft Type Filter', () => {
it('should filter by aircraft type', () => {
cy.getByTestId('filter-panel-button').click()
cy.getByTestId('aircraft-tab').click()
cy.getByTestId('aircraft-checkbox').first().click()
cy.getByTestId('apply-filter-button').click()
cy.getByTestId('schedule-item').should('have.length.greaterThan', 0)
})
it('should show aircraft list', () => {
cy.getByTestId('filter-panel-button').click()
cy.getByTestId('aircraft-tab').click()
cy.getByTestId('aircraft-item').should('have.length.greaterThan', 0)
})
})
describe('Multiple Filters', () => {
it('should apply multiple filters simultaneously', () => {
cy.getByTestId('filter-panel-button').click()
cy.getByTestId('monday-checkbox').click()
cy.getByTestId('time-range-tab').click()
cy.getByTestId('time-from-input').clear().type('10:00')
cy.getByTestId('apply-filter-button').click()
cy.getByTestId('schedule-item').should('have.length.greaterThan', 0)
})
it('should show active filter badges', () => {
cy.getByTestId('filter-panel-button').click()
cy.getByTestId('monday-checkbox').click()
cy.getByTestId('apply-filter-button').click()
cy.getByTestId('active-filter-badge').should('be.visible')
})
it('should count active filters', () => {
cy.getByTestId('filter-panel-button').click()
cy.getByTestId('monday-checkbox').click()
cy.getByTestId('tuesday-checkbox').click()
cy.getByTestId('apply-filter-button').click()
cy.getByTestId('filter-count-badge').should('contain', '2')
})
it('should reset all filters', () => {
cy.getByTestId('filter-panel-button').click()
cy.getByTestId('monday-checkbox').click()
cy.getByTestId('apply-filter-button').click()
cy.getByTestId('reset-all-filters-button').click()
cy.getByTestId('active-filter-badge').should('not.exist')
})
})
describe('Filter Persistence', () => {
it('should preserve filters on sort', () => {
cy.getByTestId('filter-panel-button').click()
cy.getByTestId('monday-checkbox').click()
cy.getByTestId('apply-filter-button').click()
cy.getByTestId('sort-by-departure').click()
cy.getByTestId('filter-count-badge').should('contain', '1')
})
it('should preserve filters on page change', () => {
cy.getByTestId('filter-panel-button').click()
cy.getByTestId('monday-checkbox').click()
cy.getByTestId('apply-filter-button').click()
cy.getByTestId('next-page-button').click()
cy.getByTestId('filter-count-badge').should('contain', '1')
})
it('should save filters to URL', () => {
cy.getByTestId('filter-panel-button').click()
cy.getByTestId('monday-checkbox').click()
cy.getByTestId('apply-filter-button').click()
cy.url().should('contain', 'monday')
})
it('should restore filters on page reload', () => {
cy.getByTestId('filter-panel-button').click()
cy.getByTestId('monday-checkbox').click()
cy.getByTestId('apply-filter-button').click()
cy.reload()
cy.getByTestId('filter-count-badge').should('contain', '1')
})
})
describe('Filter UI', () => {
it('should display filter panel', () => {
cy.getByTestId('filter-panel-button').click()
cy.getByTestId('filter-panel').should('be.visible')
})
it('should have tabs for different filters', () => {
cy.getByTestId('filter-panel-button').click()
cy.getByTestId('filter-tabs').should('be.visible')
cy.getByTestId('days-tab').should('be.visible')
cy.getByTestId('time-tab').should('be.visible')
})
it('should collapse filter panel', () => {
cy.getByTestId('filter-panel-button').click()
cy.getByTestId('close-filter-panel-button').click()
cy.getByTestId('filter-panel').should('not.be.visible')
})
it('should show selected filters summary', () => {
cy.getByTestId('filter-panel-button').click()
cy.getByTestId('monday-checkbox').click()
cy.getByTestId('filters-summary').should('contain', 'Mon')
})
})
describe('Filter Search', () => {
it('should search in airline filter', () => {
cy.getByTestId('filter-panel-button').click()
cy.getByTestId('airline-tab').click()
cy.getByTestId('airline-search').type('Aero')
cy.getByTestId('airline-item').should('have.length.lessThan', 20)
})
it('should clear search', () => {
cy.getByTestId('filter-panel-button').click()
cy.getByTestId('airline-tab').click()
cy.getByTestId('airline-search').type('Aero')
cy.getByTestId('clear-search-button').click()
cy.getByTestId('airline-search').should('have.value', '')
})
})
describe('Accessibility', () => {
it('should have accessible filter panel', () => {
cy.getByTestId('filter-panel-button').click()
cy.getByTestId('filter-panel').should('have.attr', 'role', 'dialog')
})
it('should support keyboard navigation', () => {
cy.getByTestId('filter-panel-button').focus()
cy.getByTestId('filter-panel-button').type('{enter}')
cy.getByTestId('filter-panel').should('be.visible')
})
it('should have clear filter labels', () => {
cy.getByTestId('filter-panel-button').click()
cy.getByTestId('monday-checkbox').should('have.attr', 'aria-label')
})
})
describe('Error Handling', () => {
it('should handle filter error', () => {
cy.intercept('GET', '**/api/schedule/search**', {
statusCode: 500,
body: { error: 'Filter failed' },
}).as('filterError')
cy.getByTestId('filter-panel-button').click()
cy.getByTestId('monday-checkbox').click()
cy.getByTestId('apply-filter-button').click()
cy.getByTestId('error-message').should('be.visible')
})
})
})
@@ -0,0 +1,209 @@
import { uiHelpers } from '../../support/helpers/ui-helpers'
import { apiHelpers } from '../../support/helpers/api-helpers'
describe('Schedule - Results Display', () => {
beforeEach(() => {
cy.visit('http://localhost:3001/schedule')
cy.intercept('GET', '**/api/schedule/search**', {
statusCode: 200,
body: {
schedules: Array.from({ length: 10 }, (_, i) => ({
id: String(i),
flight: `SU${1000 + i}`,
departure: '10:00',
arrival: '12:30',
days: ['Mon', 'Tue', 'Wed'],
airline: 'Aeroflot',
})),
},
}).as('scheduleSearch')
uiHelpers.fillInput('schedule-departure-input', 'SVO')
uiHelpers.fillInput('schedule-arrival-input', 'LED')
cy.getByTestId('schedule-search-button').click()
cy.wait('@scheduleSearch')
})
describe('Results Display', () => {
it('should display schedule results container', () => {
cy.getByTestId('schedule-results').should('be.visible')
})
it('should display multiple schedules', () => {
cy.getByTestId('schedule-item').should('have.length.greaterThan', 1)
})
it('should show flight number', () => {
cy.getByTestId('schedule-item').first().within(() => {
cy.getByTestId('flight-number').should('be.visible')
})
})
it('should show departure and arrival times', () => {
cy.getByTestId('schedule-item').first().within(() => {
cy.getByTestId('departure-time').should('be.visible')
cy.getByTestId('arrival-time').should('be.visible')
})
})
it('should display operating days', () => {
cy.getByTestId('schedule-item').first().within(() => {
cy.getByTestId('operating-days').should('be.visible')
})
})
it('should show airline info', () => {
cy.getByTestId('schedule-item').first().within(() => {
cy.getByTestId('airline-name').should('be.visible')
})
})
})
describe('Results Sorting', () => {
it('should sort by departure time', () => {
cy.getByTestId('sort-by-departure').click()
cy.getByTestId('schedule-item').first().within(() => {
cy.getByTestId('departure-time').should('be.visible')
})
})
it('should sort by arrival time', () => {
cy.getByTestId('sort-by-arrival').click()
cy.getByTestId('schedule-item').first().within(() => {
cy.getByTestId('arrival-time').should('be.visible')
})
})
it('should sort by duration', () => {
cy.getByTestId('sort-by-duration').click()
cy.getByTestId('schedule-item').should('have.length.greaterThan', 0)
})
it('should sort by airline', () => {
cy.getByTestId('sort-by-airline').click()
cy.getByTestId('schedule-item').first().within(() => {
cy.getByTestId('airline-name').should('be.visible')
})
})
})
describe('Results Filtering', () => {
it('should filter by operating day', () => {
cy.getByTestId('filter-by-day').click()
cy.getByTestId('monday-checkbox').click()
cy.getByTestId('apply-filter').click()
cy.getByTestId('schedule-item').should('have.length.greaterThan', 0)
})
it('should filter by airline', () => {
cy.getByTestId('filter-by-airline').click()
cy.getByTestId('airline-option').first().click()
cy.getByTestId('apply-filter').click()
cy.getByTestId('schedule-item').should('have.length.greaterThan', 0)
})
it('should filter by time range', () => {
cy.getByTestId('filter-by-time').click()
cy.getByTestId('time-from').clear().type('08:00')
cy.getByTestId('time-to').clear().type('14:00')
cy.getByTestId('apply-filter').click()
cy.getByTestId('schedule-item').should('have.length.greaterThan', 0)
})
it('should show active filter count', () => {
cy.getByTestId('filter-by-day').click()
cy.getByTestId('monday-checkbox').click()
cy.getByTestId('apply-filter').click()
cy.getByTestId('active-filter-badge').should('contain', '1')
})
it('should reset filters', () => {
cy.getByTestId('filter-by-day').click()
cy.getByTestId('monday-checkbox').click()
cy.getByTestId('apply-filter').click()
cy.getByTestId('reset-filters').click()
cy.getByTestId('active-filter-badge').should('not.exist')
})
})
describe('Results Pagination', () => {
it('should display pagination controls', () => {
cy.getByTestId('pagination-controls').should('be.visible')
})
it('should navigate to next page', () => {
cy.getByTestId('next-page-button').click()
cy.getByTestId('current-page').should('contain', '2')
})
it('should navigate to previous page', () => {
cy.getByTestId('next-page-button').click()
cy.getByTestId('prev-page-button').click()
cy.getByTestId('current-page').should('contain', '1')
})
it('should jump to specific page', () => {
cy.getByTestId('page-input').clear().type('3')
cy.getByTestId('go-button').click()
cy.getByTestId('current-page').should('contain', '3')
})
})
describe('Results Export', () => {
it('should export results to CSV', () => {
cy.getByTestId('export-csv-button').click()
cy.readFile('cypress/downloads/schedule.csv').should('exist')
})
it('should export results to PDF', () => {
cy.getByTestId('export-pdf-button').click()
cy.readFile('cypress/downloads/schedule.pdf').should('exist')
})
it('should print schedule results', () => {
cy.getByTestId('print-button').click()
cy.window().its('print').should('be.called')
})
})
describe('Results Actions', () => {
it('should expand schedule details', () => {
cy.getByTestId('schedule-item').first().click()
cy.getByTestId('schedule-details-expanded').should('be.visible')
})
it('should show book flight button', () => {
cy.getByTestId('schedule-item').first().within(() => {
cy.getByTestId('book-flight-button').should('be.visible')
})
})
it('should show add to favorites button', () => {
cy.getByTestId('schedule-item').first().within(() => {
cy.getByTestId('favorite-button').should('be.visible')
})
})
it('should add schedule to favorites', () => {
cy.getByTestId('schedule-item').first().within(() => {
cy.getByTestId('favorite-button').click()
cy.getByTestId('favorite-button').should('have.class', 'active')
})
})
})
describe('Results Accessibility', () => {
it('should have accessible results list', () => {
cy.getByTestId('schedule-list').should('have.attr', 'role', 'list')
})
it('should support keyboard navigation', () => {
cy.getByTestId('schedule-item').first().focus()
cy.getByTestId('schedule-item').first().type('{enter}')
cy.getByTestId('schedule-details-expanded').should('be.visible')
})
it('should announce result count', () => {
cy.getByTestId('results-count-announcement').should('have.attr', 'role', 'status')
})
})
})
@@ -0,0 +1,295 @@
import { uiHelpers } from '../../support/helpers/ui-helpers'
describe('Schedule - Route Display', () => {
beforeEach(() => {
cy.visit('http://localhost:3001/schedule')
cy.intercept('GET', '**/api/schedule/search**', {
statusCode: 200,
body: {
schedules: [
{
id: '1',
flight: 'SU123',
departure: { code: 'SVO', city: 'Moscow', time: '10:00' },
arrival: { code: 'LED', city: 'Saint Petersburg', time: '12:30' },
duration: '2h 30m',
stops: 0,
},
],
},
}).as('scheduleSearch')
uiHelpers.fillInput('schedule-departure-input', 'SVO')
uiHelpers.fillInput('schedule-arrival-input', 'LED')
cy.getByTestId('schedule-search-button').click()
cy.wait('@scheduleSearch')
})
describe('Route Information Display', () => {
it('should display departure airport code', () => {
cy.getByTestId('schedule-item').first().within(() => {
cy.getByTestId('departure-code').should('contain', 'SVO')
})
})
it('should display arrival airport code', () => {
cy.getByTestId('schedule-item').first().within(() => {
cy.getByTestId('arrival-code').should('contain', 'LED')
})
})
it('should display departure city', () => {
cy.getByTestId('schedule-item').first().within(() => {
cy.getByTestId('departure-city').should('contain', 'Moscow')
})
})
it('should display arrival city', () => {
cy.getByTestId('schedule-item').first().within(() => {
cy.getByTestId('arrival-city').should('contain', 'Saint Petersburg')
})
})
it('should display flight duration', () => {
cy.getByTestId('schedule-item').first().within(() => {
cy.getByTestId('flight-duration').should('contain', '2h 30m')
})
})
it('should display stops count', () => {
cy.getByTestId('schedule-item').first().within(() => {
cy.getByTestId('stops-count').should('contain', '0')
})
})
})
describe('Route Map Visualization', () => {
it('should display route map', () => {
cy.getByTestId('schedule-item').first().click()
cy.getByTestId('route-map-container').should('be.visible')
})
it('should show departure marker on map', () => {
cy.getByTestId('schedule-item').first().click()
cy.getByTestId('route-map').within(() => {
cy.getByTestId('departure-marker').should('be.visible')
})
})
it('should show arrival marker on map', () => {
cy.getByTestId('schedule-item').first().click()
cy.getByTestId('route-map').within(() => {
cy.getByTestId('arrival-marker').should('be.visible')
})
})
it('should draw flight path', () => {
cy.getByTestId('schedule-item').first().click()
cy.getByTestId('flight-path').should('be.visible')
})
it('should show route distance', () => {
cy.getByTestId('schedule-item').first().click()
cy.getByTestId('route-distance').should('be.visible')
})
it('should be interactive', () => {
cy.getByTestId('schedule-item').first().click()
cy.getByTestId('route-map').trigger('mouseenter')
cy.getByTestId('map-controls').should('be.visible')
})
})
describe('Route Details', () => {
it('should show detailed departure info', () => {
cy.getByTestId('schedule-item').first().click()
cy.getByTestId('departure-details').within(() => {
cy.getByTestId('airport-name').should('be.visible')
cy.getByTestId('airport-code').should('be.visible')
cy.getByTestId('city-name').should('be.visible')
})
})
it('should show detailed arrival info', () => {
cy.getByTestId('schedule-item').first().click()
cy.getByTestId('arrival-details').within(() => {
cy.getByTestId('airport-name').should('be.visible')
cy.getByTestId('airport-code').should('be.visible')
cy.getByTestId('city-name').should('be.visible')
})
})
it('should display route summary', () => {
cy.getByTestId('schedule-item').first().click()
cy.getByTestId('route-summary').should('be.visible')
})
it('should show flight number on route', () => {
cy.getByTestId('schedule-item').first().click()
cy.getByTestId('route-flight-number').should('contain', 'SU123')
})
})
describe('Multi-leg Routes', () => {
beforeEach(() => {
cy.intercept('GET', '**/api/schedule/search**', {
statusCode: 200,
body: {
schedules: [
{
id: '1',
flight: 'SU123 -> SU456',
stops: 1,
legs: [
{ from: 'SVO', to: 'DME', duration: '1h' },
{ from: 'DME', to: 'LED', duration: '1.5h' },
],
},
],
},
}).as('connectingFlights')
cy.reload()
uiHelpers.fillInput('schedule-departure-input', 'SVO')
uiHelpers.fillInput('schedule-arrival-input', 'LED')
cy.getByTestId('schedule-search-button').click()
cy.wait('@connectingFlights')
})
it('should display connecting flights', () => {
cy.getByTestId('schedule-item').first().click()
cy.getByTestId('legs-list').should('be.visible')
})
it('should show stop information', () => {
cy.getByTestId('schedule-item').first().click()
cy.getByTestId('stop-item').should('be.visible')
})
it('should display stop duration', () => {
cy.getByTestId('schedule-item').first().click()
cy.getByTestId('stop-item').first().within(() => {
cy.getByTestId('stop-duration').should('be.visible')
})
})
it('should show multi-leg route map', () => {
cy.getByTestId('schedule-item').first().click()
cy.getByTestId('route-map').should('be.visible')
cy.getByTestId('route-waypoint').should('have.length.greaterThan', 2)
})
})
describe('Route Comparison', () => {
it('should compare two routes', () => {
cy.getByTestId('schedule-item').first().find('input[type="checkbox"]').click()
cy.getByTestId('schedule-item').eq(1).find('input[type="checkbox"]').click()
cy.getByTestId('compare-routes-button').click()
cy.getByTestId('comparison-modal').should('be.visible')
})
it('should show side-by-side comparison', () => {
cy.getByTestId('schedule-item').first().find('input[type="checkbox"]').click()
cy.getByTestId('schedule-item').eq(1).find('input[type="checkbox"]').click()
cy.getByTestId('compare-routes-button').click()
cy.getByTestId('route-comparison-table').should('be.visible')
})
it('should highlight differences', () => {
cy.getByTestId('schedule-item').first().find('input[type="checkbox"]').click()
cy.getByTestId('schedule-item').eq(1).find('input[type="checkbox"]').click()
cy.getByTestId('compare-routes-button').click()
cy.getByTestId('difference-highlight').should('be.visible')
})
})
describe('Route Preferences', () => {
it('should allow saving favorite routes', () => {
cy.getByTestId('schedule-item').first().within(() => {
cy.getByTestId('favorite-route-button').click()
cy.getByTestId('favorite-route-button').should('have.class', 'active')
})
})
it('should show saved routes list', () => {
cy.getByTestId('saved-routes-button').click()
cy.getByTestId('saved-routes-list').should('be.visible')
})
it('should quick-search from saved routes', () => {
cy.getByTestId('saved-routes-button').click()
cy.getByTestId('saved-route-item').first().click()
cy.getByTestId('schedule-results').should('be.visible')
})
it('should remove saved route', () => {
cy.getByTestId('saved-routes-button').click()
cy.getByTestId('saved-route-item').first().within(() => {
cy.getByTestId('remove-saved-route-button').click()
})
cy.getByTestId('route-removed-message').should('be.visible')
})
})
describe('Route Statistics', () => {
it('should display average flight time', () => {
cy.getByTestId('route-statistics-button').click()
cy.getByTestId('average-duration').should('be.visible')
})
it('should show frequency of flights', () => {
cy.getByTestId('route-statistics-button').click()
cy.getByTestId('flight-frequency').should('be.visible')
})
it('should display on-time performance', () => {
cy.getByTestId('route-statistics-button').click()
cy.getByTestId('ontime-performance').should('be.visible')
})
it('should show busiest times', () => {
cy.getByTestId('route-statistics-button').click()
cy.getByTestId('busiest-times').should('be.visible')
})
})
describe('Route Accessibility', () => {
it('should have descriptive route labels', () => {
cy.getByTestId('schedule-item').first().within(() => {
cy.getByTestId('route-description').should('have.attr', 'aria-label')
})
})
it('should announce route changes', () => {
cy.getByTestId('schedule-item').first().click()
cy.getByTestId('route-update-announcement').should('have.attr', 'role', 'status')
})
it('should support keyboard navigation in map', () => {
cy.getByTestId('schedule-item').first().click()
cy.getByTestId('route-map').focus()
cy.getByTestId('route-map').type('{arrowup}')
cy.getByTestId('route-map').should('have.focus')
})
})
describe('Route Export', () => {
it('should export route details', () => {
cy.getByTestId('schedule-item').first().click()
cy.getByTestId('export-route-button').click()
cy.getByTestId('export-options').should('be.visible')
})
it('should export as PDF', () => {
cy.getByTestId('schedule-item').first().click()
cy.getByTestId('export-route-button').click()
cy.getByTestId('export-pdf-option').click()
cy.readFile('cypress/downloads/route.pdf').should('exist')
})
it('should print route', () => {
cy.getByTestId('schedule-item').first().click()
cy.getByTestId('export-route-button').click()
cy.getByTestId('print-route-option').click()
cy.window().its('print').should('be.called')
})
})
})
@@ -0,0 +1,213 @@
import { uiHelpers } from '../../support/helpers/ui-helpers'
import { apiHelpers } from '../../support/helpers/api-helpers'
import { dataHelpers } from '../../support/helpers/data-helpers'
describe('Schedule - Search', () => {
beforeEach(() => {
cy.visit('http://localhost:3001/schedule')
cy.intercept('GET', '**/api/schedule/search**', {
statusCode: 200,
body: {
schedules: [
{ id: '1', route: 'SVO-LED', days: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'] },
],
},
}).as('scheduleSearch')
})
describe('Schedule Search Interface', () => {
it('should display schedule search form', () => {
cy.getByTestId('schedule-search-form').should('be.visible')
})
it('should have departure city input', () => {
cy.getByTestId('schedule-departure-input').should('be.visible')
})
it('should have arrival city input', () => {
cy.getByTestId('schedule-arrival-input').should('be.visible')
})
it('should have search button', () => {
cy.getByTestId('schedule-search-button').should('be.visible')
})
it('should search schedule by route', () => {
uiHelpers.fillInput('schedule-departure-input', 'SVO')
uiHelpers.fillInput('schedule-arrival-input', 'LED')
cy.getByTestId('schedule-search-button').click()
cy.wait('@scheduleSearch')
cy.getByTestId('schedule-results').should('be.visible')
})
it('should validate required fields', () => {
cy.getByTestId('schedule-search-button').click()
cy.getByTestId('validation-error').should('be.visible')
})
it('should clear search filters', () => {
uiHelpers.fillInput('schedule-departure-input', 'SVO')
cy.getByTestId('schedule-clear-button').click()
cy.getByTestId('schedule-departure-input').should('have.value', '')
})
})
describe('Schedule Search Results', () => {
beforeEach(() => {
uiHelpers.fillInput('schedule-departure-input', 'SVO')
uiHelpers.fillInput('schedule-arrival-input', 'LED')
cy.getByTestId('schedule-search-button').click()
cy.wait('@scheduleSearch')
})
it('should display schedule list', () => {
cy.getByTestId('schedule-list').should('be.visible')
})
it('should show schedule items', () => {
cy.getByTestId('schedule-item').should('have.length.greaterThan', 0)
})
it('should display flight number', () => {
cy.getByTestId('schedule-item').first().within(() => {
cy.getByTestId('schedule-flight-number').should('be.visible')
})
})
it('should show departure time', () => {
cy.getByTestId('schedule-item').first().within(() => {
cy.getByTestId('schedule-departure-time').should('be.visible')
})
})
it('should show arrival time', () => {
cy.getByTestId('schedule-item').first().within(() => {
cy.getByTestId('schedule-arrival-time').should('be.visible')
})
})
it('should display operating days', () => {
cy.getByTestId('schedule-item').first().within(() => {
cy.getByTestId('operating-days').should('be.visible')
})
})
})
describe('Schedule Filters', () => {
beforeEach(() => {
uiHelpers.fillInput('schedule-departure-input', 'SVO')
uiHelpers.fillInput('schedule-arrival-input', 'LED')
cy.getByTestId('schedule-search-button').click()
cy.wait('@scheduleSearch')
})
it('should filter by airline', () => {
cy.getByTestId('filter-by-airline-button').click()
cy.getByTestId('airline-checkbox').first().click()
cy.getByTestId('apply-filter-button').click()
cy.getByTestId('schedule-item').should('have.length.greaterThan', 0)
})
it('should filter by departure time', () => {
cy.getByTestId('filter-by-time-button').click()
cy.getByTestId('time-range-start').clear().type('10:00')
cy.getByTestId('apply-filter-button').click()
cy.getByTestId('schedule-item').should('have.length.greaterThan', 0)
})
it('should filter by operating days', () => {
cy.getByTestId('filter-by-days-button').click()
cy.getByTestId('monday-checkbox').click()
cy.getByTestId('apply-filter-button').click()
cy.getByTestId('schedule-item').should('have.length.greaterThan', 0)
})
})
describe('Advanced Search Options', () => {
it('should search by airline', () => {
cy.getByTestId('search-by-airline-option').click()
cy.getByTestId('airline-select').should('be.visible')
cy.getByTestId('airline-select').select('Aeroflot')
cy.getByTestId('schedule-search-button').click()
cy.wait('@scheduleSearch')
})
it('should search by aircraft type', () => {
cy.getByTestId('search-by-aircraft-option').click()
cy.getByTestId('aircraft-select').should('be.visible')
cy.getByTestId('aircraft-select').select('A320')
cy.getByTestId('schedule-search-button').click()
cy.wait('@scheduleSearch')
})
it('should search by service class', () => {
cy.getByTestId('search-by-class-option').click()
cy.getByTestId('service-class-select').should('be.visible')
cy.getByTestId('service-class-select').select('Business')
cy.getByTestId('schedule-search-button').click()
cy.wait('@scheduleSearch')
})
})
describe('Search History', () => {
it('should display search history', () => {
cy.getByTestId('search-history-section').should('be.visible')
})
it('should allow quick search from history', () => {
uiHelpers.fillInput('schedule-departure-input', 'SVO')
uiHelpers.fillInput('schedule-arrival-input', 'LED')
cy.getByTestId('schedule-search-button').click()
cy.wait('@scheduleSearch')
cy.getByTestId('search-history-item').first().click()
cy.getByTestId('schedule-results').should('be.visible')
})
it('should clear search history', () => {
cy.getByTestId('clear-history-button').click()
cy.getByTestId('search-history-empty').should('be.visible')
})
})
describe('Error Handling', () => {
it('should handle search error', () => {
cy.intercept('GET', '**/api/schedule/search**', {
statusCode: 500,
body: { error: 'Search failed' },
}).as('searchError')
uiHelpers.fillInput('schedule-departure-input', 'SVO')
uiHelpers.fillInput('schedule-arrival-input', 'LED')
cy.getByTestId('schedule-search-button').click()
cy.wait('@searchError')
cy.getByTestId('error-message').should('be.visible')
})
it('should handle no results', () => {
cy.intercept('GET', '**/api/schedule/search**', {
statusCode: 200,
body: { schedules: [] },
}).as('emptySearch')
uiHelpers.fillInput('schedule-departure-input', 'XXX')
uiHelpers.fillInput('schedule-arrival-input', 'YYY')
cy.getByTestId('schedule-search-button').click()
cy.wait('@emptySearch')
cy.getByTestId('no-results-message').should('be.visible')
})
})
describe('Accessibility', () => {
it('should have accessible search form', () => {
cy.getByTestId('schedule-search-form').should('have.attr', 'role', 'search')
})
it('should support keyboard navigation', () => {
cy.getByTestId('schedule-departure-input').focus()
cy.getByTestId('schedule-departure-input').type('SVO')
cy.getByTestId('schedule-departure-input').type('{tab}')
cy.getByTestId('schedule-arrival-input').should('have.focus')
})
})
})