A realistic multi-step booking flow — search, choose flights, enter passenger details, pay, and confirm — for Selenium, Cypress, or Playwright.
A realistic multi-step booking wizard: search, then sort and filter a list of flights, choose a departure (and return for round-trips), enter passenger details, pay, and get a confirmation with a booking reference. Great practice for a step flow, conditional fields, dynamic lists, and sorting/filtering.
The same scenario written for two popular frameworks. These use the stable id and data-testid locators documented in the cheat sheet below.
import { test, expect } from '@playwright/test';
test('book a one-way flight through the wizard', async ({ page }) => {
await page.goto('https://www.qapractice.com/flight-booking-scenarios');
// Step 1 — search
await page.getByTestId('flight-from').selectOption('New York');
await page.getByTestId('flight-to').selectOption('London');
await page.getByTestId('flight-departure-date').fill('2026-08-01');
await page.getByTestId('flight-one-way').check(); // hides the return date
await page.getByTestId('flight-search').click();
// Step 2 — sort, then select a flight (flight numbers are stable)
await page.getByTestId('flight-sort').selectOption('price-asc');
await page.getByTestId('flight-select-GW100').click();
await page.getByTestId('flight-continue-to-passengers').click();
// Step 3 — passenger details
await page.getByTestId('flight-passenger-name').fill('Ada Lovelace');
await page.getByTestId('flight-passenger-email').fill('ada@example.com');
await page.getByTestId('flight-passenger-phone').fill('+15550100');
await page.getByTestId('flight-continue-to-payment').click();
// Step 4 — payment
await page.getByTestId('flight-card-number').fill('4111111111111111');
await page.getByTestId('flight-expiry').fill('12/30');
await page.getByTestId('flight-cvv').fill('123');
await page.getByTestId('flight-book').click();
// Step 5 — confirmation
await expect(page.getByTestId('flight-booking-success'))
.toContainText('Booking Confirmed');
});import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.Select;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;
import java.time.Duration;
public class FlightBookingTest {
public static void main(String[] args) {
WebDriver driver = new ChromeDriver();
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
driver.get("https://www.qapractice.com/flight-booking-scenarios");
// Step 1 — search
new Select(driver.findElement(By.id("flight-from"))).selectByVisibleText("New York");
new Select(driver.findElement(By.id("flight-to"))).selectByVisibleText("London");
driver.findElement(By.id("flight-departure-date")).sendKeys("2026-08-01");
driver.findElement(By.id("flight-one-way")).click();
driver.findElement(By.id("flight-search")).click();
// Step 2 — select a flight and continue
wait.until(ExpectedConditions.elementToBeClickable(
By.cssSelector("[data-testid='flight-select-GW100']"))).click();
driver.findElement(By.cssSelector("[data-testid='flight-continue-to-passengers']")).click();
// Step 3 — passenger details
driver.findElement(By.id("flight-passenger-name")).sendKeys("Ada Lovelace");
driver.findElement(By.id("flight-passenger-email")).sendKeys("ada@example.com");
driver.findElement(By.id("flight-passenger-phone")).sendKeys("+15550100");
driver.findElement(By.cssSelector("[data-testid='flight-continue-to-payment']")).click();
// Step 4 — payment
driver.findElement(By.id("flight-card-number")).sendKeys("4111111111111111");
driver.findElement(By.id("flight-expiry")).sendKeys("12/30");
driver.findElement(By.id("flight-cvv")).sendKeys("123");
driver.findElement(By.id("flight-book")).click();
String msg = wait.until(ExpectedConditions.visibilityOfElementLocated(
By.id("flight-booking-success"))).getText();
assert msg.contains("Booking Confirmed");
driver.quit();
}
}Every interactive element on this page exposes a stable id and/or data-testid so your scripts don't depend on brittle positions or styling.
| Element | id | data-testid | Playwright locator | Selenium locator |
|---|---|---|---|---|
| Step 1 · From city | flight-from | flight-from | getByTestId('flight-from').selectOption('New York') | new Select(driver.findElement(By.id("flight-from"))) |
| Step 1 · To city | flight-to | flight-to | getByTestId('flight-to').selectOption('London') | new Select(driver.findElement(By.id("flight-to"))) |
| Step 1 · Departure date | flight-departure-date | flight-departure-date | getByTestId('flight-departure-date').fill('2026-08-01') | By.id("flight-departure-date") |
| Step 1 · Return date (round-trip only) | flight-return-date | flight-return-date | getByTestId('flight-return-date') | By.id("flight-return-date") |
| Step 1 · Passengers | flight-passengers | flight-passengers | getByTestId('flight-passengers').fill('2') | By.id("flight-passengers") |
| Step 1 · Travel class | flight-class | flight-class | getByTestId('flight-class').selectOption('Business') | new Select(driver.findElement(By.id("flight-class"))) |
| Step 1 · One-way checkbox | flight-one-way | flight-one-way | getByTestId('flight-one-way') | By.id("flight-one-way") |
| Step 1 · Search button | flight-search | flight-search | getByTestId('flight-search') | By.id("flight-search") |
| Step 2 · Sort dropdown | flight-sort | flight-sort | getByTestId('flight-sort').selectOption('price-asc') | new Select(driver.findElement(By.id("flight-sort"))) |
| Step 2 · Airline filter | flight-filter-airline | flight-filter-airline | getByTestId('flight-filter-airline').selectOption('BlueJet') | new Select(driver.findElement(By.id("flight-filter-airline"))) |
| Step 2 · Non-stop filter | flight-filter-nonstop | flight-filter-nonstop | getByTestId('flight-filter-nonstop').check() | By.id("flight-filter-nonstop") |
| Step 2 · A flight row | — | flight-result-<num> | getByTestId('flight-result-GW100') | By.cssSelector("[data-testid='flight-result-GW100']") |
| Step 2 · Select a flight | — | flight-select-<num> | getByTestId('flight-select-GW100') | By.cssSelector("[data-testid='flight-select-GW100']") |
| Step 2 · Continue button | — | flight-continue-to-passengers | getByTestId('flight-continue-to-passengers') | By.cssSelector("[data-testid='flight-continue-to-passengers']") |
| Step 3 · Passenger name | flight-passenger-name | flight-passenger-name | getByTestId('flight-passenger-name') | By.id("flight-passenger-name") |
| Step 3 · Passenger email | flight-passenger-email | flight-passenger-email | getByTestId('flight-passenger-email') | By.id("flight-passenger-email") |
| Step 3 · Passenger phone | flight-passenger-phone | flight-passenger-phone | getByTestId('flight-passenger-phone') | By.id("flight-passenger-phone") |
| Step 3 · Continue to payment | — | flight-continue-to-payment | getByTestId('flight-continue-to-payment') | By.cssSelector("[data-testid='flight-continue-to-payment']") |
| Step 4 · Total price | — | flight-total | getByTestId('flight-total') | By.cssSelector("[data-testid='flight-total']") |
| Step 4 · Card number | flight-card-number | flight-card-number | getByTestId('flight-card-number') | By.id("flight-card-number") |
| Step 4 · Expiry date | flight-expiry | flight-expiry | getByTestId('flight-expiry') | By.id("flight-expiry") |
| Step 4 · CVV | flight-cvv | flight-cvv | getByTestId('flight-cvv') | By.id("flight-cvv") |
| Step 4 · Pay & Confirm | flight-book | flight-book | getByTestId('flight-book') | By.id("flight-book") |
| Step 5 · Confirmation heading | flight-booking-success | flight-booking-success | getByTestId('flight-booking-success') | By.id("flight-booking-success") |
| Step 5 · Booking reference (PNR) | — | flight-pnr | getByTestId('flight-pnr') | By.cssSelector("[data-testid='flight-pnr']") |
Use these as a checklist to build your automation suite. Download them as a Markdown file to keep alongside your test project.
| ID | Scenario | Steps | Expected Result |
|---|---|---|---|
| TC01 | One-way booking, happy path |
| A "Booking Confirmed!" screen with a booking reference (PNR) is shown. |
| TC02 | Round-trip booking |
| Both flights appear in the confirmation; total reflects both fares. |
| TC03 | Return date only for round trips |
| The Return Date field shows when One Way is unticked and hides when ticked. |
| TC04 | Search validation |
| Inline validation errors appear for the missing fields. |
| TC05 | Same origin and destination |
| A validation error prevents searching identical cities. |
| TC06 | Sort results by price |
| Flights re-order from cheapest to most expensive. |
| TC07 | Filter non-stop only |
| Only non-stop flights remain in the list. |
| TC08 | Continue disabled until selected |
| The "Continue to passenger details" button is disabled until the required flight(s) are chosen. |
| TC09 | Passenger validation |
| An email validation error is shown and the step does not advance. |
| TC10 | Total reflects passengers |
| The total equals the selected fare(s) × 2. |