TypeScript · @playwright/test
ERP with Playwright
A sortable, paginated inventory grid, purchase orders with dynamic line items and GST totals, and a two-role approval workflow with an audit trail.
App under test
🏭 CoreERP
- URL
- /apps/erp
- Buyer
- buyer / Erp@123
- Manager
- manager / Erp@123
- Tax
- GST 18%
Scenarios to automate
Work top to bottom — each one adds a new technique.
- 1
Role-based access
BeginnerAs buyer, open /apps/erp/approvals and assert access-denied; the manager sees the queue.
- 2
Grid sorting
IntermediateClick sort-qty twice and verify qty-* values are descending; aria-sort reflects the state.
- 3
Filters & pagination
IntermediateCombine inventory-search, warehouse-filter, low-stock-only and page-size; walk page-next.
- 4
Reorder shortcut
Intermediatereorder-<sku> opens a new PO with that item pre-filled on line 0.
- 5
Dynamic line items
AdvancedAdd 3 lines, remove the middle one, and check the remaining line-*-total and po-total.
- 6
PO validation
IntermediateSubmitting with no vendor, no lines, qty 0 or duplicate items shows po-errors and error-*.
- 7
Two-user workflow
AdvancedBuyer submits → logout → manager approves; po-status and po-timeline update.
- 8
Rejection with comment
Advancedreject-po requires a 10+ char reject-comment; it appears as po-comment in the timeline.
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 ×4 · getByLabel ×3 · CSS ×3 · XPath ×4 · getByTestId ×7
test('raises and approves a purchase order', async ({ page }) => {
await page.goto('/apps/erp');
// Buyer raises the purchase order
await page.getByLabel('Username').fill('buyer');
await page.locator('#erp-password').fill('Erp@123');
await page.getByRole('button', { name: 'Sign in' }).click();
await expect(page).toHaveURL(/\/apps\/erp\/dashboard/);
await page.locator('xpath=//a[normalize-space()="Purchase orders"]').click();
await page.getByTestId('new-po').click();
await page.getByRole('combobox', { name: 'Vendor' }).selectOption('v2');
await page.getByTestId('po-delivery-14').click();
await page.getByTestId('add-line').click();
await page.getByLabel('Item').selectOption('SKU-1004');
await page.locator('#line-0-qty').fill('25');
await expect(page.getByTestId('line-0-total')).toContainText('8,400.00');
await expect(page.getByTestId('po-total')).toContainText('9,912.00');
await page.locator('xpath=//button[normalize-space()="Submit for approval"]').click();
await expect(page).toHaveURL(/\/apps\/erp\/po\//);
await expect(page.getByTestId('po-status')).toContainText('Pending approval');
await page.getByRole('button', { name: 'Log out' }).click();
// Manager approves it
await page.getByLabel('Username').fill('manager');
await page.locator('#erp-password').fill('Erp@123');
await page.getByRole('button', { name: 'Sign in' }).click();
await expect(page).toHaveURL(/\/apps\/erp\/dashboard/);
await page.locator('xpath=//a[normalize-space()="Approvals"]').click();
await page.getByTestId('approval-review').nth(0).click();
await page.locator('xpath=//button[normalize-space()="Approve"]').click();
await expect(page.getByTestId('po-status')).toContainText('Approved');
});