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.
459 lines
13 KiB
TypeScript
459 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 - Flight List View', () => {
|
|
beforeEach(() => {
|
|
cy.visit('http://localhost:3001')
|
|
apiHelpers.mockFlightSearch()
|
|
uiHelpers.fillInput('departure-input', 'SVO')
|
|
uiHelpers.fillInput('arrival-input', 'LED')
|
|
cy.getByTestId('search-button').click()
|
|
cy.wait('@flightSearch')
|
|
})
|
|
|
|
describe('Flight List Display', () => {
|
|
it('should display flight list container', () => {
|
|
// Assert
|
|
cy.getByTestId('flight-results-container').should('be.visible')
|
|
})
|
|
|
|
it('should display multiple flight items', () => {
|
|
// Assert
|
|
cy.getByTestId('flight-item').should('have.length.greaterThan', 1)
|
|
})
|
|
|
|
it('should display flight in correct order', () => {
|
|
// Act
|
|
cy.getByTestId('flight-item').then($items => {
|
|
const times = []
|
|
$items.each((i, el) => {
|
|
times.push(el.textContent)
|
|
})
|
|
|
|
// Assert
|
|
expect(times.length).to.be.greaterThan(0)
|
|
})
|
|
})
|
|
|
|
it('should display each flight with required fields', () => {
|
|
// Assert
|
|
cy.getByTestId('flight-item').first().within(() => {
|
|
cy.getByTestId('flight-number').should('be.visible')
|
|
cy.getByTestId('departure-time').should('be.visible')
|
|
cy.getByTestId('arrival-time').should('be.visible')
|
|
cy.getByTestId('airline-logo').should('be.visible')
|
|
})
|
|
})
|
|
|
|
it('should show flight status', () => {
|
|
// Assert
|
|
cy.getByTestId('flight-item').first().within(() => {
|
|
cy.getByTestId('flight-status').should('be.visible')
|
|
})
|
|
})
|
|
|
|
it('should show price per passenger', () => {
|
|
// Assert
|
|
cy.getByTestId('flight-item').first().within(() => {
|
|
cy.getByTestId('flight-price').should('be.visible')
|
|
cy.getByTestId('flight-price').should('contain', '₽')
|
|
})
|
|
})
|
|
|
|
it('should show total price for group', () => {
|
|
// Assert
|
|
cy.getByTestId('flight-item').first().within(() => {
|
|
cy.getByTestId('total-price').should('be.visible')
|
|
})
|
|
})
|
|
})
|
|
|
|
describe('Flight List Sorting', () => {
|
|
it('should sort by departure time ascending', () => {
|
|
// Act
|
|
cy.getByTestId('sort-by-time').click()
|
|
|
|
// Assert
|
|
cy.getByTestId('departure-time').first().then($first => {
|
|
const firstTime = $first.text()
|
|
cy.getByTestId('departure-time').last().then($last => {
|
|
const lastTime = $last.text()
|
|
expect(firstTime).to.be.lessThan(lastTime)
|
|
})
|
|
})
|
|
})
|
|
|
|
it('should sort by price ascending', () => {
|
|
// Act
|
|
cy.getByTestId('sort-by-price').click()
|
|
|
|
// Assert
|
|
cy.getByTestId('flight-price').first().then($first => {
|
|
const firstPrice = parseFloat($first.text())
|
|
cy.getByTestId('flight-price').eq(1).then($second => {
|
|
const secondPrice = parseFloat($second.text())
|
|
expect(firstPrice).to.be.lessThan(secondPrice)
|
|
})
|
|
})
|
|
})
|
|
|
|
it('should sort by duration', () => {
|
|
// Act
|
|
cy.getByTestId('sort-by-duration').click()
|
|
|
|
// Assert
|
|
cy.getByTestId('flight-duration').should('be.visible')
|
|
})
|
|
|
|
it('should toggle sort direction', () => {
|
|
// Act
|
|
cy.getByTestId('sort-by-price').click()
|
|
cy.getByTestId('sort-direction-toggle').click()
|
|
|
|
// Assert
|
|
cy.getByTestId('sort-direction-toggle').should('have.class', 'descending')
|
|
})
|
|
|
|
it('should show sort indicator', () => {
|
|
// Act
|
|
cy.getByTestId('sort-by-time').click()
|
|
|
|
// Assert
|
|
cy.getByTestId('sort-by-time').should('have.class', 'active')
|
|
})
|
|
|
|
it('should show sort direction icon', () => {
|
|
// Act
|
|
cy.getByTestId('sort-by-price').click()
|
|
|
|
// Assert
|
|
cy.getByTestId('sort-arrow-icon').should('be.visible')
|
|
})
|
|
})
|
|
|
|
describe('Flight List Filtering', () => {
|
|
it('should filter by departure time range', () => {
|
|
// Act
|
|
cy.getByTestId('filter-by-time').click()
|
|
cy.getByTestId('time-range-start').clear().type('10:00')
|
|
cy.getByTestId('time-range-end').clear().type('14:00')
|
|
cy.getByTestId('apply-filter-button').click()
|
|
|
|
// Assert
|
|
cy.getByTestId('flight-item').each($flight => {
|
|
cy.wrap($flight).within(() => {
|
|
cy.getByTestId('departure-time').should('be.visible')
|
|
})
|
|
})
|
|
})
|
|
|
|
it('should filter by price range', () => {
|
|
// Act
|
|
cy.getByTestId('filter-by-price').click()
|
|
cy.getByTestId('price-min').clear().type('5000')
|
|
cy.getByTestId('price-max').clear().type('15000')
|
|
cy.getByTestId('apply-filter-button').click()
|
|
|
|
// Assert
|
|
cy.getByTestId('flight-item').should('have.length.greaterThan', 0)
|
|
})
|
|
|
|
it('should filter by number of stops', () => {
|
|
// Act
|
|
cy.getByTestId('filter-by-stops').click()
|
|
cy.getByTestId('nonstop-option').click()
|
|
|
|
// Assert
|
|
cy.getByTestId('flight-item').each($flight => {
|
|
cy.wrap($flight).within(() => {
|
|
cy.getByTestId('stops-info').should('contain', '0')
|
|
})
|
|
})
|
|
})
|
|
|
|
it('should filter by airline', () => {
|
|
// Act
|
|
cy.getByTestId('filter-by-airline').click()
|
|
cy.getByTestId('airline-checkbox').first().click()
|
|
cy.getByTestId('apply-filter-button').click()
|
|
|
|
// Assert
|
|
cy.getByTestId('flight-item').should('have.length.greaterThan', 0)
|
|
})
|
|
|
|
it('should reset all filters', () => {
|
|
// Arrange
|
|
cy.getByTestId('filter-by-price').click()
|
|
cy.getByTestId('price-min').clear().type('5000')
|
|
cy.getByTestId('apply-filter-button').click()
|
|
|
|
// Act
|
|
cy.getByTestId('reset-filters-button').click()
|
|
|
|
// Assert
|
|
cy.getByTestId('flight-item').should('have.length.greaterThan', 1)
|
|
})
|
|
|
|
it('should show active filter count', () => {
|
|
// Act
|
|
cy.getByTestId('filter-by-stops').click()
|
|
cy.getByTestId('nonstop-option').click()
|
|
|
|
// Assert
|
|
cy.getByTestId('active-filters-badge').should('contain', '1')
|
|
})
|
|
})
|
|
|
|
describe('Flight Item Interaction', () => {
|
|
it('should expand flight item on click', () => {
|
|
// Act
|
|
cy.getByTestId('flight-item').first().click()
|
|
|
|
// Assert
|
|
cy.getByTestId('flight-details-expanded').should('be.visible')
|
|
})
|
|
|
|
it('should show additional details when expanded', () => {
|
|
// Act
|
|
cy.getByTestId('flight-item').first().click()
|
|
|
|
// Assert
|
|
cy.getByTestId('flight-details-expanded').within(() => {
|
|
cy.getByTestId('baggage-info').should('be.visible')
|
|
cy.getByTestId('seat-map-link').should('be.visible')
|
|
})
|
|
})
|
|
|
|
it('should collapse flight item on second click', () => {
|
|
// Act
|
|
cy.getByTestId('flight-item').first().click()
|
|
cy.getByTestId('flight-item').first().click()
|
|
|
|
// Assert
|
|
cy.getByTestId('flight-details-expanded').should('not.be.visible')
|
|
})
|
|
|
|
it('should select flight for booking', () => {
|
|
// Act
|
|
cy.getByTestId('flight-item').first().within(() => {
|
|
cy.getByTestId('select-flight-button').click()
|
|
})
|
|
|
|
// Assert
|
|
cy.getByTestId('selected-flight-indicator').should('be.visible')
|
|
})
|
|
|
|
it('should deselect flight', () => {
|
|
// Act
|
|
cy.getByTestId('flight-item').first().within(() => {
|
|
cy.getByTestId('select-flight-button').click()
|
|
cy.getByTestId('select-flight-button').click()
|
|
})
|
|
|
|
// Assert
|
|
cy.getByTestId('selected-flight-indicator').should('not.exist')
|
|
})
|
|
|
|
it('should show compare button for multiple flights', () => {
|
|
// Arrange
|
|
cy.getByTestId('flight-item').first().within(() => {
|
|
cy.getByTestId('select-flight-button').click()
|
|
})
|
|
|
|
// Act
|
|
cy.getByTestId('flight-item').eq(1).within(() => {
|
|
cy.getByTestId('select-flight-button').click()
|
|
})
|
|
|
|
// Assert
|
|
cy.getByTestId('compare-flights-button').should('be.visible')
|
|
})
|
|
})
|
|
|
|
describe('Flight List Pagination', () => {
|
|
it('should display pagination controls', () => {
|
|
// Assert
|
|
cy.getByTestId('pagination-container').should('be.visible')
|
|
})
|
|
|
|
it('should show current page', () => {
|
|
// Assert
|
|
cy.getByTestId('current-page').should('contain', '1')
|
|
})
|
|
|
|
it('should go to next page', () => {
|
|
// Act
|
|
cy.getByTestId('next-page-button').click()
|
|
|
|
// Assert
|
|
cy.getByTestId('current-page').should('contain', '2')
|
|
})
|
|
|
|
it('should go to previous page', () => {
|
|
// Arrange
|
|
cy.getByTestId('next-page-button').click()
|
|
|
|
// Act
|
|
cy.getByTestId('prev-page-button').click()
|
|
|
|
// Assert
|
|
cy.getByTestId('current-page').should('contain', '1')
|
|
})
|
|
|
|
it('should disable previous button on first page', () => {
|
|
// Assert
|
|
cy.getByTestId('prev-page-button').should('be.disabled')
|
|
})
|
|
|
|
it('should jump to specific page', () => {
|
|
// Act
|
|
cy.getByTestId('page-input').clear().type('3')
|
|
cy.getByTestId('go-to-page-button').click()
|
|
|
|
// Assert
|
|
cy.getByTestId('current-page').should('contain', '3')
|
|
})
|
|
|
|
it('should show flights per page selector', () => {
|
|
// Act
|
|
cy.getByTestId('flights-per-page-select').select('20')
|
|
|
|
// Assert
|
|
cy.getByTestId('flight-item').should('have.length', 20)
|
|
})
|
|
})
|
|
|
|
describe('Flight List Loading States', () => {
|
|
it('should show loading spinner while fetching', () => {
|
|
// Arrange
|
|
cy.intercept('GET', '**/api/flights/**', {
|
|
statusCode: 200,
|
|
body: { flights: [] },
|
|
delay: 1000,
|
|
}).as('slowLoad')
|
|
|
|
// Act
|
|
uiHelpers.fillInput('departure-input', 'VVO')
|
|
cy.getByTestId('search-button').click()
|
|
|
|
// Assert
|
|
cy.getByTestId('loading-spinner').should('be.visible')
|
|
cy.wait('@slowLoad')
|
|
cy.getByTestId('loading-spinner').should('not.exist')
|
|
})
|
|
|
|
it('should show skeleton loaders', () => {
|
|
// Arrange
|
|
cy.intercept('GET', '**/api/flights/**', {
|
|
statusCode: 200,
|
|
body: { flights: [] },
|
|
delay: 500,
|
|
}).as('load')
|
|
|
|
// Act
|
|
uiHelpers.fillInput('departure-input', 'VVO')
|
|
cy.getByTestId('search-button').click()
|
|
|
|
// Assert
|
|
cy.getByTestId('skeleton-loader').should('be.visible')
|
|
})
|
|
|
|
it('should show empty state when no results', () => {
|
|
// Arrange
|
|
cy.intercept('GET', '**/api/flights/**', {
|
|
statusCode: 200,
|
|
body: { flights: [] },
|
|
}).as('emptySearch')
|
|
|
|
// Act
|
|
uiHelpers.fillInput('departure-input', 'XXXX')
|
|
cy.getByTestId('search-button').click()
|
|
cy.wait('@emptySearch')
|
|
|
|
// Assert
|
|
cy.getByTestId('empty-state-message').should('be.visible')
|
|
})
|
|
|
|
it('should show error state on API failure', () => {
|
|
// Arrange
|
|
cy.intercept('GET', '**/api/flights/**', {
|
|
statusCode: 500,
|
|
body: { error: 'Server error' },
|
|
}).as('errorLoad')
|
|
|
|
// Act
|
|
uiHelpers.fillInput('departure-input', 'SVO')
|
|
cy.getByTestId('search-button').click()
|
|
cy.wait('@errorLoad')
|
|
|
|
// Assert
|
|
cy.getByTestId('error-message').should('be.visible')
|
|
})
|
|
})
|
|
|
|
describe('Flight List Virtualization', () => {
|
|
it('should render only visible flights', () => {
|
|
// Arrange
|
|
cy.intercept('GET', '**/api/flights/**', {
|
|
statusCode: 200,
|
|
body: {
|
|
flights: Array.from({ length: 100 }, (_, i) => ({
|
|
id: String(i),
|
|
number: `SU${1000 + i}`,
|
|
})),
|
|
},
|
|
}).as('largeFlight')
|
|
|
|
// Act
|
|
cy.getByTestId('search-button').click()
|
|
cy.wait('@largeFlight')
|
|
|
|
// Assert
|
|
cy.getByTestId('flight-item').should('have.length.lessThan', 100)
|
|
})
|
|
|
|
it('should load more flights on scroll', () => {
|
|
// Act
|
|
cy.getByTestId('flight-list-container').scrollTo('bottom')
|
|
|
|
// Assert
|
|
cy.getByTestId('flight-item').should('have.length.greaterThan', 10)
|
|
})
|
|
})
|
|
|
|
describe('Flight List Accessibility', () => {
|
|
it('should have proper semantic structure', () => {
|
|
// Assert
|
|
cy.getByTestId('flight-results-container').should('be.visible')
|
|
cy.getByTestId('flight-item').each($item => {
|
|
cy.wrap($item).should('have.attr', 'role', 'listitem')
|
|
})
|
|
})
|
|
|
|
it('should support keyboard navigation', () => {
|
|
// Act
|
|
cy.getByTestId('flight-item').first().focus()
|
|
cy.getByTestId('flight-item').first().type('{enter}')
|
|
|
|
// Assert
|
|
cy.getByTestId('flight-details-expanded').should('be.visible')
|
|
})
|
|
|
|
it('should announce loading state', () => {
|
|
// Arrange
|
|
cy.intercept('GET', '**/api/flights/**', {
|
|
statusCode: 200,
|
|
body: { flights: [] },
|
|
delay: 500,
|
|
}).as('load')
|
|
|
|
// Act
|
|
cy.getByTestId('search-button').click()
|
|
|
|
// Assert
|
|
cy.getByTestId('loading-message').should('have.attr', 'role', 'status')
|
|
})
|
|
})
|
|
})
|