import { expect, test, uniqueSurname } from '../fixtures/test'; import { SKYLINE_AGENT } from '../fixtures/accounts'; test.beforeEach(async ({ signInAs, shell, search }) => { await signInAs(SKYLINE_AGENT); await shell.goToSearch(); await search.settled(); }); test.describe('Booking a flight', () => { test('carries the fare and traveller count into checkout', async ({ search, checkout, page }) => { await search.searchRoute('JFK', 'LHR'); await search.setTravellers(2); const fare = await search.card(0).fare(); await search.card(0).selectButton.click(); await page.waitForURL(/\/book\//); await checkout.waitUntilReady(); await expect(checkout.travellerFieldsets).toHaveCount(2); expect(await checkout.totalAmount()).toBeCloseTo(fare * 2, 2); }); test('reprices as travellers are added and removed', async ({ search, checkout, page }) => { const fare = await search.card(0).fare(); await search.card(0).selectButton.click(); await page.waitForURL(/\/book\//); await checkout.waitUntilReady(); expect(await checkout.totalAmount()).toBeCloseTo(fare, 2); await checkout.addTraveller.click(); await expect(checkout.travellerFieldsets).toHaveCount(2); expect(await checkout.totalAmount()).toBeCloseTo(fare * 2, 2); await checkout.removeTraveller(1); await expect(checkout.travellerFieldsets).toHaveCount(1); expect(await checkout.totalAmount()).toBeCloseTo(fare, 2); }); test('refuses to submit with missing traveller names', async ({ search, checkout, page }) => { await search.card(0).selectButton.click(); await page.waitForURL(/\/book\//); await checkout.waitUntilReady(); await checkout.confirm.click(); await expect(checkout.error).toBeVisible(); await expect(page.locator('.pax .error').first()).toContainText('Required'); // Still on the form, nothing sold. await expect(page).toHaveURL(/\/book\//); }); test('books, confirms, lists and cancels, returning the seats', async ({ search, checkout, bookings, bookingDetail, shell, page, evidence, }) => { const surname = uniqueSurname(); await search.searchRoute('JFK', 'LHR'); const card = search.card(0); const flightNumber = await card.flightNumber(); const fare = await card.fare(); const seatsBefore = await card.seatsLeft(); await evidence.step('Search results: JFK to LHR', { agency: 'Skyline Travel', route: 'JFK to LHR', matchingFlights: await search.totalResults(), cheapestFlight: flightNumber, fare: `$${fare.toFixed(2)}`, seatsAvailable: seatsBefore, }); await card.selectButton.click(); await page.waitForURL(/\/book\//); await checkout.waitUntilReady(); await checkout.fillTravellers([ { firstName: 'Dana', lastName: surname, dateOfBirth: '1988-04-12', passportNumber: 'X1234567' }, { firstName: 'Elias', lastName: surname }, ]); await checkout.contactPhone.fill('+1-555-0101'); const expectedTotal = await checkout.totalAmount(); await evidence.step('Checkout completed for two travellers', { travellers: `Dana ${surname}, Elias ${surname}`, farePerTraveller: `$${fare.toFixed(2)}`, total: `$${expectedTotal.toFixed(2)}`, contactPhone: '+1-555-0101', }); await checkout.confirmBooking(); // --- confirmation ------------------------------------------------------- await expect(bookingDetail.confirmationBanner).toBeVisible(); await expect(bookingDetail.confirmationBanner).toContainText('Booking confirmed'); await expect(bookingDetail.statusBadge).toHaveText(/confirmed/i); await expect(bookingDetail.travellerRows).toHaveCount(2); await expect(bookingDetail.travellerRows.first()).toContainText(surname); const reference = await bookingDetail.bookingReference(); expect(reference).toMatch(/^[A-Z0-9]{6}$/); expect(Number((await bookingDetail.totalPaid.innerText()).replace(/[^0-9.]/g, ''))).toBeCloseTo( expectedTotal, 2, ); expect(expectedTotal).toBeCloseTo(fare * 2, 2); await evidence.step('Booking confirmed', { bookingReference: reference, status: 'confirmed', travellers: 2, totalPaid: `$${expectedTotal.toFixed(2)}`, soldBy: SKYLINE_AGENT.fullName, }); // --- appears in the list ------------------------------------------------ await shell.goToBookings(); await expect(bookings.rowFor(reference)).toHaveCount(1); await expect(bookings.rowFor(reference)).toContainText(/confirmed/i); await expect(bookings.rowFor(reference)).toContainText(SKYLINE_AGENT.fullName); await evidence.step('Booking appears in the agency list', { bookingReference: reference, visibleInList: true, }); // --- seats were taken out of inventory ---------------------------------- await shell.goToSearch(); await search.searchRoute('JFK', 'LHR'); const seatsAfterSale = await search.cardForFlight(flightNumber).seatsLeft(); expect(seatsAfterSale).toBe(seatsBefore - 2); await evidence.step('Seats removed from inventory', { flight: flightNumber, seatsBefore, seatsAfterSale, difference: seatsBefore - seatsAfterSale, }); // --- cancel ------------------------------------------------------------- await shell.goToBookings(); await bookings.open(reference); await bookingDetail.cancel(reference); await expect(bookingDetail.statusBadge).toHaveText(/cancelled/i); await expect(bookingDetail.cancelledNotice).toContainText('returned to inventory'); await expect(bookingDetail.cancelButton).toHaveCount(0); await evidence.step('Booking cancelled', { bookingReference: reference, status: 'cancelled', }); // --- seats came back ---------------------------------------------------- await shell.goToSearch(); await search.searchRoute('JFK', 'LHR'); const seatsAfterCancel = await search.cardForFlight(flightNumber).seatsLeft(); expect(seatsAfterCancel).toBe(seatsBefore); await evidence.step('Seats returned to inventory', { flight: flightNumber, seatsBefore, seatsAfterSale, seatsAfterCancel, restored: seatsAfterCancel === seatsBefore, }); }); test('a cancel can be backed out of', async ({ search, checkout, bookingDetail, page }) => { const surname = uniqueSurname('KEEP'); await search.card(0).selectButton.click(); await page.waitForURL(/\/book\//); await checkout.waitUntilReady(); await checkout.fillTravellers([{ firstName: 'Mina', lastName: surname }]); await checkout.confirmBooking(); const reference = await bookingDetail.bookingReference(); await bookingDetail.cancelButton.click(); await expect(bookingDetail.confirmCancelButton).toBeVisible(); await bookingDetail.keepButton.click(); await expect(bookingDetail.confirmCancelButton).toHaveCount(0); await expect(bookingDetail.statusBadge).toHaveText(/confirmed/i); // Leave the data as we found it. await bookingDetail.cancel(reference); }); test('the bookings list filters by status', async ({ search, checkout, bookings, bookingDetail, shell, page, }) => { const surname = uniqueSurname('FILTER'); await search.card(0).selectButton.click(); await page.waitForURL(/\/book\//); await checkout.waitUntilReady(); await checkout.fillTravellers([{ firstName: 'Otto', lastName: surname }]); await checkout.confirmBooking(); const reference = await bookingDetail.bookingReference(); await shell.goToBookings(); await bookings.filterBy('confirmed'); await expect(bookings.rowFor(reference)).toHaveCount(1); await bookings.filterBy('cancelled'); await expect(bookings.rowFor(reference)).toHaveCount(0); // Cancel it, then confirm it moves between the two filters. await bookings.filterBy('all'); await bookings.open(reference); await bookingDetail.cancel(reference); await shell.goToBookings(); await bookings.filterBy('cancelled'); await expect(bookings.rowFor(reference)).toHaveCount(1); await bookings.filterBy('confirmed'); await expect(bookings.rowFor(reference)).toHaveCount(0); }); test('a booking opened directly shows no stale confirmation banner', async ({ search, checkout, bookingDetail, bookings, shell, page, }) => { const surname = uniqueSurname('DIRECT'); await search.card(0).selectButton.click(); await page.waitForURL(/\/book\//); await checkout.waitUntilReady(); await checkout.fillTravellers([{ firstName: 'Rhea', lastName: surname }]); await checkout.confirmBooking(); const reference = await bookingDetail.bookingReference(); // Re-entering from the list is not a fresh purchase. await shell.goToBookings(); await bookings.open(reference); await expect(bookingDetail.confirmationBanner).toHaveCount(0); await expect(bookingDetail.statusBadge).toHaveText(/confirmed/i); await bookingDetail.cancel(reference); }); });