PAPracticeAutomation
Pw

TypeScript ยท @playwright/test

Ticket booking with Playwright

Automate a movie booking โ€” dates, filters, a seat map, snacks, promos, OTP payment and a seat-hold timer.

App under test

ShowTime

URL
/tickets
Login
not required
Promos
MOVIE50 ยท FIRSTSHOW
Card OTP
123456
Seat hold
5 minutes
Launch ShowTime โ†’

Scenarios to automate

Work top to bottom โ€” each one adds a new technique.

  1. 1

    Date strip

    Beginner

    Seven date-option chips from today; dates are computed from the browser clock.

  2. 2

    Filters

    Beginner

    Combine genre-* toggles, language-filter and movie-search; assert movie-card count.

  3. 3

    Showtime states

    Intermediate

    data-status is available / filling / soldout / started โ€” disabled ones can't be clicked.

  4. 4

    Seat map

    Intermediate

    Pick seats by data-status, verify selected-seat chips and seat-total per tier.

  5. 5

    Seat limit

    Intermediate

    The 9th seat shows seat-limit-error.

  6. 6

    Snacks & promo codes

    Intermediate

    MOVIE50 halves tickets (max $10); FIRSTSHOW only works on a first booking.

  7. 7

    OTP payment

    Advanced

    Card payments open otp-modal; 123456 passes, anything else shows otp-error.

  8. 8

    Async UPI approval

    Advanced

    UPI shows payment-processing for ~3s before the confirmation page.

  9. 9

    Hold timer

    Advanced

    Seats are held for 5:00. Mock the clock to hit timer-expired without waiting.

  10. 10

    Cancel a booking

    Advanced

    Cancel from My bookings, confirm the dialog, and find it under the Cancelled tab.

Sample Playwright suite

A working starting point written against this app's real data-testids.

tickets.spec.ts
import { test, expect } from '@playwright/test';

test('book two seats and pay by card with OTP', async ({ page }) => {
  await page.goto('/tickets');
  await page.getByTestId('date-option').nth(1).click();   // tomorrow: no "started" shows
  await page.getByTestId('city-select').selectOption('London');

  const movie = page.getByTestId('movie-card').filter({ hasText: 'Nebula Drift' });
  await movie.locator('[data-testid="showtime"]:enabled').first().click();
  await expect(page).toHaveURL(/\/tickets\/show\//);

  // Re-evaluated on every use, so the second click gets the next free seat.
  const freeSeat = page.locator('[data-testid^="seat-"][data-status="available"]').first();
  await freeSeat.click();
  await freeSeat.click();
  await expect(page.getByTestId('selected-seat')).toHaveCount(2);
  await page.getByTestId('proceed-button').click();

  await page.getByTestId('contact-name').fill('Grace Hopper');
  await page.getByTestId('contact-email').fill('grace@example.com');
  await page.getByTestId('contact-phone').fill('5559876543');
  await page.getByTestId('snack-popcorn-inc').click();
  await page.getByTestId('promo-input').fill('MOVIE50');
  await page.getByTestId('apply-promo').click();
  await expect(page.getByTestId('promo-message')).toContainText('applied');

  await page.getByTestId('card-number').fill('4242424242424242');
  await page.getByTestId('card-expiry').fill('12/30');
  await page.getByTestId('card-cvv').fill('123');
  await page.getByTestId('accept-terms').check();
  await page.getByTestId('pay-button').click();

  await page.getByTestId('otp-input').fill('123456');
  await page.getByTestId('otp-submit').click();

  await expect(page.getByTestId('booking-success')).toBeVisible();
  await expect(page.getByTestId('booking-ref')).toHaveText(/^ST[A-Z0-9]{6}$/);
});

test('hold timer expires (mocked clock)', async ({ page }) => {
  await page.clock.install();
  await page.goto('/tickets');
  await page.getByTestId('date-option').nth(1).click();
  await page.locator('[data-testid="showtime"]:enabled').first().click();
  await page.locator('[data-testid^="seat-"][data-status="available"]').first().click();
  await page.getByTestId('proceed-button').click();

  await page.clock.fastForward('05:01');
  await expect(page.getByTestId('timer-expired')).toBeVisible();
});

Practising another tool?