Playwright Tutorial: Beginner to Advanced
Since you already work in automation testing, I'll cover Playwright from a QA Automation Engineer perspective, including interview-focused concepts and framework design.
1. What is Playwright?
Playwright is a modern automation framework developed by Microsoft Playwright for web application testing.
Features
- Supports Chromium, Firefox, and WebKit
- Cross-platform (Windows, Linux, macOS)
- Auto-wait mechanism
- Parallel execution
- API Testing
- Mobile emulation
- Network interception
- Visual testing
- CI/CD integration
2. Installation
Install Node.js
Download Node.js:
Verify:
node -v
npm -v
Create Project
mkdir PlaywrightFramework
cd PlaywrightFramework
npm init -y
Install Playwright
npm init playwright@latest
Select:
✔ TypeScript
✔ Tests Folder
✔ GitHub Actions
✔ Install Browsers
3. Project Structure
PlaywrightFramework
tests/
pages/
utils/
fixtures/
test-data/
playwright.config.ts
package.json
4. First Test
import { test, expect } from '@playwright/test';
test('Login Page Title', async ({ page }) => {
await page.goto('https://example.com');
await expect(page).toHaveTitle(/Example/);
});
Run:
npx playwright test
5. Playwright Inspector
Debug Test
npx playwright test --debug
Inspector opens.
Useful for:
- Step execution
- Locator identification
- Debugging failures
6. Record Script
npx playwright codegen https://example.com
Playwright records actions automatically.
7. Locators
By Text
page.getByText('Login')
By Role
page.getByRole('button', { name: 'Login' })
By Label
page.getByLabel('Email')
CSS
page.locator('#username')
XPath
page.locator('//input[@id="username"]')
8. Actions
Click
await page.locator('#login').click();
Fill
await page.fill('#username','admin');
Type
await page.locator('#username').type('admin');
Dropdown
await page.selectOption('#country','India');
Checkbox
await page.check('#remember');
9. Assertions
await expect(page).toHaveTitle('Dashboard');
await expect(locator).toBeVisible();
await expect(locator).toContainText('Welcome');
await expect(locator).toBeEnabled();
10. Handling Waits
Auto Wait
await page.click('#login');
Playwright automatically waits.
Explicit Wait
await page.waitForSelector('#dashboard');
Network Wait
await page.waitForLoadState('networkidle');
11. Input Fields
await page.fill('#email','test@test.com');
await page.fill('#password','123456');
12. Mouse Actions
await page.hover('#menu');
await page.dragAndDrop(
'#source',
'#target'
);
13. Keyboard Actions
await page.keyboard.press('Enter');
await page.keyboard.press('Control+A');
await page.keyboard.press('Delete');
14. Frames
const frame =
page.frame({ name: 'frame1' });
await frame.fill('#username','admin');
Or:
const frame =
page.frameLocator('#frame');
await frame.locator('#login').click();
15. Multiple Tabs
const pagePromise =
context.waitForEvent('page');
await page.click('a');
const newPage =
await pagePromise;
16. File Upload
await page.setInputFiles(
'#upload',
'test.pdf'
);
17. Download File
const downloadPromise =
page.waitForEvent('download');
await page.click('#download');
const download =
await downloadPromise;
await download.saveAs('report.pdf');
18. Screenshot
await page.screenshot({
path:'home.png'
});
Element Screenshot:
await page.locator('#logo')
.screenshot({
path:'logo.png'
});
19. Handling Alerts
page.on('dialog',
async dialog => {
console.log(dialog.message());
await dialog.accept();
});
Reject:
await dialog.dismiss();
20. API Testing
import { test, expect } from '@playwright/test';
test('GET API', async ({ request }) => {
const response =
await request.get(
'https://reqres.in/api/users/2'
);
expect(response.status())
.toBe(200);
});
21. Authentication
const response =
await request.post('/login', {
data:{
email:'admin@test.com',
password:'123'
}
});
22. Data Driven Testing
const users = [
{
username:'admin',
password:'admin123'
},
{
username:'user',
password:'user123'
}
];
for(const user of users){
test(`Login ${user.username}`,
async ({ page }) => {
});
}
23. Page Object Model (POM)
LoginPage.ts
export class LoginPage {
constructor(page){
this.page = page;
this.username =
page.locator('#username');
this.password =
page.locator('#password');
this.loginBtn =
page.locator('#login');
}
async login(user,pass){
await this.username.fill(user);
await this.password.fill(pass);
await this.loginBtn.click();
}
}
Test
import { LoginPage }
from '../pages/LoginPage';
test('Login Test',
async ({ page }) => {
const login =
new LoginPage(page);
await login.login(
'admin',
'admin123'
);
});
24. Parallel Execution
npx playwright test --workers=4
Config:
workers: 4
25. Retry Failed Tests
retries: 2
26. Cross Browser Testing
projects: [
{
name:'Chrome'
},
{
name:'Firefox'
},
{
name:'Safari'
}
]
Run:
npx playwright test
27. CI/CD with GitHub Actions
name: Playwright
on:
push:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- run: npm ci
- run: npx playwright install
- run: npx playwright test
28. Network Interception
await page.route('**/users',
route => route.fulfill({
status:200,
body:JSON.stringify({
name:'Pushkar'
})
})
);
29. Mock API
await page.route(
'**/api/login',
route => route.fulfill({
status:200,
body:'Success'
})
);
30. Advanced Framework Structure
PlaywrightFramework
pages/
LoginPage.ts
HomePage.ts
tests/
login.spec.ts
utils/
logger.ts
helper.ts
fixtures/
baseFixture.ts
data/
users.json
reports/
playwright.config.ts
31. Playwright Interview Questions
Why Playwright over Selenium?
Answer:
- Faster execution
- Auto-waiting
- Better handling of modern web apps
- Multiple browser engines
- Built-in API testing
- Parallel execution support
What is auto-waiting?
Playwright automatically waits for:
- Element visibility
- Stability
- Clickability
- Navigation completion
Difference between Locator and ElementHandle?
Locator:
- Dynamic
- Recommended
- Auto waits
ElementHandle:
- Static reference
- Can become stale
32. Advanced Topics for Senior QA
- Playwright with TypeScript
- POM + Fixtures
- Custom Reporter
- Allure Integration
- API + UI Hybrid Testing
- Docker Execution
- Jenkins Pipeline
- BrowserStack Integration
- Visual Regression Testing
- Network Mocking
- Contract Testing
- Accessibility Testing
- Performance Metrics Collection
- Microservices API Automation
Recommended Learning Path
- Basics & Locators
- Actions & Assertions
- Frames & Windows
- API Testing
- Data-Driven Testing
- POM Design Pattern
- Fixtures
- Parallel Execution
- Network Mocking
- CI/CD Integration
- Allure Reporting
- BrowserStack Execution
- Enterprise Framework Design
Given your 6+ years of QA experience and automation background, the next valuable step would be building a real-world Playwright framework with TypeScript, POM, API testing, Allure reporting, Jenkins, GitHub Actions, and BrowserStack integration.
Excellent. Since you're already experienced in automation testing, I'll focus on an Enterprise-Level Playwright Framework that is commonly expected for Senior QA Engineer, QA Lead, and SDET interviews.
Phase 1: Create Playwright Framework
Project Structure
PlaywrightFramework
├── tests
│ ├── login.spec.ts
│ ├── dashboard.spec.ts
│
├── pages
│ ├── LoginPage.ts
│ ├── DashboardPage.ts
│
├── fixtures
│ ├── baseFixture.ts
│
├── utils
│ ├── Logger.ts
│ ├── APIHelper.ts
│
├── testdata
│ ├── users.json
│
├── reports
│
├── playwright.config.ts
│
├── package.json
Phase 2: Install Dependencies
npm init playwright@latest
Install additional packages:
npm install dotenv
npm install allure-playwright
npm install axios
npm install winston
Phase 3: Page Object Model
LoginPage.ts
import { Page } from '@playwright/test';
export class LoginPage {
constructor(private page: Page) {}
username = this.page.locator('#username');
password = this.page.locator('#password');
loginBtn = this.page.locator('#login');
async login(user: string, pass: string) {
await this.username.fill(user);
await this.password.fill(pass);
await this.loginBtn.click();
}
}
DashboardPage.ts
import { Page } from '@playwright/test';
export class DashboardPage {
constructor(private page: Page) {}
dashboardTitle = this.page.locator('.dashboard');
async verifyDashboard() {
return this.dashboardTitle.isVisible();
}
}
Phase 4: Test Data Management
users.json
{
"validUser": {
"username": "admin",
"password": "admin123"
},
"invalidUser": {
"username": "test",
"password": "wrong"
}
}
Test Example
import { test, expect } from '@playwright/test';
import { LoginPage } from '../pages/LoginPage';
const data = require('../testdata/users.json');
test('Valid Login', async ({ page }) => {
const loginPage = new LoginPage(page);
await page.goto('https://example.com');
await loginPage.login(
data.validUser.username,
data.validUser.password
);
await expect(page).toHaveURL(/dashboard/);
});
Phase 5: Fixtures
baseFixture.ts
import { test as base } from '@playwright/test';
import { LoginPage } from '../pages/LoginPage';
type MyFixtures = {
loginPage: LoginPage;
};
export const test = base.extend<MyFixtures>({
loginPage: async ({ page }, use) => {
await use(new LoginPage(page));
}
});
export { expect } from '@playwright/test';
Usage
import { test, expect } from '../fixtures/baseFixture';
test('Login', async ({ page, loginPage }) => {
await page.goto('https://example.com');
await loginPage.login(
'admin',
'admin123'
);
});
Phase 6: API Testing
APIHelper.ts
import axios from 'axios';
export class APIHelper {
async getUser() {
const response = await axios.get(
'https://reqres.in/api/users/2'
);
return response;
}
}
API Test
import { test, expect } from '@playwright/test';
test('Get User API', async ({ request }) => {
const response =
await request.get(
'https://reqres.in/api/users/2'
);
expect(response.status()).toBe(200);
const body = await response.json();
expect(body.data.id).toBe(2);
});
Phase 7: Environment Configuration
.env
QA_URL=https://qa.site.com
UAT_URL=https://uat.site.com
PROD_URL=https://prod.site.com
playwright.config.ts
import * as dotenv from 'dotenv';
dotenv.config();
export default {
use: {
baseURL: process.env.QA_URL,
headless: false
}
};
Phase 8: Retry & Parallel Execution
export default {
retries: 2,
workers: 4
}
Run:
npx playwright test --workers=4
Phase 9: Screenshots & Videos
use: {
screenshot: 'only-on-failure',
video: 'retain-on-failure',
trace: 'retain-on-failure'
}
Phase 10: Allure Reporting
Install:
npm install allure-playwright
Config:
reporter: [
['list'],
['allure-playwright']
]
Generate Report:
npx allure generate allure-results --clean
npx allure open
Phase 11: Network Mocking
test('Mock Login API', async ({ page }) => {
await page.route(
'**/login',
async route => {
await route.fulfill({
status: 200,
body: JSON.stringify({
success: true
})
});
}
);
});
Phase 12: Visual Testing
test('Homepage Snapshot',
async ({ page }) => {
await page.goto('/');
await expect(page)
.toHaveScreenshot();
});
Phase 13: BrowserStack Integration
Install:
npm install browserstack-node-sdk
browserstack.yml
userName: USERNAME
accessKey: ACCESS_KEY
framework: playwright
Run:
browserstack-node-sdk playwright test
Phase 14: Jenkins Integration
Jenkinsfile
pipeline {
agent any
stages {
stage('Install') {
steps {
sh 'npm install'
}
}
stage('Test') {
steps {
sh 'npx playwright test'
}
}
stage('Allure') {
steps {
allure([
includeProperties: false
])
}
}
}
}
Phase 15: Advanced Interview Questions
What is a Fixture in Playwright?
A fixture provides reusable setup and teardown logic that can be injected into tests.
What is Trace Viewer?
Playwright Trace Viewer records:
- Screenshots
- Network calls
- Console logs
- DOM snapshots
Open trace:
npx playwright show-trace trace.zip
How do you handle flaky tests?
- Use stable locators
- Avoid hard waits
- Use retries
- Network mocking
- Explicit assertions
- Test isolation
Playwright vs Selenium
| Feature | Playwright | Selenium |
|---|---|---|
| Auto Wait | Yes | No |
| Speed | Fast | Moderate |
| API Testing | Built-in | External |
| Parallel Execution | Native | Grid |
| Mobile Emulation | Built-in | Limited |
| Network Mocking | Yes | No |
Enterprise-Level Framework Features
A mature Playwright framework should include:
✅ TypeScript
✅ POM Design Pattern
✅ Fixtures
✅ API + UI Testing
✅ Allure Reports
✅ Jenkins Pipeline
✅ GitHub Actions
✅ BrowserStack
✅ Docker
✅ Environment Management
✅ Data-Driven Testing
✅ Retry Mechanism
✅ Screenshot/Video Capture
✅ Trace Viewer
✅ Network Mocking
✅ Visual Testing
✅ Slack/Email Notifications
Since your background includes game automation and QA leadership, the next advanced step would be a Playwright + TypeScript Hybrid Framework with UI, API, Database Validation, Allure, Jenkins, BrowserStack, Docker, and GitHub Actions, similar to what large product companies use.
Enterprise Playwright Framework (UI + API + DB + Allure + Jenkins + Docker)
This is the kind of framework architecture commonly used in product companies and expected from Senior QA, QA Lead, and SDET candidates.
Folder Structure
playwright-enterprise-framework
├── tests
│ ├── ui
│ │ ├── login.spec.ts
│ │ ├── dashboard.spec.ts
│ │
│ ├── api
│ │ ├── userApi.spec.ts
│ │
│ └── db
│ ├── userValidation.spec.ts
│
├── pages
│ ├── LoginPage.ts
│ ├── DashboardPage.ts
│
├── api
│ ├── UserAPI.ts
│
├── database
│ ├── DBHelper.ts
│
├── fixtures
│ ├── baseFixture.ts
│
├── utils
│ ├── Logger.ts
│ ├── ConfigReader.ts
│ ├── ScreenshotUtil.ts
│
├── testdata
│ ├── users.json
│
├── reports
├── allure-results
├── allure-report
│
├── .env
├── Dockerfile
├── Jenkinsfile
├── playwright.config.ts
│
└── package.json
Step 1: Install Dependencies
npm install
npm install dotenv
npm install allure-playwright
npm install axios
npm install mysql2
npm install pg
npm install winston
npm install faker
Step 2: Environment Configuration
.env
BASE_URL=https://qa.myapp.com
DB_HOST=localhost
DB_USER=root
DB_PASSWORD=password
DB_NAME=testdb
API_URL=https://api.myapp.com
Config Reader
import dotenv from 'dotenv';
dotenv.config();
export const config = {
baseUrl: process.env.BASE_URL,
apiUrl: process.env.API_URL,
dbHost: process.env.DB_HOST,
dbUser: process.env.DB_USER,
dbPassword: process.env.DB_PASSWORD,
dbName: process.env.DB_NAME
};
Step 3: Logger Utility
Logger.ts
import winston from 'winston';
export const logger = winston.createLogger({
transports: [
new winston.transports.Console(),
new winston.transports.File({
filename: 'execution.log'
})
]
});
Usage:
logger.info('Login Started');
logger.error('Login Failed');
Step 4: Database Validation
MySQL Example
DBHelper.ts
import mysql from 'mysql2/promise';
export class DBHelper {
async executeQuery(query: string) {
const connection =
await mysql.createConnection({
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME
});
const [rows] =
await connection.execute(query);
await connection.end();
return rows;
}
}
Database Validation Test
import { test, expect }
from '@playwright/test';
import { DBHelper }
from '../../database/DBHelper';
test('Validate User In DB',
async () => {
const db = new DBHelper();
const users =
await db.executeQuery(
"SELECT * FROM users WHERE id=1"
);
expect(users.length)
.toBeGreaterThan(0);
});
Step 5: API Layer
UserAPI.ts
import axios from 'axios';
export class UserAPI {
async getUser(id:number) {
return await axios.get(
`${process.env.API_URL}/users/${id}`
);
}
}
API Test
import { test, expect }
from '@playwright/test';
import { UserAPI }
from '../../api/UserAPI';
test('Get User API',
async () => {
const api = new UserAPI();
const response =
await api.getUser(2);
expect(response.status)
.toBe(200);
});
Step 6: UI + API Validation
A common interview question.
Scenario
- Create User via API
- Login UI
- Search User
- Validate Data
test('End-to-End Validation',
async ({ page, request }) => {
const response =
await request.post('/users', {
data: {
name:'Pushkar'
}
});
const body =
await response.json();
const userId =
body.id;
await page.goto('/users');
await page.fill(
'#search',
userId
);
await page.click('#searchBtn');
await expect(
page.locator('.username')
)
.toContainText('Pushkar');
});
Step 7: Custom Fixture
import { test as base }
from '@playwright/test';
import { LoginPage }
from '../pages/LoginPage';
type MyFixture = {
loginPage: LoginPage
};
export const test =
base.extend<MyFixture>({
loginPage:
async ({ page }, use) => {
await use(
new LoginPage(page)
);
}
});
Step 8: Screenshot Utility
import { Page }
from '@playwright/test';
export async function captureScreenshot(
page: Page,
name: string
){
await page.screenshot({
path:
`reports/${name}.png`
});
}
Step 9: Retry Failed Tests
retries: 2
Step 10: Parallel Execution
workers: 6
Step 11: Allure Reporting
Config:
reporter: [
['list'],
['allure-playwright']
]
Generate Report:
npx allure generate
npx allure open
Step 12: Docker Integration
Dockerfile
FROM mcr.microsoft.com/playwright:v1.55.0
WORKDIR /app
COPY . .
RUN npm install
CMD ["npx","playwright","test"]
Build:
docker build -t playwright .
docker run playwright
Step 13: GitHub Actions
name: Playwright
on:
push:
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
- run: npm ci
- run: npx playwright install
- run: npx playwright test
Step 14: Jenkins Pipeline
pipeline {
agent any
stages {
stage('Install') {
steps {
sh 'npm install'
}
}
stage('Execute') {
steps {
sh 'npx playwright test'
}
}
stage('Allure') {
steps {
allure()
}
}
}
}
Step 15: BrowserStack Parallel Execution
browserstack.yml
platforms:
- os: Windows
osVersion: 11
browserName: chrome
- os: OS X
osVersion: Ventura
browserName: safari
Execute:
browserstack-node-sdk playwright test
Step 16: Advanced Playwright Concepts
Network Interception
await page.route(
'**/users',
route => route.fulfill({
status:200,
body:'mocked'
})
);
Request Monitoring
page.on(
'request',
request => {
console.log(
request.url()
);
});
Response Monitoring
page.on(
'response',
response => {
console.log(
response.status()
);
});
Step 17: Visual Regression Testing
await expect(page)
.toHaveScreenshot(
'homepage.png'
);
Step 18: Playwright Interview Questions for QA Lead
What framework architecture would you choose?
- POM
- Fixtures
- API Layer
- Utility Layer
- Reporting Layer
- CI/CD Layer
- Test Data Layer
How do you reduce flaky tests?
- Stable locators
- Avoid hard waits
- Network mocking
- Retry strategy
- Test isolation
- Independent test data
How do you run tests in parallel safely?
- Separate test data
- Unique users
- Independent environments
- Cleanup after execution
How do you integrate UI, API, and DB testing?
- Create data via API
- Validate in UI
- Validate in DB
- Clean up via API