60e2149072
Tasks 16-20: Online Board Tests (Search/Filter, Tabs, Flight List, Details Modal, Time/Date) - Task 16: Search & Filter tests (37 tests) - departure/arrival cities, passenger count, cabin class - Task 17: Arrival/Departure Tabs tests (45 tests) - tab switching, flight display, sorting - Task 18: Flight List View tests (50 tests) - display, sorting, filtering, pagination, loading states - Task 19: Flight Details Modal tests (40 tests) - opening/closing, content display, actions - Task 20: Time & Date Filter tests (43 tests) - date selection, time ranges, calendar navigation Tasks 21-25: Flight Details Tests (Flight Info, Passengers, Seats, Services, Fares) - Task 21: Flight Info Display tests (40 tests) - basic info, airports, route visualization, timeline - Task 22: Passenger Info tests (50 tests) - passenger list, details, services, special requirements - Task 23: Seat Selection tests (50 tests) - seat map, selection, categories, recommendations - Task 24: Service Selection tests (25 tests) - baggage, meals, seats, summary - Task 25: Fare Display tests (55 tests) - fare breakdown, comparisons, discounts, refunds All tests follow AAA pattern and use data-testid selectors matching Angular version. Total: 245 tests across 10 feature suites.
440 lines
13 KiB
TypeScript
440 lines
13 KiB
TypeScript
import { uiHelpers } from '../../support/helpers/ui-helpers'
|
|
import { apiHelpers } from '../../support/helpers/api-helpers'
|
|
import { dataHelpers } from '../../support/helpers/data-helpers'
|
|
|
|
describe('Online Board - Arrival/Departure Tabs', () => {
|
|
beforeEach(() => {
|
|
cy.visit('http://localhost:3001')
|
|
apiHelpers.mockFlightSearch()
|
|
})
|
|
|
|
describe('Tab Navigation - Happy Path', () => {
|
|
it('should display departure flights tab by default', () => {
|
|
// Arrange & Act
|
|
uiHelpers.fillInput('departure-input', 'SVO')
|
|
uiHelpers.fillInput('arrival-input', 'LED')
|
|
cy.getByTestId('search-button').click()
|
|
cy.wait('@flightSearch')
|
|
|
|
// Assert
|
|
cy.getByTestId('departure-tab').should('have.class', 'active')
|
|
cy.getByTestId('departure-flights-list').should('be.visible')
|
|
})
|
|
|
|
it('should switch to arrivals tab', () => {
|
|
// Arrange
|
|
uiHelpers.fillInput('departure-input', 'SVO')
|
|
uiHelpers.fillInput('arrival-input', 'LED')
|
|
cy.getByTestId('search-button').click()
|
|
cy.wait('@flightSearch')
|
|
|
|
// Act
|
|
cy.getByTestId('arrival-tab').click()
|
|
|
|
// Assert
|
|
cy.getByTestId('arrival-tab').should('have.class', 'active')
|
|
cy.getByTestId('departure-tab').should('not.have.class', 'active')
|
|
cy.getByTestId('arrival-flights-list').should('be.visible')
|
|
})
|
|
|
|
it('should switch back to departures tab', () => {
|
|
// Arrange
|
|
uiHelpers.fillInput('departure-input', 'SVO')
|
|
uiHelpers.fillInput('arrival-input', 'LED')
|
|
cy.getByTestId('search-button').click()
|
|
cy.wait('@flightSearch')
|
|
cy.getByTestId('arrival-tab').click()
|
|
|
|
// Act
|
|
cy.getByTestId('departure-tab').click()
|
|
|
|
// Assert
|
|
cy.getByTestId('departure-tab').should('have.class', 'active')
|
|
cy.getByTestId('departure-flights-list').should('be.visible')
|
|
})
|
|
|
|
it('should preserve tab state when filtering results', () => {
|
|
// Arrange
|
|
uiHelpers.fillInput('departure-input', 'SVO')
|
|
uiHelpers.fillInput('arrival-input', 'LED')
|
|
cy.getByTestId('search-button').click()
|
|
cy.wait('@flightSearch')
|
|
cy.getByTestId('arrival-tab').click()
|
|
|
|
// Act
|
|
cy.getByTestId('sort-by-time').click()
|
|
|
|
// Assert
|
|
cy.getByTestId('arrival-tab').should('have.class', 'active')
|
|
})
|
|
|
|
it('should show flight count in tab labels', () => {
|
|
// Arrange & Act
|
|
uiHelpers.fillInput('departure-input', 'SVO')
|
|
uiHelpers.fillInput('arrival-input', 'LED')
|
|
cy.getByTestId('search-button').click()
|
|
cy.wait('@flightSearch')
|
|
|
|
// Assert
|
|
cy.getByTestId('departure-tab').should('contain', '(')
|
|
cy.getByTestId('departure-tab').should('contain', ')')
|
|
})
|
|
})
|
|
|
|
describe('Departure Tab - Flight Display', () => {
|
|
beforeEach(() => {
|
|
uiHelpers.fillInput('departure-input', 'SVO')
|
|
uiHelpers.fillInput('arrival-input', 'LED')
|
|
cy.getByTestId('search-button').click()
|
|
cy.wait('@flightSearch')
|
|
})
|
|
|
|
it('should list all departure flights', () => {
|
|
// Arrange & Act
|
|
cy.getByTestId('departure-tab').click()
|
|
|
|
// Assert
|
|
cy.getByTestId('flight-item').should('have.length.greaterThan', 0)
|
|
})
|
|
|
|
it('should display departure time in flight item', () => {
|
|
// Arrange & Act
|
|
cy.getByTestId('departure-tab').click()
|
|
|
|
// Assert
|
|
cy.getByTestId('flight-item').first().within(() => {
|
|
cy.getByTestId('departure-time').should('be.visible')
|
|
})
|
|
})
|
|
|
|
it('should display arrival time in flight item', () => {
|
|
// Arrange & Act
|
|
cy.getByTestId('departure-tab').click()
|
|
|
|
// Assert
|
|
cy.getByTestId('flight-item').first().within(() => {
|
|
cy.getByTestId('arrival-time').should('be.visible')
|
|
})
|
|
})
|
|
|
|
it('should display flight duration', () => {
|
|
// Arrange & Act
|
|
cy.getByTestId('departure-tab').click()
|
|
|
|
// Assert
|
|
cy.getByTestId('flight-item').first().within(() => {
|
|
cy.getByTestId('flight-duration').should('be.visible')
|
|
})
|
|
})
|
|
|
|
it('should display flight number', () => {
|
|
// Arrange & Act
|
|
cy.getByTestId('departure-tab').click()
|
|
|
|
// Assert
|
|
cy.getByTestId('flight-item').first().within(() => {
|
|
cy.getByTestId('flight-number').should('be.visible')
|
|
})
|
|
})
|
|
|
|
it('should display airline logo', () => {
|
|
// Arrange & Act
|
|
cy.getByTestId('departure-tab').click()
|
|
|
|
// Assert
|
|
cy.getByTestId('flight-item').first().within(() => {
|
|
cy.getByTestId('airline-logo').should('be.visible')
|
|
})
|
|
})
|
|
|
|
it('should display price in flight item', () => {
|
|
// Arrange & Act
|
|
cy.getByTestId('departure-tab').click()
|
|
|
|
// Assert
|
|
cy.getByTestId('flight-item').first().within(() => {
|
|
cy.getByTestId('flight-price').should('be.visible')
|
|
})
|
|
})
|
|
|
|
it('should display stops information', () => {
|
|
// Arrange & Act
|
|
cy.getByTestId('departure-tab').click()
|
|
|
|
// Assert
|
|
cy.getByTestId('flight-item').first().within(() => {
|
|
cy.getByTestId('stops-info').should('be.visible')
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('Arrival Tab - Flight Display', () => {
|
|
beforeEach(() => {
|
|
uiHelpers.fillInput('departure-input', 'SVO')
|
|
uiHelpers.fillInput('arrival-input', 'LED')
|
|
cy.getByTestId('search-button').click()
|
|
cy.wait('@flightSearch')
|
|
cy.getByTestId('arrival-tab').click()
|
|
})
|
|
|
|
it('should list all arrival flights', () => {
|
|
// Arrange & Act
|
|
cy.getByTestId('arrival-flights-list').should('be.visible')
|
|
|
|
// Assert
|
|
cy.getByTestId('flight-item').should('have.length.greaterThan', 0)
|
|
})
|
|
|
|
it('should display arrival tab as active', () => {
|
|
// Assert
|
|
cy.getByTestId('arrival-tab').should('have.class', 'active')
|
|
})
|
|
|
|
it('should show different flights in arrival tab', () => {
|
|
// Arrange
|
|
cy.getByTestId('departure-tab').click()
|
|
cy.getByTestId('flight-item').first().then($dep => {
|
|
const depFlight = $dep.text()
|
|
|
|
// Act
|
|
cy.getByTestId('arrival-tab').click()
|
|
|
|
// Assert
|
|
cy.getByTestId('flight-item').first().should('not.contain', depFlight)
|
|
})
|
|
})
|
|
|
|
it('should display return flight details', () => {
|
|
// Assert
|
|
cy.getByTestId('flight-item').first().within(() => {
|
|
cy.getByTestId('departure-time').should('be.visible')
|
|
cy.getByTestId('arrival-time').should('be.visible')
|
|
cy.getByTestId('flight-number').should('be.visible')
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('Tab Switching with Sorting', () => {
|
|
beforeEach(() => {
|
|
uiHelpers.fillInput('departure-input', 'SVO')
|
|
uiHelpers.fillInput('arrival-input', 'LED')
|
|
cy.getByTestId('search-button').click()
|
|
cy.wait('@flightSearch')
|
|
})
|
|
|
|
it('should maintain sort order when switching tabs', () => {
|
|
// Arrange
|
|
cy.getByTestId('sort-by-price').click()
|
|
|
|
// Act
|
|
cy.getByTestId('arrival-tab').click()
|
|
cy.getByTestId('departure-tab').click()
|
|
|
|
// Assert
|
|
cy.getByTestId('departure-tab').should('have.class', 'active')
|
|
cy.getByTestId('sort-by-price').should('have.class', 'active')
|
|
})
|
|
|
|
it('should apply different sorts to each tab', () => {
|
|
// Arrange
|
|
cy.getByTestId('sort-by-time').click()
|
|
|
|
// Act
|
|
cy.getByTestId('arrival-tab').click()
|
|
cy.getByTestId('sort-by-price').click()
|
|
cy.getByTestId('departure-tab').click()
|
|
|
|
// Assert
|
|
cy.getByTestId('sort-by-time').should('have.class', 'active')
|
|
})
|
|
|
|
it('should refresh flight list when tab changes', () => {
|
|
// Arrange
|
|
cy.intercept('GET', '**/api/flights/**', {
|
|
statusCode: 200,
|
|
body: { flights: [] },
|
|
}).as('flightFetch')
|
|
|
|
// Act
|
|
cy.getByTestId('arrival-tab').click()
|
|
|
|
// Assert
|
|
cy.wait('@flightFetch')
|
|
})
|
|
})
|
|
|
|
describe('Tab UI Elements', () => {
|
|
it('should highlight active tab', () => {
|
|
// Arrange & Act
|
|
uiHelpers.fillInput('departure-input', 'SVO')
|
|
uiHelpers.fillInput('arrival-input', 'LED')
|
|
cy.getByTestId('search-button').click()
|
|
cy.wait('@flightSearch')
|
|
|
|
// Assert
|
|
cy.getByTestId('departure-tab').should('have.class', 'active')
|
|
cy.getByTestId('arrival-tab').should('not.have.class', 'active')
|
|
})
|
|
|
|
it('should have visible tab buttons', () => {
|
|
// Arrange & Act
|
|
uiHelpers.fillInput('departure-input', 'SVO')
|
|
uiHelpers.fillInput('arrival-input', 'LED')
|
|
cy.getByTestId('search-button').click()
|
|
cy.wait('@flightSearch')
|
|
|
|
// Assert
|
|
cy.getByTestId('departure-tab').should('be.visible')
|
|
cy.getByTestId('arrival-tab').should('be.visible')
|
|
})
|
|
|
|
it('should show tab indicator line', () => {
|
|
// Arrange & Act
|
|
uiHelpers.fillInput('departure-input', 'SVO')
|
|
uiHelpers.fillInput('arrival-input', 'LED')
|
|
cy.getByTestId('search-button').click()
|
|
cy.wait('@flightSearch')
|
|
|
|
// Assert
|
|
cy.getByTestId('tab-indicator').should('be.visible')
|
|
})
|
|
})
|
|
|
|
describe('Edge Cases - Tab Behavior', () => {
|
|
it('should handle empty departure flights', () => {
|
|
// Arrange
|
|
cy.intercept('GET', '**/api/flights/**', {
|
|
statusCode: 200,
|
|
body: { flights: [] },
|
|
}).as('emptySearch')
|
|
|
|
// Act
|
|
uiHelpers.fillInput('departure-input', 'SVO')
|
|
uiHelpers.fillInput('arrival-input', 'LED')
|
|
cy.getByTestId('search-button').click()
|
|
cy.wait('@emptySearch')
|
|
|
|
// Assert
|
|
cy.getByTestId('no-flights-message').should('be.visible')
|
|
})
|
|
|
|
it('should handle tab switching with pending requests', () => {
|
|
// Arrange
|
|
cy.intercept('GET', '**/api/flights/**', {
|
|
statusCode: 200,
|
|
body: { flights: [] },
|
|
delay: 2000,
|
|
}).as('slowSearch')
|
|
|
|
// Act
|
|
uiHelpers.fillInput('departure-input', 'SVO')
|
|
uiHelpers.fillInput('arrival-input', 'LED')
|
|
cy.getByTestId('search-button').click()
|
|
cy.wait(100)
|
|
cy.getByTestId('arrival-tab').click()
|
|
|
|
// Assert
|
|
cy.getByTestId('loading-spinner').should('be.visible')
|
|
})
|
|
|
|
it('should restore tab state after error', () => {
|
|
// Arrange
|
|
cy.intercept('GET', '**/api/flights/**', {
|
|
statusCode: 500,
|
|
body: { error: 'Server error' },
|
|
}).as('errorSearch')
|
|
|
|
// Act
|
|
uiHelpers.fillInput('departure-input', 'SVO')
|
|
uiHelpers.fillInput('arrival-input', 'LED')
|
|
cy.getByTestId('search-button').click()
|
|
cy.wait('@errorSearch')
|
|
cy.getByTestId('arrival-tab').click()
|
|
|
|
// Assert
|
|
cy.getByTestId('error-message').should('be.visible')
|
|
cy.getByTestId('arrival-tab').should('have.class', 'active')
|
|
})
|
|
|
|
it('should handle rapid tab switching', () => {
|
|
// Arrange
|
|
uiHelpers.fillInput('departure-input', 'SVO')
|
|
uiHelpers.fillInput('arrival-input', 'LED')
|
|
cy.getByTestId('search-button').click()
|
|
cy.wait('@flightSearch')
|
|
|
|
// Act
|
|
cy.getByTestId('arrival-tab').click()
|
|
cy.getByTestId('departure-tab').click()
|
|
cy.getByTestId('arrival-tab').click()
|
|
cy.getByTestId('departure-tab').click()
|
|
|
|
// Assert
|
|
cy.getByTestId('departure-tab').should('have.class', 'active')
|
|
cy.getByTestId('departure-flights-list').should('be.visible')
|
|
})
|
|
|
|
it('should scroll to top when switching tabs', () => {
|
|
// Arrange
|
|
uiHelpers.fillInput('departure-input', 'SVO')
|
|
uiHelpers.fillInput('arrival-input', 'LED')
|
|
cy.getByTestId('search-button').click()
|
|
cy.wait('@flightSearch')
|
|
cy.getByTestId('flight-item').last().scrollIntoView()
|
|
|
|
// Act
|
|
cy.getByTestId('arrival-tab').click()
|
|
|
|
// Assert
|
|
cy.window().then(win => {
|
|
expect(win.scrollY).to.equal(0)
|
|
})
|
|
})
|
|
|
|
it('should preserve tab selection on page refresh', () => {
|
|
// Arrange
|
|
uiHelpers.fillInput('departure-input', 'SVO')
|
|
uiHelpers.fillInput('arrival-input', 'LED')
|
|
cy.getByTestId('search-button').click()
|
|
cy.wait('@flightSearch')
|
|
cy.getByTestId('arrival-tab').click()
|
|
|
|
// Act
|
|
cy.reload()
|
|
|
|
// Assert
|
|
cy.getByTestId('arrival-tab').should('have.class', 'active')
|
|
})
|
|
})
|
|
|
|
describe('Accessibility - Tab Navigation', () => {
|
|
beforeEach(() => {
|
|
uiHelpers.fillInput('departure-input', 'SVO')
|
|
uiHelpers.fillInput('arrival-input', 'LED')
|
|
cy.getByTestId('search-button').click()
|
|
cy.wait('@flightSearch')
|
|
})
|
|
|
|
it('should have proper tab roles', () => {
|
|
// Assert
|
|
cy.getByTestId('departure-tab').should('have.attr', 'role', 'tab')
|
|
cy.getByTestId('arrival-tab').should('have.attr', 'role', 'tab')
|
|
})
|
|
|
|
it('should support keyboard navigation', () => {
|
|
// Act
|
|
cy.getByTestId('departure-tab').focus()
|
|
cy.getByTestId('departure-tab').type('{rightarrow}')
|
|
|
|
// Assert
|
|
cy.getByTestId('arrival-tab').should('have.focus')
|
|
})
|
|
|
|
it('should announce active tab to screen readers', () => {
|
|
// Assert
|
|
cy.getByTestId('departure-tab').should('have.attr', 'aria-selected', 'true')
|
|
cy.getByTestId('arrival-tab').should('have.attr', 'aria-selected', 'false')
|
|
})
|
|
})
|
|
})
|