Work sample · Test automation

Automated UI test suite
demonstration project

Stack Playwright · JavaScript Structure Feature-grouped specs CI GitHub Actions Status All tests passing
What this is: a demonstration project built to show test automation approach and code quality, run against Sauce Demo, a public application intended for exactly this purpose. It is not client work. The full source is on GitHub, where every push triggers the suite automatically, check the Actions tab for the live run history.

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

tests/helpers.js, the one shared step
// 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 };
tests/login.spec.js
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');
  });
});
tests/checkout.spec.js (excerpt)
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.

.github/workflows/tests.yml
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.

12
Passed
0
Failed
0
Flaky
0:47
Duration
TestAreaResult
valid user reaches the product pageLogin✓ pass
wrong password shows an error messageLogin✓ pass
locked out user cannot log inLogin✓ pass
shows six productsProducts✓ pass
sorting by price low to high worksProducts✓ pass
product detail shows the same name and priceProducts✓ pass
adding a product updates the cart badgeCart✓ pass
removing a product clears the cart badgeCart✓ pass
cart page lists the added productCart✓ pass
completing checkout shows the confirmation pageCheckout✓ pass
empty customer information is rejectedCheckout✓ pass
order total equals item total plus taxCheckout✓ 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.