E2E Testing with Playwright
Playwright is a reliable End-to-End (E2E) testing framework developed by Microsoft. It supports all modern browsers including Chromium, WebKit, and Firefox.
Key Features
- Cross-browser Support: Works on Chrome, Edge, Firefox, Safari (WebKit).
- High Reliability: Auto-wait feature waits for elements to appear before operating, reducing flaky tests.
- Powerful Tools: Comes standard with Code generation (Codegen), Playwright Inspector, Trace Viewer, etc.
- Complete Isolation: Each test runs in an independent browser context, so there is no state interference.
Installation and Setup
To introduce Playwright to your project, run the following command.
npm init playwright@latest
A setup wizard will appear, so select the following items.
- TypeScript or JavaScript (TypeScript recommended)
- Name of test folder (Default:
tests) - Whether to add GitHub Actions workflow
Basic Structure of Tests
Playwright test files are usually placed in the tests directory.
import { test, expect } from '@playwright/test';
test('Homepage title is correct', async ({ page }) => {
// Access page
await page.goto('https://playwright.dev/');
// Verify title contains "Playwright"
await expect(page).toHaveTitle(/Playwright/);
});
test('Get Started link works', async ({ page }) => {
await page.goto('https://playwright.dev/');
// Click "Get started" link
await page.getByRole('link', { name: 'Get started' }).click();
// Verify URL contains "/docs/intro"
await expect(page).toHaveURL(/.*intro/);
});
Locators
Playwright has recommended locators to retrieve elements in a way close to user operations.
page.getByRole(): Retrieve by role such as button, link, heading (Most recommended)page.getByText(): Retrieve by text contentpage.getByLabel(): Retrieve by form labelpage.getByPlaceholder(): Retrieve by placeholder textpage.getByAltText(): Retrieve by image alt textpage.getByTestId(): Retrieve bydata-testidattribute
// Click button
await page.getByRole('button', { name: 'Submit' }).click();
// Text input
await page.getByLabel('Username').fill('user123');
Auto-waiting
One of the major features of Playwright is that it automatically waits for the element to become actionable before performing an operation. This significantly reduces the need to write sleep or explicit wait processing.
For example, when executing click(), Playwright waits until all of the following conditions are met:
- Attached to DOM
- Visible: Not
display: noneorvisibility: hidden - Stable: Animation has stopped
- Receives Events: Not hidden by other elements
- Enabled: No
disabledattribute
These checks are repeated until timeout (default 30 seconds).
Assertions
Use the expect function to verify the state of elements or the page. These automatically retry.
// Is element visible
await expect(locator).toBeVisible();
// Does it contain text
await expect(locator).toContainText('Complete');
// Is input value correct
await expect(locator).toHaveValue('user123');
// Is checkbox checked
await expect(locator).toBeChecked();
Running Tests
Running from Command Line
Run all tests:
npx playwright test
Run only specific file:
npx playwright test tests/example.spec.ts
Run with browser displayed (not headless mode):
npx playwright test --headed
UI Mode
You can run and debug tests in interactive UI mode. Time travel debugging etc. are possible.
npx playwright test --ui
Viewing Report
To view HTML report after test execution:
npx playwright show-report
VS Code Extension
If you are using VS Code, it is strongly recommended to install the official Playwright Test for VSCode extension. This allows you to run and debug tests directly from the editor.
Frontend Implementation to Prevent Flaky Tests
Test stability (elimination of Flakiness) depends heavily not only on test code but also on frontend implementation.
1. Utilizing Test Attributes (data-testid)
CSS classes and IDs may change for styling. By assigning the test-specific attribute data-testid, you can create robust locators that are not affected by design changes.
// Not recommended implementation
<button className="btn-primary-large">Submit</button>
// Recommended implementation
<button data-testid="submit-button">Submit</button>
On the test side, retrieve with page.getByTestId('submit-button').
2. Semantic HTML and ARIA Roles
Playwright recommends using getByRole. By using appropriate HTML tags (<button>, <nav>, <h1>, etc.) and ARIA attributes, you can improve accessibility and make testing easier at the same time.
3. Visualization of Loading State
Display a spinner or skeleton screen during asynchronous processing, and implement it so that they disappear after processing is complete. This allows you to write clear wait conditions like "wait until loading disappears" on the test code side.
// Guarantee data loading completion by waiting for loading to disappear
await expect(page.getByTestId('loading-spinner')).not.toBeVisible();
4. Control of Dynamic Content
Ensure that dates, random numbers, animations, etc. do not affect results during test execution. Consider using mock data or settings to disable animations as needed.
Best Practices
- Locator Priority: Use attributes visible to users like
getByRoleas much as possible, and avoid CSS selectors and XPath. - Web First Assertions: Use
await expect(...)to appropriately wait for asynchronous state changes. - Independence: Make each test independent and do not depend on the execution order or state of other tests.
- Utilize Codegen: Use
npx playwright codegento record operations and generate test code templates.