Back to Dify

Playwright Best Practices

.agents/skills/e2e-cucumber-playwright/references/playwright-best-practices.md

1.16.13.5 KB
Original Source

Playwright Best Practices

Use this reference when writing or reviewing locator, assertion, isolation, or synchronization logic.

Official sources:

What Matters Most

1. Keep scenarios isolated

Playwright's model is built around clean browser contexts so one test does not leak into another.

Apply it like this:

  • do not depend on another scenario having run first
  • keep scenario state in the runner's scenario-owned context rather than module globals
  • model special authentication or session setup through explicit per-scenario fixtures rather than shared mutable state

2. Prefer user-facing locators

Playwright recommends built-in locators that reflect what users perceive on the page.

Preferred order:

  1. getByRole
  2. getByLabel
  3. getByPlaceholder
  4. getByText
  5. getByTestId when an explicit test contract is the most stable option

Avoid raw CSS/XPath selectors unless no stable user-facing contract exists and adding one is not practical.

Also remember:

  • repeated content usually needs scoping to a stable container
  • exact text matching is often too brittle when role/name or label already exists
  • getByTestId is acceptable when semantics are weak but the contract is intentional
  • when a real UI region, card, status, or icon lacks an accessible name, prefer adding that semantic contract in product code before falling back to getByTestId

3. Use web-first assertions

Playwright assertions auto-wait and retry. Prefer them over manual state inspection.

Prefer:

  • await expect(page).toHaveURL(...)
  • await expect(locator).toBeVisible()
  • await expect(locator).toBeHidden()
  • await expect(locator).toBeEnabled()
  • await expect(locator).toHaveText(...)

Avoid:

  • expect(await locator.isVisible()).toBe(true)
  • custom polling loops for DOM state
  • waitForTimeout as synchronization

If a condition genuinely needs custom retry logic, use Playwright's polling/assertion tools deliberately and keep that choice local and explicit.

Use expect.poll for non-DOM truth such as API state, backend eventual consistency, generated resources, or captured browser events. For DOM state, use locator assertions so Playwright can apply actionability and web-first retry semantics.

4. Let actions wait for actionability

Locator actions already wait for the element to be actionable. Do not preface every click/fill with extra timing logic unless the action needs a specific visible/ready assertion for clarity.

Good pattern:

  • assert a meaningful visible state when that is part of the behavior
  • then click/fill/select via locator APIs

Bad pattern:

  • stack arbitrary waits before every action
  • wait on unstable implementation details instead of the visible state the user cares about

5. Match debugging to the active harness

Playwright supports traces, screenshots, page snapshots, and browser logs. Configure artifact capture at the runner boundary instead of adding parallel diagnostics to individual scenarios.

Review Questions

  • Would this locator survive DOM refactors that do not change user-visible behavior?
  • Is this assertion using Playwright's retrying semantics?
  • Is any explicit wait masking a real readiness problem?
  • Does this code preserve per-scenario isolation?
  • Is a new abstraction really needed, or does it bypass the runner's scenario-owned context and lifecycle?