Chapter 18 – Playwright Project

Now that you’ve mastered all aspects of Playwright from basic automation to advanced framework design and CI integration it’s time to bring everything together in a real-world project example.

In this chapter, we’ll build a complete Playwright automation framework that includes:

  • End-to-end tests (login, dashboard, API validation, file upload/download)
  • Reusable fixtures and Page Object Model (POM)
  • Reporting, CI configuration, and artifacts collection

This practical example mirrors how enterprise teams use Playwright in production.

1. Project Overview

Application Under Test (AUT):

For this example, we’ll test a web application “SauceStore” an e-commerce site with:

  • Login page
  • Dashboard displaying products
  • API endpoint for user data

2. Project structure

Here’s the recommended project structure for our real-world Playwright framework:

/playwright-framework
 ┣ tests
 ┃ ┣ login.spec.js
 ┃ ┣ dashboard.spec.js
 ┃ ┣ api.spec.js
 ┣ pages
 ┃ ┣ LoginPage.js
 ┃ ┣ DashboardPage.js
 ┣ fixtures
 ┃ ┗ authFixture.js
 ┣ utils
 ┃ ┣ testData.js
 ┃ ┣ apiHelper.js
 ┃ ┗ config.js
 ┣ reports
 ┣ playwright.config.js
 ┣ package.json
 ┗ README.md

3. Configuration (playwright.config.js)

This is the heart of your Playwright framework managing browsers, retries, traces, and reports.

import { defineConfig } from '@playwright/test';

export default defineConfig({
  testDir: './tests',
  timeout: 30 * 1000,
  retries: 1,
  reporter: [
    ['list'],
    ['html', { outputFolder: 'playwright-report', open: 'never' }],
    ['junit', { outputFile: 'results.xml' }],
  ],
  use: {
    baseURL: 'https://saucedemo.com',
    headless: true,
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
    trace: 'retain-on-failure',
  },
});

4. Utilities

utils/config.js

export const config = {
  baseURL: 'https://www.saucedemo.com',
  validUser: { username: 'standard_user', password: 'secret_sauce' },
  invalidUser: { username: 'fake_user', password: 'wrong_pass' },
};

utils/testData.js

export const testData = {
  products: ['Sauce Labs Backpack', 'Sauce Labs Bike Light'],
  uploadFile: 'test-data/sample.txt',
};

utils/apiHelper.js

import { request } from '@playwright/test';

export async function getUserData() {
  const context = await request.newContext();
  const response = await context.get('https://jsonplaceholder.typicode.com/users/1');
  return await response.json();
}

5. Page Objects

pages/LoginPage.js

export class LoginPage {
  constructor(page) {
    this.page = page;
    this.usernameInput = page.locator('#user-name');
    this.passwordInput = page.locator('#password');
    this.loginButton = page.locator('#login-button');
    this.errorMessage = page.locator('[data-test="error"]');
  }

  async goto() {
    await this.page.goto('/');
  }

  async login(username, password) {
    await this.usernameInput.fill(username);
    await this.passwordInput.fill(password);
    await this.loginButton.click();
  }
}

pages/DashboardPage.js

export class DashboardPage {
  constructor(page) {
    this.page = page;
    this.productTitles = page.locator('.inventory_item_name');
    this.cartButton = page.locator('.shopping_cart_link');
  }

  async verifyProducts(expectedProducts) {
    const productCount = await this.productTitles.count();
    const productNames = [];
    for (let i = 0; i < productCount; i++) {
      productNames.push(await this.productTitles.nth(i).innerText());
    }
    expectedProducts.forEach(product => expect(productNames).toContain(product));
  }
}

6. Fixtures

fixtures/authFixture.js

import { test as base } from '@playwright/test';
import { LoginPage } from '../pages/LoginPage';

export const test = base.extend({
  loggedInPage: async ({ browser }, use) => {
    const context = await browser.newContext();
    const page = await context.newPage();
    const loginPage = new LoginPage(page);
    await loginPage.goto();
    await loginPage.login('standard_user', 'secret_sauce');
    await use(page);
    await context.close();
  },
});

This fixture logs in once and shares the authenticated session for all tests using loggedInPage.

7. Test Cases

tests/login.spec.js

import { test, expect } from '@playwright/test';
import { LoginPage } from '../pages/LoginPage';
import { config } from '../utils/config';

test('successful login', async ({ page }) => {
  const loginPage = new LoginPage(page);
  await loginPage.goto();
  await loginPage.login(config.validUser.username, config.validUser.password);
  await expect(page).toHaveURL(/inventory/);
});

test('failed login with invalid credentials', async ({ page }) => {
  const loginPage = new LoginPage(page);
  await loginPage.goto();
  await loginPage.login(config.invalidUser.username, config.invalidUser.password);
  await expect(loginPage.errorMessage).toBeVisible();
});

tests/dashboard.spec.js

import { test, expect } from '../fixtures/authFixture';
import { DashboardPage } from '../pages/DashboardPage';
import { testData } from '../utils/testData';

test('verify product list on dashboard', async ({ loggedInPage }) => {
  const dashboard = new DashboardPage(loggedInPage);
  await dashboard.verifyProducts(testData.products);
});

tests/api.spec.js

import { test, expect } from '@playwright/test';
import { getUserData } from '../utils/apiHelper';

test('validate API user data', async () => {
  const user = await getUserData();
  expect(user).toHaveProperty('username');
  expect(user.id).toBe(1);
});

8. CI/CD Integration

Here’s a sample GitHub Actions workflow for running this project.

.github/workflows/playwright.yml

name: Playwright Framework CI

on:
  push:
    branches: [ main ]
  pull_request:
    branches: [ main ]

jobs:
  test:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v3

      - name: Setup Node.js
        uses: actions/setup-node@v3
        with:
          node-version: 18

      - name: Install dependencies
        run: npm ci

      - name: Install Playwright browsers
        run: npx playwright install --with-deps

      - name: Run Playwright tests
        run: npx playwright test --reporter=html

      - name: Upload Playwright HTML Report
        uses: actions/upload-artifact@v3
        with:
          name: playwright-report
          path: playwright-report/

9. Running the project locally

To execute all tests locally:

npx playwright test

To run a specific test file:

npx playwright test tests/login.spec.js

To generate and open the HTML report:

npx playwright show-report

10. Debugging and artifacts

Enable traces, screenshots, and videos for failed tests.

export default {
  use: {
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
    trace: 'retain-on-failure',
  },
};

After the run, use:

npx playwright show-trace trace.zip

This opens an interactive timeline showing DOM snapshots, network calls, and console logs.

Key Takeaways

  1. Use POM + Fixtures for maintainability.
  2. Keep config centralized in playwright.config.js.
  3. Store test data and utilities separately.
  4. Integrate with CI/CD pipelines for continuous testing.
  5. Collect and review artifacts for debugging.
  6. Modularize your framework to add new features easily.

12. Exercise

Task 1: Add a test that verifies the shopping cart functionality.
Task 2: Extend apiHelper.js to perform POST and DELETE operations.
Task 3: Add parallel browser execution in your config.
Task 4: Integrate Slack notifications for failed CI tests.
Bonus: Deploy this project to GitHub and set up scheduled nightly test runs.

Summary

In this final, real-world project, you built a complete, production-grade Playwright framework with:

  • Modular Page Objects
  • Fixtures and utilities
  • API and UI integration tests
  • File upload/download handling
  • CI/CD integration and reporting

Congratulations! You’ve reached the end of Practical Playwright Automation with JavaScript a complete, hands-on journey from Playwright basics to advanced, production-level automation.

By now, you’ve not only learned how to use Playwright but also how to design, optimize, and deploy a full-featured, real-world automation framework.

Previous Chapter Chapter 17 – Playwright Advanced Tips and Optimization