TypeScript · @playwright/test
Pharmacy with Playwright
Search medicines by name or salt, see cheaper generic substitutes, respect per-item quantity limits, and get blocked at checkout until you upload a prescription (or book a consult) for Rx items.
App under test
💊 MediKart
- URL
- /apps/pharmacy
- Mobile
- 9876543210
- OTP
- 445566
- Rx upload
- PDF/JPG/PNG ≤ 5 MB
- Free delivery
- ₹300+
- Consult
- ₹199
Scenarios to automate
Work top to bottom — each one adds a new technique.
- 1
Mobile + OTP login
BeginnerPasswordless sign-in: mk-login-id (mobile) → mk-login → mk-code (OTP) → mk-verify. A wrong OTP shows mk-code-error; an unknown mobile counts toward the 3-attempt lock.
- 2
Min-length autocomplete
Beginnermed-suggestions only appears after 2 characters; match by brand or salt.
- 3
Substitutes & savings
IntermediateOn Mox 500, saving-m3g shows “Save ₹50 (42%)”; add-substitute-* adds the generic.
- 4
Quantity limits
Intermediateinc-<id> disables at the medicine's max (antibiotics: 2–3); add-med-* disables when maxed.
- 5
Rx gate
AdvancedWith an Rx item, placing the order shows error-rx until rx-upload + prescription-file or rx-consult.
- 6
Upload validation
IntermediateA .txt or >5 MB file shows an error under prescription-file; valid files show prescription-file-name.
- 7
Totals
Intermediatemed-delivery is ₹40 under ₹300 and FREE above; rx-consult adds ₹199 to med-total.
- 8
Filters
Beginnermed-category-* chips and filter-all/otc/rx narrow the med-grid.
- 9
Reorder
Beginnerreorder-<id> on the orders page refills the cart with the same lines.
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 ×5 · getByLabel ×3 · CSS ×3 · XPath ×2 · getByTestId ×16
test('orders medicines with a prescription', async ({ page }) => {
await page.goto('/apps/pharmacy');
// Sign in first
await page.getByLabel('Mobile number').fill('9876543210');
await page.getByRole('button', { name: 'Continue' }).click();
await page.locator('#mk-code').fill('445566');
await page.locator('xpath=//button[normalize-space()="Verify & continue"]').click();
await page.getByTestId('med-search').fill('amox');
await page.getByTestId('med-option-m3').click();
await expect(page.getByTestId('med-title')).toContainText('Mox 500');
await expect(page.getByTestId('saving-m3g')).toContainText('Save ₹50');
await page.getByRole('button', { name: 'Add instead' }).click();
await expect(page.getByTestId('cart-count')).toContainText('1');
await page.locator('xpath=//a[normalize-space()="Shop"]').click();
await page.getByTestId('add-med-m1').click();
await page.getByTestId('add-med-m1').click();
await page.getByTestId('nav-cart').click();
await expect(page.getByTestId('rx-required')).toBeVisible();
await page.getByRole('button', { name: 'Place order' }).click();
await expect(page.getByTestId('error-rx')).toBeVisible();
await page.getByTestId('rx-upload').click();
await page.getByLabel('Prescription').setInputFiles('fixtures/prescription.pdf');
await expect(page.getByTestId('prescription-file-name')).toContainText('prescription.pdf');
await page.locator('#delivery-slot').selectOption('Tomorrow 8–11 am');
await page.getByLabel('Phone').fill('9876543210');
await page.locator('#med-address').fill('12B Palm Residency, Baner, Pune');
await expect(page.getByTestId('med-total')).toContainText('₹178');
await page.getByRole('switch', { name: 'Refill reminder' }).click();
await page.getByRole('button', { name: 'Place order' }).click();
await expect(page.getByTestId('med-order-placed')).toBeVisible();
await expect(page.getByTestId('med-order-id')).toContainText('MK-');
await expect(page.getByTestId('refill-on')).toBeVisible();
});