Playwright Best Practices

This chapter summarizes the most important best practices in writing and maintaining Playwright tests, helping you write stable and maintainable tests.


Testing Philosophy

Test User-Visible Behavior

Automated tests should verify what end users can see and interact with, rather than verifying code implementation details.

For example, tests should verify that the correct text is displayed on the page, buttons are clickable, and submitting the form navigates to the correct page, rather than verifying the return value of a JavaScript function or the internal structure of the DOM.

Example

// Not recommended: testing implementation details
expect(await page.evaluate(() => window.__store.getState().user.name))
  .toBe('example');

// Recommended: testing what users see
await expect(page.getByText('Welcome, example')).toBeVisible();

Test Isolation

Each test should becompletely independentfrom other tests, and not depend on the state of a previous test.

Playwright provides an independent context by default, but you should also ensure that you don't rely on the execution order of tests.

Example

// Not recommended: tests depend on each other
let createdId;
test('Create resource', async ({ request }) => {
  const resp = await request.post('/api/items');
  createdId = (await resp.json()).id;  // Shared state
});
test('Use created resource', async ({ page }) => {
  await page.goto(`/items/${createdId}`);  // Depends on the result of the previous test
});

// Recommended: each test is self-sufficient
test('Create and use resource', async ({ request, page }) => {
  const resp = await request.post('/api/items');
  const id = (await resp.json()).id;
  await page.goto(`/items/${id}`);
  await expect(page.getByText('Resource details')).toBeVisible();
});

Locator Priority Principles

Using Locators in the recommended order can improve test stability.

PriorityMethodApplicable scenario
1getByRole()Element has a clear ARIA role
2getByLabel()Form element associated with a label
3getByPlaceholder()Input has a placeholder
4getByText()Element has explicit text
5getByAltText()Image has alt attribute
6getByTitle()Element has title attribute
7getByTestId()Fallback
8locator()CSS/XPath, last resort

Try to avoid using CSS class names as locators.

Class names are prone to change due to style refactoring, causing large-scale test failures.


Avoid Unnecessary Waits

Leverage Playwright's auto-wait mechanism, avoid hand-codingsleeporwaitForTimeout。

Example

// Not recommended: hardcoded waits
await page.waitForTimeout(3000);
await page.getByText('Data loading complete').click();

// Recommended: rely on auto-waiting and assertions
await expect(page.getByText('Loading data...')).toBeHidden({ timeout: 10000 });
await page.getByText('Data loading complete').click();

Don't Test Third-Party Services

Only test what you can control; don't depend on the availability of third-party services.

Example

// Not recommended: depending on external CDN or API
await page.goto('https://www.example.com/');
// The page may load Google Analytics, external fonts, etc.

// Recommended: intercept third-party requests, mock external services
await page.route('**/*analytics*', route => route.abort());
await page.route('**/external-api/**', route => {
  route.fulfill({ json: { status: 'ok' } });
});

Use expect.soft() Soft Assertions

When checking multiple independent conditions, soft assertions can report all failures at once instead of stopping at the first failure.

Example

// Recommended: soft assertions check all fields at once
await expect.soft(page.getByLabel('Username')).toBeVisible();
await expect.soft(page.getByLabel('Password')).toBeVisible();
await expect.soft(page.getByLabel('Email')).toBeVisible();
await expect.soft(page.getByRole('button', { name: 'Register' })).toBeEnabled();
// If multiple fields are missing, report all at once

Organize Test Files Reasonably

PracticeDescription
Split files by functional modulelogin.spec.ts、checkout.spec.tswait
Use describe to groupPut related tests intest.describe()Medium
Use beforeEach wellExtract repeated navigation and setup steps intobeforeEach
Descriptive test namesGood names explain "what was done" and "what is expected"
Extract common codeHelpers used in more than 3 files should be extracted into a shared module
Don't over-DRY2-3 lines of repetition are easier to maintain than over-abstraction

Common Troubleshooting

Flaky Tests

Causes and solutions for flaky tests:

CauseSolution
Hardcoded sleep is not long enoughUse auto-wait mechanism instead of fixed waits
Depending on third-party servicesUseroute()Mock external requests
Animations cause element position changesIn the configurationanimations: 'disabled'
Data residue between testsEnsure each test independently creates and cleans up data
Time-related logicUsepage.clockFixed time

Locator Cannot Find Element

Troubleshooting steps:

  • Confirm whether the element is in the DOM (usetoBeAttached()rather thantoBeVisible())
  • Confirm whether the element is in the viewport
  • Confirm whether it is nested in an iframe
  • Use Pick Locator in UI Mode to check the locator
  • Check whether there are multiple matching elements (Locator requires strict mode by default)
Other extensions