TypeScript · @playwright/test
Ride hailing with Playwright
Pickup and drop autocomplete with swap, fares per vehicle computed from distance, a promo, then a live ride that moves from searching to completed — with OTP, a cancel fee rule, rating and tip.
App under test
🚕 RideGo
- URL
- /apps/ride
- Promo
- FIRSTRIDE (50%, max ₹75)
- Stages
- 2s · 5s · 8s · 11s
- Cancel fee
- ₹30 after arrival
Scenarios to automate
Work top to bottom — each one adds a new technique.
- 1
Autocomplete
BeginnerType in pickup-input / drop-input and choose *-option-<place>; the same place can't be both.
- 2
Swap & shortcut
Beginneruse-home fills pickup; swap-locations exchanges both inputs and redraws ride-map.
- 3
Fare maths
Intermediatefare-<vehicle> = base + per-km × distance + per-min × time; check against trip-distance.
- 4
Promo cap
IntermediateFIRSTRIDE halves the fare but caps at ₹75 — fare-payable reflects the cap.
- 5
Live status
Advancedride-status walks through all five stages on a timer — chain text waits, don't sleep.
- 6
Cancel rules
AdvancedCancel before arrival is free; after “Driver arrived” cancel-fee-warning shows ₹30 and cancel-fee records it.
- 7
Moving marker
IntermediateDuring the trip, map-car moves along the route (SVG text element).
- 8
Rate & tip
BeginnerAfter completion pick rate-driver-* and tip-*; trip-total includes the tip.
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 ×2 · getByLabel ×1 · getByPlaceholder ×1 · CSS ×1 · XPath ×2 · getByTestId ×14
test('books a cab and completes the trip', async ({ page }) => {
await page.goto('/apps/ride');
await page.getByLabel('Pickup').fill('Tech');
await page.getByTestId('pickup-option-office').click();
await page.getByPlaceholder('Where to?', { exact: true }).fill('air');
await page.getByTestId('drop-option-airport').click();
await expect(page.getByTestId('trip-distance')).toContainText('24.7 km');
await page.getByTestId('ride-mini').click();
await page.locator('#ride-promo').fill('FIRSTRIDE');
await page.getByRole('button', { name: 'Apply' }).click();
await expect(page.getByTestId('fare-payable')).toContainText('₹432');
await page.getByRole('combobox', { name: 'Payment' }).selectOption('upi');
await page.locator('xpath=//button[normalize-space()="Book Mini"]').click();
// The ride progresses on its own — wait for each status
await expect(page.getByTestId('ride-status')).toContainText('Driver assigned');
await expect(page.getByTestId('ride-otp')).toBeVisible();
await expect(page.getByTestId('ride-status')).toContainText('Driver arrived');
await expect(page.getByTestId('ride-status')).toContainText('On trip');
await expect(page.getByTestId('ride-status')).toContainText('Completed');
await page.getByTestId('rate-driver-5').click();
await page.getByTestId('tip-20').click();
await page.locator('xpath=//button[normalize-space()="Submit"]').click();
await expect(page.getByTestId('rating-saved')).toBeVisible();
await expect(page.getByTestId('trip-total')).toContainText('₹452');
});