Playwright cheat sheet
The locators, the actions and the assertions of the course on one page. Each part names the lesson that teaches it.
A test file
Lesson: Your first test
import { test, expect } from '@playwright/test';
test('opens the todo app', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveTitle(
'Absolute Path Todos',
);
});
pageis the browser tab. Each test gets a new, clean page.page.goto('/')opens the first page of the app.- An assertion checks a fact. When the fact is wrong, the test fails.
Find elements by role
Lesson: Find elements by role
// By role and name: the first choice
page.getByRole('button', { name: 'Add' })
page.getByRole('heading', { level: 1 })
// Act on the element
const box = page.getByRole('textbox', {
name: 'New todo',
});
await box.fill('Buy milk');
const add = page.getByRole('button', {
name: 'Add',
});
await add.click();
| Element | Role |
|---|---|
<button> |
button |
<a href> |
link |
<input type="text"> |
textbox |
<input type="checkbox"> |
checkbox |
<h1> to <h6> |
heading |
The name is the text that a person sees or hears for the element, such as the text of a button.
Labels, text and test ids
Lesson: Labels, text, and test ids
// A form field, by the text of its label
page.getByLabel('Email')
// A field with no label, by its placeholder
page.getByPlaceholder('Search')
// By the text in the element
page.getByText('Welcome back')
// By the attribute data-testid
page.getByTestId('cart-count')
// A name can be a regular expression
page.getByRole('heading', { name: /^Welcome/ })
Use getByRole first. Use a test id for an element that has no good role, label or text.
Narrow down a locator
Lesson: Narrow down a locator
const list = page.getByRole('list', {
name: 'Todo list',
});
const rows = page.getByTestId('todo-item');
// An element inside another element
list.getByRole('button', { name: 'Delete' })
// Rows that contain a text
rows.filter({ hasText: 'Write a test' })
// Rows that do not contain a text
rows.filter({ hasNotText: 'Walk the dog' })
// Rows that contain another element
const ticked = page.getByRole('checkbox', {
checked: true,
});
rows.filter({ has: ticked })
Strict mode: an action fails when its locator matches more than one element. Make the locator narrower.
Actions
Lesson: Click, type, and press keys
| Action | What it does |
|---|---|
click() |
clicks the element |
dblclick() |
clicks two times quickly |
hover() |
moves the pointer over the element |
fill('text') |
clears a text field and sets its value |
clear() |
clears a text field |
pressSequentially('text') |
types the text one key at a time |
press('Enter') |
presses one key, or keys such as Control+A |
check() uncheck() |
ticks or unticks a checkbox |
selectOption('value') |
chooses an option of a <select> list |
focus() blur() |
moves the focus into or out of the element |
Before an action, Playwright waits until the element is visible, stable and enabled, and until no other element covers it.
Assertions
Lesson: Assertions
| Assertion | Checks that |
|---|---|
toBeVisible() toBeHidden() |
the element is shown, or is not shown |
toHaveText('...') |
the text is exactly this |
toContainText('...') |
the text has this text in it |
toHaveValue('...') |
a form field has this value |
toBeChecked() |
a checkbox is ticked |
toBeEnabled() toBeDisabled() |
a control can be used, or cannot |
toBeEmpty() |
a field or an element has no content |
toHaveAttribute('name', 'v') |
an attribute has a value |
toHaveCount(3) |
the locator matches this many elements |
toHaveURL('...') |
the address of the page is this |
toHaveTitle('...') |
the title of the page is this |
const alert = page.getByRole('alert');
await expect(alert).toHaveText('Wrong password.');
await expect(alert).toHaveText(/wrong/i);
// The texts of many elements, in order
await expect(rows).toHaveText(['Tea', 'Milk']);
// Soft: the test goes on after a failure
await expect.soft(alert).toBeVisible();
Waiting
Lesson: Waiting and flaky tests
const rows = page.getByTestId('todo-item');
// Wrong: one read, no waiting
expect(await rows.count()).toBe(3);
// Right: the assertion waits
await expect(rows).toHaveCount(3);
Never wait for a fixed time. A fixed wait is too short on a slow day and too long on a fast day.
// Until a loading message is gone
const loading = page.getByText('Loading');
await expect(loading).toBeHidden();
// Until the element is there
await page.getByText('Saved').waitFor();
// Until the address changes
await page.waitForURL('/account');
// Until the result of a function is right
await expect.poll(() => total()).toBe(3);
// Until a block of code stops failing
await expect(async () => {
await expect(rows).toHaveCount(3);
}).toPass();
Tests that catch bugs
Lesson: Tests that catch bugs
A test that passes proves little. A good test fails when the app has a bug.
- Check the result, not the action. A test that clicks and checks nothing catches nothing.
- Use exact values.
toHaveText('1 item left')finds a count that does not change.toBeVisible()does not. - Check what must stay and what must go. After a delete, check that the other items are still there.
Groups and hooks
Lesson: Groups and hooks
test.describe('sign in', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/login');
});
test('shows the heading', async ({ page }) => {
await test.step('check the title', async () => {
// ...
});
});
});
| Hook | Runs |
|---|---|
test.beforeEach |
before each test of the file or the group |
test.afterEach |
after each test, also after a failed test |
test.beforeAll |
one time, before the first test |
test.afterAll |
one time, after the last test |
test.skip(...) does not run a test. test.only(...) runs this test and no other. Do not save a test.only in a shared project.
Page objects
Lesson: Page objects
class ShopPage {
constructor(page) {
this.page = page;
this.cards = page.getByTestId('product-card');
}
async open() {
await this.page.goto('/');
}
async addToCart(name) {
const card = this.cards.filter({
hasText: name,
});
await card
.getByRole('button', { name: 'Add to cart' })
.click();
}
}
const shop = new ShopPage(page);
await shop.open();
await shop.addToCart('Blue Mug');
A page object holds the locators and the steps of one page. A step that changes the page waits for its result.
Fixtures
Lesson: Fixtures
import { test as base } from '@playwright/test';
const test = base.extend({
loginPage: async ({ page }, use) => {
const loginPage = new LoginPage(page);
await loginPage.open(); // the setup
await use(loginPage); // the test runs here
// The cleanup goes here.
},
});
test('signs in', async ({ loginPage }) => {
// ...
});
A test gets a fixture when it lists the name of the fixture in its arguments. The code after use runs also after a failed test.
Mock the network
Lesson: Mock the network
// Set the route before the page loads.
await page.route('**/api/todos', (route) =>
route.fulfill({ json: [] }),
);
await page.goto('/');
| Call | What the page sees |
|---|---|
route.fulfill({ ... }) |
an answer that you write: json, status, body |
route.abort() |
a network failure |
route.continue() |
the real answer of the server |
Use a mock for rare states, for errors such as the status 500, and for a page that must not depend on a server.
Requests and the API
Lesson: Wait for requests and call the API
// Start to wait before the click.
const responsePromise = page.waitForResponse(
(res) => res.url().includes('/api/cart'),
);
await addButton.click();
const response = await responsePromise;
expect(response.ok()).toBeTruthy();
Call the API without a page
test('lists products', async ({ request }) => {
const res = await request.get('/api/products');
expect(res.ok()).toBeTruthy();
expect(await res.json()).toHaveLength(6);
});
On your own computer
Lesson: Playwright on your own computer
npm init playwright@latest # a new project
npx playwright test # all tests
npx playwright test --headed # show the browser
npx playwright test --ui # UI mode
npx playwright test --debug # step by step
npx playwright test -g "sign in" # by title
npx playwright test --last-failed
npx playwright show-report
npx playwright show-trace trace.zip
npx playwright codegen https://example.com
In playwright.config.js:
use.baseURLis the address of your app, so thatpage.goto('/')works.retriesruns a failed test again. A test that fails and then passes is flaky.use.tracerecords a trace for a look at a failed run.projectsruns the tests in each browser.webServerstarts your app before the tests.forbidOnlyfails the run when atest.onlyis in the code.