TypeScript · @playwright/test
Travel & Airline with Playwright
One-way or round trip with airport autocomplete and swap, passenger steppers with infant rules, filters and sort tabs, fare families, traveller forms, a seat map, payment and PNR.
App under test
✈️ SkyWays
- URL
- /apps/flights
- Login
- not required
- Max travellers
- 9 (1 infant/adult)
- Card
- any 16 digits
Scenarios to automate
Work top to bottom — each one adds a new technique.
- 1
Airport autocomplete
BeginnerType in from-city, pick from-option-<code>; free text without picking fails search-errors.
- 2
Swap & same-city rule
Beginnerswap-cities exchanges both fields; the same city on both sides is rejected.
- 3
Passenger rules
Intermediateinfants-inc is disabled once infants = adults; the total never exceeds 9.
- 4
Round trip dates
Intermediatereturn-day chips before the departure date are disabled.
- 5
Filters & sorting
IntermediateCombine stops-*, airline-*, dep-* and max-fare; sort-fastest orders by duration.
- 6
Fare families
Intermediateselect-flight expands fare-basic / fare-flex inside that card only.
- 7
Seat map
AdvancedPick seats with data-status="available"; selection auto-advances to the next traveller (assigned-seat-*).
- 8
Round-trip booking
AdvancedSelect outbound, then the return list appears; the ticket shows two ticket-leg rows.
Sample Playwright suite
A working starting point that finds elements every way Playwright can — by role, label, text, id, name, CSS, XPath and data-testid — so you practice them all. Pick your language, switch to data-testid only to compare, or try your own in the Locator Playground.
import { test, expect } from '@playwright/test';
// playwright.config.ts → use: { baseURL: 'https://www.practiceqaautomation.com' }
// Locators in this suite: getByRole ×4 · getByLabel ×6 · getByPlaceholder ×1 · CSS ×7 · XPath ×3 · getByTestId ×9
test('books a one-way flight for two', async ({ page }) => {
await page.goto('/apps/flights');
await page.getByRole('button', { name: 'One way' }).click();
await page.getByLabel('From').fill('Mum');
await page.getByTestId('from-option-bom').click();
await page.locator('#to-city').fill('Del');
await page.getByTestId('to-option-del').click();
await page.getByTestId('depart-day').nth(1).click();
await page.getByTestId('passengers-toggle').click();
await page.getByTestId('adults-inc').click();
await page.locator('xpath=//button[normalize-space()="Done"]').click();
await expect(page.getByTestId('passengers-summary')).toContainText('2 adults');
await page.getByRole('combobox', { name: 'Cabin' }).selectOption('economy');
await page.locator('xpath=//button[normalize-space()="Search flights"]').click();
await expect(page).toHaveURL(/\/apps\/flights\/results/);
await page.getByLabel('Non-stop').check();
await page.getByRole('tab', { name: 'cheapest' }).click();
await page.getByTestId('select-flight').nth(0).click();
await page.getByTestId('fare-flex').click();
await expect(page).toHaveURL(/\/apps\/flights\/passengers/);
await page.locator('#pax-0-title').selectOption('mr');
await page.locator('#pax-0-first').fill('Amir');
await page.locator('#pax-0-last').fill('Khan');
await page.locator('#pax-1-title').selectOption('ms');
await page.locator('#pax-1-first').fill('Sara');
await page.locator('#pax-1-last').fill('Khan');
await page.getByLabel('Email').fill('amir@example.com');
await page.getByLabel('Phone').fill('9876543210');
await page.locator('xpath=//button[normalize-space()="Continue to seats"]').click();
// Each pick auto-advances to the next traveller
await page.locator('[data-testid^="seat-"][data-status="available"]').first().click();
await page.locator('[data-testid^="seat-"][data-status="available"]').first().click();
await page.getByRole('button', { name: 'Continue to payment' }).click();
await page.getByLabel('Card number').fill('4242424242424242');
await page.getByPlaceholder('MM/YY', { exact: true }).fill('1230');
await page.getByLabel('CVV').fill('123');
await page.getByTestId('pay-flight').click();
await expect(page.getByTestId('ticket-confirmed')).toBeVisible();
await expect(page.getByTestId('ticket-passengers')).toContainText('Sara Khan');
});