TypeScript · @playwright/test
Hospitality with Playwright
Search a destination, filter by stars, price and amenities, pick a room with add-ons, enter guest details, pay and manage the reservation.
App under test
🏨 StayEase
- URL
- /apps/hotel
- Login
- not required
- Taxes
- 12%
- Card
- any 16 digits
Scenarios to automate
Work top to bottom — each one adds a new technique.
- 1
Search validation
BeginnerSearch with no destination or date shows error-city and error-checkin.
- 2
Steppers & date chips
Beginnernights-inc / guests-inc change the summary; pick checkin-day by index.
- 3
Filters & sorting
IntermediateCombine star-*, max-price (range), amenity-* and sort; assert result-count and price order.
- 4
Room capacity
IntermediateWith 3+ guests the Standard room is disabled and shows room-standard-capacity.
- 5
Live price summary
Intermediateaddon-* checkboxes update price-addons, price-taxes and price-total.
- 6
Checkout validation
IntermediateSubmit empty to get form-errors and error-* per field; Pay at hotel hides card fields.
- 7
Async confirmation
Advancedconfirm-booking shows a processing overlay (~1.5s) before booking-confirmed.
- 8
Cancel with reason
Advancedcancel-<id> opens cancel-modal; a reason is required before confirm-cancel.
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 ×3 · getByLabel ×4 · getByPlaceholder ×1 · CSS ×5 · XPath ×3 · getByTestId ×10
test('books a hotel end to end', async ({ page }) => {
await page.goto('/apps/hotel');
await page.getByRole('combobox', { name: 'Destination' }).selectOption('goa');
await page.getByTestId('checkin-day').nth(2).click();
await page.getByTestId('nights-inc').click();
await page.locator('xpath=//button[normalize-space()="Search hotels"]').click();
await expect(page).toHaveURL(/\/apps\/hotel\/results/);
// Filter to 5-star stays, cheapest first
await page.locator('#star-5').check();
await page.getByRole('combobox', { name: 'Sort by' }).selectOption('price-asc');
await expect(page.getByTestId('result-count')).toContainText('2 properties');
await page.getByTestId('view-rooms-g1').click();
await expect(page).toHaveURL(/\/apps\/hotel\/hotel\/g1/);
await expect(page.getByTestId('hotel-title')).toContainText('Palm Grove Resort');
await page.getByTestId('select-room-deluxe').click();
await page.locator('#addon-breakfast').check();
await expect(page.getByTestId('price-total')).toBeVisible();
await page.locator('xpath=//button[normalize-space()="Continue to booking"]').click();
// Guest details and card payment
await page.getByLabel('First name').fill('Ada');
await page.getByLabel('Last name').fill('Lovelace');
await page.getByLabel('Email').fill('ada@example.com');
await page.locator('#guest-phone').fill('5551234567');
await page.getByRole('combobox', { name: 'Estimated arrival' }).selectOption('14:00 – 16:00');
await page.getByTestId('pay-now').click();
await page.getByLabel('Card number').fill('4242424242424242');
await page.getByPlaceholder('MM/YY', { exact: true }).fill('1230');
await page.locator('#card-cvv').fill('123');
await page.locator('#accept-policy').check();
await page.locator('xpath=//button[normalize-space()="Confirm booking"]').click();
await expect(page.getByTestId('booking-confirmed')).toBeVisible();
await expect(page.getByTestId('reservation-id')).toContainText('SE-');
await expect(page.getByTestId('confirmed-room')).toContainText('Deluxe Room');
});