Playwright Intro - discussion (2022-10-15)
We’re trying to get serious about “route-flow” tests: not just clicking around, but asserting that posture/contract markers make the behavior explainable.
How do you scope Playwright tests in a React app so they’re high-signal and not a flaky mess? Do you render explicit markers (lane/posture/version) to assert on, or do you rely purely on user-visible text? What’s your strategy for debugging failures (screenshots, traces, DOM markers)?
Comments (10)
Back to latestThe best e2e tests assert on one thing: that the user can complete a flow. Everything else is a trap.
Markers are worth it if they’re honest and few.
We assert on a small set of route posture markers and treat them as part of the contract surface.
Related: Testing and Debugging and Performance and Rendering Best Practices.
A route-flow test that stays stable is usually “assert a marker, then assert one outcome”:
ts
import { test, expect } from '@playwright/test';
test('checkout route court posture', async ({ page }) => {
await page.goto('/checkout');
await expect(page.locator('[data-court]')).toHaveAttribute('data-court', 'dual');
await page.getByLabel('Email').fill('a@b.com');
await expect(page.getByText(/valid/i)).toBeVisible();
});Counterpoint: markers can make teams stop caring about real user semantics.
If you only assert on data-*, you can ship something unusable that still “passes” because the marker says it’s fine.
100% agree. We use markers to *explain* behavior, not replace it.
The test still asserts a user-visible outcome; the marker is what makes failures debuggable.
That’s a good framing. Marker as narration, not as success criteria.
Big flake killer for us: never “wait for time”, always “wait for state”. If the UI has a visible status marker, the test can wait on that instead of guessing.
ts
await page.goto('/search');
await expect(page.locator('[data-status]')).toHaveAttribute('data-status', 'ready');
await expect(page.getByRole('heading', { name: /results/i })).toBeVisible();Traces are nice, but screenshots are still the fastest triage artifact if the UI is self-describing.
If the screenshot can’t prove posture/state, you end up reading logs anyway.
We reduced flake by cutting hidden sync effects.
A lot of e2e instability was “DOM changed after render because an effect settled state”.
One more: be intentional about selectors. If you assert on fragile CSS structure, your tests become a styling tax.
Either use roles/labels or a few explicit data-* markers with meaning.
If a test fails and the screenshot doesn’t tell you what happened, your UI isn’t a contract surface yet.
We treat Playwright as “route truth enforcement”.
If a contract marker changes, a route-flow test should fail loudly so the team updates the story intentionally.