This project is a Playwright automation framework built using JavaScript.
The framework follows:
- Page Object Model (POM)
- Playwright Test
- Reusable fixtures
- Test data separation
- Utility functions
- Environment-based configuration
- UI and API automation support
- HTML reporting
- Screenshot and video capture
- CI/CD readiness
Playwright-Automation/
│
├── tests/
│ ├── login/
│ │ ├── login.spec.js
│ │ └── forgotPassword.spec.js
│ │
│ ├── ecommerce/
│ │ ├── productListing.spec.js
│ │ ├── productDetails.spec.js
│ │ └── cart.spec.js
│ │
│ └── api/
│ └── auth.api.spec.js
│
├── pages/
│ ├── LoginPage.js
│ ├── ForgotPasswordPage.js
│ ├── ProductListingPage.js
│ ├── ProductDetailsPage.js
│ └── CartPage.js
│
├── fixtures/
│ └── base.fixture.js
│
├── test-data/
│ ├── users.json
│ └── products.json
│
├── utils/
│ ├── commonUtils.js
│ ├── apiHelper.js
│ └── randomData.js
│
├── config/
│ └── environments.js
│
├── reports/
│
├── screenshots/
│
├── downloads/
│
├── videos/
│ └── New Tab - Google Chrome 2026-08-08 11-36-07.mp4
│
├── playwright.config.js
├── package.json
├── .env
├── .env.example
├── .gitignore
└── README.md
The tests folder contains all Playwright test cases.
Tests should contain:
- Test scenarios
- Assertions
- Test descriptions
- Test grouping
Avoid putting:
- Locators
- Common reusable functions
- Environment configuration
- Large amounts of test data
tests/
├── login/
├── ecommerce/
└── api/
import { test, expect } from '@playwright/test';
import { LoginPage } from '../../pages/LoginPage';
test.describe('Login Tests', () => {
test('Verify user can login successfully', async ({ page }) => {
const loginPage = new LoginPage(page);
await loginPage.navigate();
await loginPage.login(
'test@example.com',
'Password123'
);
await expect(page).toHaveURL(/dashboard/);
});
});The pages folder follows the Page Object Model (POM) design pattern.
Each application page should have a corresponding Page Object.
Example:
pages/
├── LoginPage.js
├── ForgotPasswordPage.js
├── ProductListingPage.js
├── ProductDetailsPage.js
└── CartPage.js
Page Objects should contain:
- Locators
- Page actions
- Reusable page-level methods
export class LoginPage {
constructor(page) {
this.page = page;
this.emailInput = page.locator('#email');
this.passwordInput = page.locator('#password');
this.loginButton = page.getByRole(
'button',
{ name: 'Login' }
);
}
async navigate() {
await this.page.goto('/login');
}
async enterEmail(email) {
await this.emailInput.fill(email);
}
async enterPassword(password) {
await this.passwordInput.fill(password);
}
async clickLogin() {
await this.loginButton.click();
}
async login(email, password) {
await this.enterEmail(email);
await this.enterPassword(password);
await this.clickLogin();
}
}The fixtures folder contains custom Playwright fixtures.
Fixtures can be used for:
- Page Object initialization
- Login setup
- Authentication
- Test data
- Reusable setup/teardown
import { test as base } from '@playwright/test';
import { LoginPage } from '../pages/LoginPage';
export const test = base.extend({
loginPage: async ({ page }, use) => {
const loginPage = new LoginPage(page);
await use(loginPage);
}
});Tests can then use:
import { test } from '../../fixtures/base.fixture';
test('Login Test', async ({ loginPage }) => {
await loginPage.navigate();
await loginPage.login(
'test@example.com',
'Password123'
);
});The test-data folder stores test data separately from test scripts.
Example:
test-data/
├── users.json
└── products.json
{
"validUser": {
"email": "test@example.com",
"password": "Password123"
},
"invalidUser": {
"email": "invalid@example.com",
"password": "WrongPassword"
}
}This approach helps avoid hardcoding test data inside test cases.
The utils folder contains reusable helper functions.
utils/
├── commonUtils.js
├── apiHelper.js
└── randomData.js
Contains generic reusable functions.
Examples:
- Wait helpers
- Date helpers
- Element utilities
- Common browser actions
Contains reusable API functions.
Examples:
- GET
- POST
- PUT
- DELETE
- Authentication requests
Contains functions for generating dynamic test data.
Example:
export function generateRandomEmail() {
return `test_${Date.now()}@example.com`;
}The config folder contains environment-specific configuration.
Example:
config/
└── environments.js
export const environments = {
uat: {
baseURL: 'https://uat.example.com'
},
staging: {
baseURL: 'https://staging.example.com'
},
production: {
baseURL: 'https://example.com'
}
};This makes it easier to run tests against different environments.
The reports folder contains generated Playwright reports.
Example:
reports/
└── html/
The HTML report can be opened using:
npx playwright show-reportReports should generally not be committed to Git.
Screenshots generated during test execution can be stored here.
Recommended configuration:
use: {
screenshot: 'only-on-failure'
}Screenshots should primarily be captured when tests fail.
The downloads folder is used for files downloaded during automation.
Examples:
- Excel
- CSV
- Images
- Reports
Example test:
const downloadPromise = page.waitForEvent('download');
await page.getByText('Download').click();
const download = await downloadPromise;
await download.saveAs(
'downloads/report.xlsx'
);The videos folder contains the reference/input meeting recording video(s) demonstrating the end-to-end user flows that need to be automated (e.g. New Tab - Google Chrome 2026-08-08 11-36-07.mp4).
This is the main Playwright configuration file.
It controls:
- Test directory
- Timeout
- Browser configuration
- Base URL
- Retries
- Workers
- Reports
- Screenshots
- Videos
- Trace
- Browser projects
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
timeout: 30 * 1000,
expect: {
timeout: 5000
},
fullyParallel: true,
forbidOnly: !!process.env.CI,
retries: process.env.CI ? 2 : 0,
workers: process.env.CI ? 2 : undefined,
reporter: [
['html', {
outputFolder: 'reports/html'
}],
['list']
],
use: {
baseURL: process.env.BASE_URL,
trace: 'retain-on-failure',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
headless: true
},
projects: [
{
name: 'chromium',
use: {
...devices['Desktop Chrome']
}
},
{
name: 'firefox',
use: {
...devices['Desktop Firefox']
}
},
{
name: 'webkit',
use: {
...devices['Desktop Safari']
}
}
]
});Environment variables should be stored in .env.
Example:
BASE_URL=https://uat.example.com
API_URL=https://uat.example.com/api
Sensitive credentials should also be stored through environment variables rather than directly inside test scripts.
Do not commit .env to Git.
The .env.example file documents the required environment variables without exposing actual credentials.
Example:
BASE_URL=
API_URL=
USERNAME=
PASSWORD=
This file can safely be committed to Git.
The .gitignore should exclude generated and sensitive files.
Example:
node_modules/
.env
playwright-report/
test-results/
reports/
screenshots/
videos/
downloads/
*.log
The package.json contains:
- Project information
- Playwright dependency
- Test scripts
- Other npm dependencies
Example scripts:
{
"scripts": {
"test": "npx playwright test",
"test:headed": "npx playwright test --headed",
"test:debug": "npx playwright test --debug",
"test:chromium": "npx playwright test --project=chromium",
"test:firefox": "npx playwright test --project=firefox",
"test:webkit": "npx playwright test --project=webkit",
"report": "npx playwright show-report"
}
}The README should contain project documentation.
Recommended sections:
Project Overview
Prerequisites
Installation
Project Structure
Environment Setup
How to Run Tests
Browser Execution
Test Tags
Reports
Debugging
Coding Standards
Git Workflow
CI/CD
Troubleshooting
The recommended architecture is:
┌──────────────────┐
│ Test Specs │
│ tests/*.spec.js│
└────────┬─────────┘
│
▼
┌──────────────────┐
│ Fixtures │
│ base.fixture.js │
└────────┬─────────┘
│
▼
┌──────────────────┐
│ Page Objects │
│ pages/*.js │
└────────┬─────────┘
│
┌───────────┴───────────┐
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ Utils │ │ API Layer │
│ utils/*.js │ │ api/*.js │
└────────┬────────┘ └────────┬────────┘
│ │
└────────────┬───────────┘
▼
┌──────────────────┐
│ Application │
│ Web / API / SUT │
└──────────────────┘
Do not write selectors directly in test files.
await page.locator('#email').fill('test@example.com');await loginPage.enterEmail('test@example.com');Prefer:
page.getByRole()
page.getByLabel()
page.getByText()
page.getByPlaceholder()Use CSS/XPath only when necessary.
await page.waitForTimeout(5000);await expect(element).toBeVisible();or:
await page.waitForLoadState('networkidle');when appropriate.
Use descriptive test names.
Recommended:
login.spec.js
forgotPassword.spec.js
productListing.spec.js
productDetails.spec.js
cart.spec.js
Test cases:
test('Verify user can login with valid credentials', async () => {});test('Verify error message for invalid password', async () => {});npx playwright testnpx playwright test --headednpx playwright test tests/login/login.spec.jsnpx playwright test --project=chromiumnpx playwright test --project=firefoxnpx playwright test --project=webkitnpx playwright test --debugnpx playwright show-reportThe framework can later be extended with:
- Authentication / storage state
- API + UI hybrid testing
- Cucumber BDD
- Allure reporting
- CI/CD with GitHub Actions
- Docker execution
- Parallel execution
- Cross-browser testing
- Mobile browser testing
- Accessibility testing
- Visual regression testing
- Database validation
- MCP integration
- AI-assisted test generation
- OpenCode-based framework maintenance
The final baseline structure should remain simple and scalable:
Playwright-Automation/
│
├── tests/
│ ├── login/
│ ├── ecommerce/
│ └── api/
│
├── pages/
│
├── fixtures/
│
├── test-data/
│
├── utils/
│
├── config/
│
├── reports/
├── screenshots/
├── downloads/
├── videos/
│
├── playwright.config.js
├── package.json
├── .env
├── .env.example
├── .gitignore
└── README.md
This structure provides a clean foundation for a maintainable, scalable Playwright JavaScript automation framework and can be expanded as the automation suite grows.