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.
Overall, Playwright is designed to simplify modern test automation challenges with fewer dependencies and less boilerplate code.
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.
npm init playwright@latest
This command will initialize a new Playwright project, letting you choose:
Playwright will automatically download browser binaries for Chromium, Firefox, and WebKit.
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!
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:
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.
After running, you can generate an HTML report:
npx playwright show-report
This will open a graphical test report in your browser.
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.
page.locator('text=Login')
page.locator('button.submit')
page.locator('//button[@type=\"submit\"]')
page.getByRole('button', { name: 'Submit' })
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.
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.
await expect(page).toHaveURL(/.*dashboard/)
await expect(page).toHaveTitle(/Dashboard/)
await expect(page.locator('h1')).toHaveText('Welcome')
await expect(page.locator('#menu')).toBeVisible()
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.
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
.
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.
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.
Playwright supports file uploads directly:
await page.setInputFiles('input[type=file]', 'myfile.pdf');
To handle downloads:
const [ download ] = await Promise.all([
page.waitForEvent('download'),
page.click('a#downloadLink')
]);
await download.saveAs('myfile.pdf');
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.
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 };
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.
↑ Back to Dropdowns & AlertsData-driven testing lets you run the same test scenario with different sets of data. This improves coverage and reduces duplication.
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/);
});
}
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
});
}
Playwright supports parallel execution natively, running multiple tests simultaneously to speed up overall execution.
Playwright creates workers that run test files in parallel. By default, it will choose the number of workers based on your CPU cores.
npx playwright test --workers=4
This will run 4 parallel workers. Adjust the number to suit your machine or CI pipeline.
You can also configure the number of workers in playwright.config.js
:
module.exports = {
workers: 3
};
Playwright provides several built-in reporting options to help you analyze test results. The default HTML report is easy to use and visually appealing.
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.
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.
Reporting helps stakeholders and developers understand test quality over time.
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.
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
node
Docker image and add Playwright commands in your pipelineFollowing best practices makes your Playwright tests more reliable, easier to maintain, and faster to execute.
sleep
page.pause()
to interactively debug in headed modeconsole.log()
for quick inspectionThis example demonstrates a simple Playwright test framework structure using the Page Object Model (POM) to test a login feature.
playwright-login/
├── tests/
│ └── login.spec.js
├── pages/
│ └── LoginPage.js
├── data/
│ └── credentials.json
├── playwright.config.js
├── package.json
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 };
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/);
});
}
[
{ "username": "user1", "password": "pass1" },
{ "username": "user2", "password": "pass2" }
]
npx playwright test
This small framework can be extended to other modules like registration, search, dashboard, etc.
Here are some official and community-driven resources to go deeper with Playwright:
Bookmark these resources and keep exploring the ecosystem as Playwright evolves rapidly.