What is Playwright?
Playwright is a modern, open-source automation framework developed by Microsoft, enabling fast, reliable end-to-end testing of web applications. It supports multiple languages (JavaScript, TypeScript, Python, Java, .NET) and covers all modern browsers like Chromium, Firefox, and WebKit.
Playwright’s strength is its automatic wait handling, modern architecture, and cross-browser capabilities. It is especially popular for testing Single Page Applications (SPAs) with dynamic elements and complex client-side logic.
Why Playwright?
- Faster than legacy Selenium-based frameworks
- Built-in support for modern web frameworks (React, Angular, Vue)
- Cross-browser testing with a single API
- Automatic waiting and retries reduce flaky tests
- First-class support for headless execution
- Powerful features like network mocking, file uploads, geolocation testing
Overall, Playwright is designed to simplify modern test automation challenges with fewer dependencies and less boilerplate code.
Installing Playwright
Playwright is built on Node.js. Before starting, ensure you have Node.js installed on your machine. You can verify by running:
node -v
If not installed, download from Node.js official site
Install Playwright Using NPM
npm init playwright@latest
This command will initialize a new Playwright project, letting you choose:
- Language (JavaScript or TypeScript)
- Whether to use test examples
- Browser options
Playwright will automatically download browser binaries for Chromium, Firefox, and WebKit.
Project Structure
After setup, a typical Playwright project folder will look like this:
tests/
example.spec.js
playwright.config.js
package.json
You are now ready to write your first Playwright script!
Writing Your First Playwright Test
Let’s write a simple test to check the Playwright setup. By default, the Playwright test runner uses @playwright/test
which supports powerful test functions and assertions.
Create a file called example.spec.js
under the tests
folder:
const { test, expect } = require('@playwright/test');
test('should have correct page title',
async ({ page }) => {
await page.goto('https://playwright.dev/');
await expect(page).toHaveTitle(/Playwright/);
});
This test will:
- Open the Playwright homepage
- Check if the page title contains the word “Playwright”
Running the Test
In your terminal, run:
npx playwright test
Playwright will launch the browser in headless mode and execute your test. You’ll see a success or failure report in the console.
View HTML Reports
After running, you can generate an HTML report:
npx playwright show-report
This will open a graphical test report in your browser.
Understanding Locators in Playwright
Locators are at the heart of Playwright. They help you identify and interact with page elements. Playwright offers powerful locator strategies that automatically retry and wait until the element is ready, reducing flaky tests.
Common Locator Types
- Text selector:
page.locator('text=Login')
- CSS selector:
page.locator('button.submit')
- XPath:
page.locator('//button[@type=\"submit\"]')
- Role-based:
page.getByRole('button', { name: 'Submit' })
Using Locators
You can perform actions with locators:
await page.locator('#username').fill('myUser');
await page.locator('#password').fill('myPass');
await page.locator('button[type=submit]').click();
Playwright’s locator engine supports chaining and powerful queries:
await page.locator('.card').
filter({ hasText: 'Special Offer' }).click();
These locators make it easy to write readable, maintainable tests.
Assertions in Playwright
Playwright includes a powerful set of built-in assertions that integrate seamlessly with its test runner. Assertions validate that the page or elements meet expected conditions.
Common Assertions
- Check URL:
await expect(page).toHaveURL(/.*dashboard/)
- Check page title:
await expect(page).toHaveTitle(/Dashboard/)
- Check element text:
await expect(page.locator('h1')).toHaveText('Welcome')
- Check visibility:
await expect(page.locator('#menu')).toBeVisible()
Why Assertions Matter
Assertions make your tests meaningful by verifying the correct state of the application after performing actions. Without assertions, you are only performing steps without validating outcomes.
Best Practices
- Use Playwright’s built-in assertions whenever possible
- Include at least one assertion per test case
- Keep assertions clear and precise to avoid confusion
Handling Dropdowns, Alerts, and Iframes
Handling Dropdowns
You can select an option from a dropdown using:
await page.selectOption('#country', 'IN');
// Selects India by value
You can also select by label or index with selectOption
.
Handling Alerts and Pop-ups
Playwright provides event listeners for handling dialogs:
page.on('dialog', async dialog => {
console.log(dialog.message());
await dialog.accept();
});
This pattern helps manage confirmation boxes or JavaScript alerts.
Working with Iframes
To work with elements inside an iframe:
const frame = page.frame({ name: 'myFrame' });
await frame.click('button#submit');
You can locate the iframe either by its name
, url
, or other attributes.
File Uploads
Playwright supports file uploads directly:
await page.setInputFiles('input[type=file]'
, 'myfile.pdf');
File Downloads
To handle downloads:
const [ download ] = await Promise.
all([
page.waitForEvent('download'),
page.click('a#downloadLink')
]);
await download.saveAs('myfile.pdf');
Page Object Model in Playwright
The Page Object Model (POM) is a design pattern that helps you organize test code by separating page interactions into reusable classes. This improves maintainability and readability.
Creating a Page Object
For example, create a file called loginPage.js
:
class LoginPage {
constructor(page) {
this.page = page;
this.usernameInput = page.locator('#username');
this.passwordInput = page.locator('#password');
this.loginButton = page.locator(
'button[type=submit]');
}
async login(username, password) {
await this.usernameInput.fill(username);
await this.passwordInput.fill(password);
await this.loginButton.click();
}
}
module.exports = { LoginPage };
Using the Page Object
In your test file:
const { LoginPage } = require('./loginPage');
test('login scenario', async ({ page })
=> {
const login = new LoginPage(page);
await page.goto('https://example.com/login');
await login.login('user1', 'pass1');
await expect(page).toHaveURL(/dashboard/);
});
POM makes tests cleaner and easier to update when page layouts change.
Data-driven Testing in Playwright
Data-driven testing lets you run the same test scenario with different sets of data. This improves coverage and reduces duplication.
Example with Inline Data
const { test, expect } = require('@playwright/test');
const dataSet = [
{ username: 'user1', password: 'pass1' },
{ username: 'user2', password: 'pass2' }
];
for (const data of dataSet) {
test(`login test for ${data.username}`,
async ({ page }) => {
await page.goto('https://example.com/login');
await page.fill('#username', data.username);
await page.fill('#password', data.password);
await page.click('button[type=submit]');
await expect(page).toHaveURL(/dashboard/);
});
}
Reading from JSON
You can also store test data in an external JSON file and import it:
// data.json
[
{ "username": "u1", "password": "p1" },
{ "username": "u2", "password": "p2" }
]
// test file
const data = require('./data.json');
for (const record of data) {
test(`login test for ${record.username}`,
async ({ page }) => {
// same steps
});
}
Benefits
- Test multiple variations with minimal code
- Easier to maintain
- Encourages reusable scripts
Parallel Test Execution in Playwright
Playwright supports parallel execution natively, running multiple tests simultaneously to speed up overall execution.
How It Works
Playwright creates workers that run test files in parallel. By default, it will choose the number of workers based on your CPU cores.
Running in Parallel
npx playwright test --workers=4
This will run 4 parallel workers. Adjust the number to suit your machine or CI pipeline.
Configuration
You can also configure the number of workers in playwright.config.js
:
module.exports = {
workers: 3
};
Best Practices
- Keep tests independent of each other
- Use isolated test data or cleanup between tests
- Avoid hard-coded waits — rely on Playwright's auto-waiting
Reporting in Playwright
Playwright provides several built-in reporting options to help you analyze test results. The default HTML report is easy to use and visually appealing.
Generating an HTML Report
After running your tests, you can view the report using:
npx playwright show-report
This will open an HTML-based dashboard summarizing your test results, including passed/failed test cases and their timings.
Custom Reporters
Playwright also supports adding custom reporters in your playwright.config.js
:
module.exports = {
reporter: [
['list'],
['json', { outputFile: 'results.json' }],
['html', { open: 'never' }]
]
};
This configuration uses a list reporter, a JSON file for automation pipelines, and an HTML report without auto-opening.
Popular Third-Party Reporting Options
- Allure Playwright Reporter
- JUnit XML
- Custom JSON for dashboards
Reporting helps stakeholders and developers understand test quality over time.
Integrating Playwright with CI/CD
Integrating Playwright into a CI/CD pipeline ensures tests run automatically whenever code changes are pushed. Popular platforms like GitHub Actions, GitLab CI, and Jenkins support Playwright seamlessly.
Example: GitHub Actions
Create a file called .github/workflows/playwright.yml
:
name: Playwright Tests
on:
push:
branches: [ main ]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- name: Install dependencies
run: npm ci
- name: Install Playwright browsers
run: npx playwright install --with-deps
- name: Run tests
run: npx playwright test
Other CI Tools
- Jenkins: Run Playwright tests with a simple shell/batch step
- GitLab CI: Use the
node
Docker image and add Playwright commands in your pipeline - Azure DevOps: Integrate using npm scripts in a pipeline YAML
Benefits of CI/CD Automation
- Catch bugs earlier in the development cycle
- Generate reports after every build
- Save manual testing time
Best Practices in Playwright
Following best practices makes your Playwright tests more reliable, easier to maintain, and faster to execute.
Recommended Practices
- Use the Page Object Model to separate locators and actions from tests
- Keep tests independent; never rely on execution order
- Use Playwright's built-in waits instead of hard-coded
sleep
- Organize your tests logically in folders with consistent naming
- Use environment variables to manage URLs, credentials, and secrets
- Clean up data after tests to avoid polluting other test runs
- Run tests in headless mode for CI pipelines, but optionally headed mode for debugging
Debugging Tips
- Use
page.pause()
to interactively debug in headed mode - Use Playwright Trace Viewer to replay steps and spot errors
- Print console logs with
console.log()
for quick inspection
Maintainability
- Use data-driven patterns to minimize test duplication
- Version-control your tests just like your application code
- Use a consistent linting and formatting style (e.g., ESLint + Prettier)
Final Mini Project: Automated Login Flow
This example demonstrates a simple Playwright test framework structure using the Page Object Model (POM) to test a login feature.
1. Project Structure
playwright-login/
├── tests/
│ └── login.spec.js
├── pages/
│ └── LoginPage.js
├── data/
│ └── credentials.json
├── playwright.config.js
├── package.json
2. Page Object – LoginPage.js
class LoginPage {
constructor(page) {
this.page = page;
this.username = page.locator('#username');
this.password = page.locator('#password');
this.loginBtn = page.locator
('button[type=submit]');
}
async goto() {
await this.page.goto('https://example.com/login');
}
async login(user, pass) {
await this.username.fill(user);
await this.password.fill(pass);
await this.loginBtn.click();
}
}
module.exports = { LoginPage };
3. Test File – login.spec.js
const { test, expect } = require('@playwright/test');
const { LoginPage } = require('../pages/LoginPage');
const data = require('../data/credentials.json');
for (const { username, password } of data) {
test(`Login test for ${username}`, async ({ page })
=> {
const login = new LoginPage(page);
await login.goto();
await login.login(username, password);
await expect(page).toHaveURL(/dashboard/);
});
}
4. Test Data – credentials.json
[
{ "username": "user1", "password": "pass1" },
{ "username": "user2", "password": "pass2" }
]
5. Run the Tests
npx playwright test
This small framework can be extended to other modules like registration, search, dashboard, etc.
Resources & Further Learning
Here are some official and community-driven resources to go deeper with Playwright:
Official Documentation
Core Features of Playwright
- Auto-waiting and retries
- Powerful selectors (text, CSS, XPath, role)
- Headless and headed execution
- Built-in test generator
Advanced Usage of Playwright
- Parallel and sharded test execution
- Taking screenshots and video recording
- Running tests in CI environments
- Using Playwright with Jest, Mocha or Cucumber
Pro Tips
- Use test fixtures for login and setup
- Use
playwright.config.js
for environment settings - Debug tests using
PWDEBUG=1
mode
Real-World Use
- Testing form workflows and validation
- Validating UI components like modals and carousels
- Running cross-browser regression tests