TypeScript · @playwright/test
Manufacturing with Playwright
A live machine dashboard with status filters and OEE meters, work orders whose bill of materials is checked against stock (shortages block release), a start → complete lifecycle, and quality inspection with tolerance-based pass/fail.
App under test
⚙️ FactoryOS
- URL
- /apps/factory
- Impeller stock
- 40 units
- QC spec
- 25.00 ± 0.05 mm …
- WO numbers
- WO-1001 …
Scenarios to automate
Work top to bottom — each one adds a new technique.
- 1
Changing data
Intermediatemachine-card data-status changes every ~4s; assert counts add up (count-*) rather than specific states.
- 2
Meters
Beginneroee-* use role=meter with aria-valuenow; avg-oee is the mean of all machines.
- 3
BOM maths
IntermediatePump × 50 → required-bearing 100, required-impeller 50 (stock 40) → shortage-impeller shows −10.
- 4
Blocked action
Intermediaterelease-wo with a shortage shows wo-error naming the part; wo-qty-dec10 clears it (materials-ok).
- 5
Lifecycle
Beginnerstart-<id> → In progress → complete-<id> → Completed; buttons change with status.
- 6
Tolerance checks
Advanced25.06 mm fails (Out of tolerance) while 25.05 passes; any defect checkbox forces FAIL.
- 7
Incomplete inspection
Beginnersubmit-inspection before all measurements shows qc-error; qc-result stays Pending.
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 ×1 · CSS ×4 · XPath ×4 · getByTestId ×11
test('releases a work order and passes QC', async ({ page }) => {
await page.goto('/apps/factory');
await expect(page.getByTestId('machine-grid')).toBeVisible();
await page.getByRole('link', { name: 'Work orders' }).click();
await page.getByTestId('new-work-order').click();
await page.getByLabel('Product', { exact: true }).selectOption('pump');
await page.locator('#wo-qty').fill('50');
await expect(page.getByTestId('required-impeller')).toContainText('50');
await expect(page.getByTestId('shortage-impeller')).toContainText('−10');
await page.locator('xpath=//button[normalize-space()="Release work order"]').click();
await expect(page.getByTestId('wo-error')).toContainText('Impeller');
await page.getByTestId('wo-qty-dec10').click();
await expect(page.getByTestId('materials-ok')).toBeVisible();
await page.getByRole('combobox', { name: 'Production line' }).selectOption('Line B');
await page.getByTestId('wo-due').nth(3).click();
await page.locator('xpath=//button[normalize-space()="Urgent"]').click();
await page.locator('xpath=//button[normalize-space()="Release work order"]').click();
await expect(page.getByTestId('wo-status-WO-1001')).toContainText('Released');
await page.getByRole('button', { name: 'Start' }).click();
await page.getByRole('button', { name: 'Complete' }).click();
await expect(page.getByTestId('wo-status-WO-1001')).toContainText('Completed');
await page.locator('xpath=//a[normalize-space()="Quality"]').click();
await page.locator('#qc-diameter').fill('25.02');
await page.locator('#qc-length').fill('119.9');
await page.locator('#qc-weight').fill('2.43');
await expect(page.getByTestId('qc-result')).toContainText('PASS');
await page.getByRole('button', { name: 'Record inspection' }).click();
await expect(page.getByTestId('inspection-id')).toContainText('QC-');
});