Automated UI test suite
demonstration project
1. Scope & approach
The suite covers the critical user journeys of a web shop: login, product browsing, cart handling and checkout, the flows the business depends on first. The design keeps things deliberately simple and stable:
One file per feature, login, products, cart, checkout. Anyone can open a file and read it top to bottom. Stable selectors, the app's data-test attributes are used wherever available, so styling changes don't break tests. Negative paths included, wrong password, locked-out user and empty forms are tested, not only the happy path.
2. Project structure
saucedemo-e2e/ ├── playwright.config.js # browser, base URL, reporting ├── tests/ │ ├── helpers.js # shared login step │ ├── login.spec.js # 3 tests │ ├── products.spec.js # 3 tests │ ├── cart.spec.js # 3 tests │ └── checkout.spec.js # 3 tests └── .github/workflows/tests.yml # CI: run on every push
3. The code
// Shared steps used by more than one test file. async function login(page, user = 'standard_user', pass = 'secret_sauce') { await page.goto('/'); await page.locator('[data-test="username"]').fill(user); await page.locator('[data-test="password"]').fill(pass); await page.locator('[data-test="login-button"]').click(); } module.exports = { login };
const { test, expect } = require('@playwright/test'); const { login } = require('./helpers'); test.describe('Login', () => { test('valid user reaches the product page', async ({ page }) => { await login(page); await expect(page).toHaveURL(/inventory/); await expect(page.locator('.title')).toHaveText('Products'); }); test('wrong password shows an error message', async ({ page }) => { await login(page, 'standard_user', 'wrong_password'); await expect(page.locator('[data-test="error"]')) .toContainText('Username and password do not match'); }); test('locked out user cannot log in', async ({ page }) => { await login(page, 'locked_out_user', 'secret_sauce'); await expect(page.locator('[data-test="error"]')) .toContainText('this user has been locked out'); }); });
test('order total equals item total plus tax', async ({ page }) => { await page.locator('[data-test="firstName"]').fill('Anna'); await page.locator('[data-test="lastName"]').fill('Muster'); await page.locator('[data-test="postalCode"]').fill('8001'); await page.locator('[data-test="continue"]').click(); const itemTotal = parseFloat((await page.locator('.summary_subtotal_label').innerText()).replace(/[^0-9.]/g, '')); const tax = parseFloat((await page.locator('.summary_tax_label').innerText()).replace(/[^0-9.]/g, '')); const total = parseFloat((await page.locator('.summary_total_label').innerText()).replace(/[^0-9.]/g, '')); expect(total).toBeCloseTo(itemTotal + tax, 2); });
4. Continuous integration
Every push runs the full suite on GitHub Actions, the same gate a real project would have before a release. The HTML report is saved as a build artifact.
name: E2E tests on: [push, pull_request] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: { node-version: 20 } - run: npm ci - run: npx playwright install chromium --with-deps - run: npx playwright test - uses: actions/upload-artifact@v4 if: always() with: { name: playwright-report, path: playwright-report/ }
5. Run report
Summary of the latest full run.
| Test | Area | Result |
|---|---|---|
| valid user reaches the product page | Login | ✓ pass |
| wrong password shows an error message | Login | ✓ pass |
| locked out user cannot log in | Login | ✓ pass |
| shows six products | Products | ✓ pass |
| sorting by price low to high works | Products | ✓ pass |
| product detail shows the same name and price | Products | ✓ pass |
| adding a product updates the cart badge | Cart | ✓ pass |
| removing a product clears the cart badge | Cart | ✓ pass |
| cart page lists the added product | Cart | ✓ pass |
| completing checkout shows the confirmation page | Checkout | ✓ pass |
| empty customer information is rejected | Checkout | ✓ pass |
| order total equals item total plus tax | Checkout | ✓ pass |
6. What a real engagement adds
For a client project, this foundation is extended with: environment-specific configuration and test data management, additional browsers (Firefox, WebKit, mobile viewports), integration into the client's CI (Azure DevOps, GitLab or GitHub), reporting into the existing bug workflow (Jira / Azure Boards), and a maintenance handover so the client's team can own the suite afterwards.