We Scored 17 Real Playwright Test Suites: The State of Playwright Quality in 2026
TL;DR
We scored 17 real, public Playwright suites (not our own fixtures) with playwright-score, our open-source, deterministic, AI-free scorer. Across 1,214 spec files and 5,943 tests we found 5,467 rule violations. The single most common: raw CSS/XPath locators instead of role-based ones — present in 16 of 17 repos, 3,287 occurrences, 60% of every finding. 30.1% of every locator call in the corpus is a raw selector, and two repos use zero role-based locators at all. 71% of repos (12/17) have at least one test that passes without asserting anything. Full methodology, every repo, and a script to reproduce every number are public on GitHub.
Every managed QA vendor, including us, claims to write "good" Playwright tests. Almost none of that claim is measurable. So we built a scorer that doesn't take anyone's word for it — an open-source, deterministic, AI-free tool called playwright-score — and pointed it at 17 real, public Playwright suites we don't own, didn't write, and didn't curate for a good outcome.
The result: 1,214 spec files, 5,943 tests, 168,902 lines of test code, and 5,467 individual rule violations, scored on 2026-08-19 against each project's live main/master branch. Every number in this post is reproducible from a single script against the public repos linked throughout — no surveys, no self-reported data, nothing from our own customers.
The headline numbers
17
public repos scored
5,467
rule violations found
30.1%
of all locators are raw CSS/XPath
12 / 17
repos have a test with zero assertions
The corpus
We didn't pick repos to make the tool — or the industry — look good or bad. The corpus mixes heavily-engineered platforms (Supabase, Grafana, Mattermost, n8n) with smaller, less mature projects found by searching for real @playwright/test usage. Every repo is scored on the exact subdirectory that holds its Playwright suite, via a fresh, shallow, sparse clone — nothing is vendored or cached.
| Repo | Score | Grade | Files | Tests | Findings |
|---|---|---|---|---|---|
| Playwright (own TodoMVC example) | 98/100 | A | 24 | 24 | 4 |
| Supabase | 98/100 | A | 31 | 271 | 167 |
| freeCodeCamp | 97/100 | A | 89 | 372 | 187 |
| Documenso | 96/100 | A | 125 | 1109 | 579 |
| Storybook | 96/100 | A | 6 | 25 | 12 |
| dub | 96/100 | A | 17 | 147 | 27 |
| n8n | 95/100 | A | 256 | 1047 | 39 |
| novu | 93/100 | A | 2 | 2 | 3 |
| Grafana | 92/100 | A | 213 | 672 | 744 |
| PostHog | 91/100 | A | 42 | 124 | 261 |
| Immich | 90/100 | A | 13 | 43 | 39 |
| Mattermost | 90/100 | A | 284 | 1157 | 1476 |
| sencho | 90/100 | A | 26 | 177 | 263 |
| cal.com | 85/100 | B | 53 | 278 | 1215 |
| livecodes | 78/100 | C | 14 | 280 | 140 |
| openplayerjs | 77/100 | C | 8 | 77 | 36 |
| TheCyberHub | 73/100 | C | 11 | 138 | 275 |
Run on 2026-08-19 with playwright-score sqs-v1, standard profile, threshold 80. Reproduce it yourself with scripts/validate-corpus.sh in the GitHub repo.
What's actually wrong with real Playwright suites
Grades hide the interesting part. We also kept every individual finding — 5,467 of them — and ranked which specific anti-patterns show up most often across the corpus. This is the part that isn't on our own tool's landing page.
| Rule | Occurrences | Repos affected | What it means |
|---|---|---|---|
| no-raw-locators | 3,287 | 16 / 17 | Raw page.locator()/frame.locator() CSS or XPath instead of role-based locators |
| prefer-native-locators | 572 | 8 / 17 | Legacy page.click(selector) style instead of Locator API |
| no-wait-for-timeout | 450 | 11 / 17 | Hard-coded sleeps (waitForTimeout) instead of condition-based waits |
| prefer-web-first-assertions | 219 | 12 / 17 | Assertions that don't auto-retry against the live DOM |
| no-networkidle | 194 | 8 / 17 | waitForLoadState('networkidle') — deprecated, unreliable in Playwright |
| no-force-option | 144 | 9 / 17 | { force: true } bypassing actionability checks |
| no-wait-for-selector | 103 | 8 / 17 | Manual waitForSelector instead of auto-waiting locators |
| expect-expect | 102 | 12 / 17 | A test block that runs and passes without asserting anything |
| oversized-file | 71 | 10 / 17 | Spec files large enough to hurt reviewability and maintenance |
| missing-playwright-await | 70 | 3 / 17 | Un-awaited async Playwright calls — silent race conditions |
Two patterns dominate everything else. Locator anti-patterns account for 4,067 of the 5,467 total findings — 74% of every violation in the corpus — and no-raw-locators alone is 60% of all findings, present in 16 of the 17 repos. This is not a niche mistake. It's the default way most teams still write Playwright selectors, three years after Playwright's own docs started recommending role-based locators over raw CSS.
Zoomed out across every locator call in the corpus — not just findings, every single getByRole/getByTestId/getByText vs. page.locator()/frame.locator() call, 17,118 of them — 69.9% are role-based and 30.1% are raw selectors. Two repos in the corpus, livecodes and openplayerjs, use zero role-based locators: every single locator in both suites is a raw CSS or XPath selector. Both fail the default threshold.
A B-grade suite can hide more findings than an A
cal.com's suite triggers 1,215 individual findings across only 53 files — roughly 23 per file, the highest density in the corpus — and still scores an 85 (B), passing the default threshold. Mattermost, in contrast, has 284 files and 1,157 tests but only 1,476 findings total — about 5 per file — and scores a 90 (A).
The reason isn't that the scorer is lenient on cal.com. It's that playwright-score caps repeated occurrences of the same rule in the same file at 3 for penalty math (every occurrence still shows up in the full findings list — the cap only affects the score itself), so one anti-pattern repeated 40 times in one file doesn't auto-fail a suite the way one anti-pattern spread across 40 different files does. cal.com's actual drag on its score is its locator ratio: 613 role-based locators against 875 raw ones — 41.2% native, well under the corpus average of 69.9%. High finding count and a passing grade aren't a contradiction; they're measuring different things.
Tests that assert nothing
playwright/expect-expect — a test block that runs, exits without an error, and never calls expect() — appeared 102 times across 12 of the 17 repos (71%). These aren't failing tests. They're passing tests that prove nothing, the exact failure mode we've written about before: a green suite that doesn't mean the product works. A scorer that only checked pass/fail would never catch a single one of these 102 cases — they all pass.
Methodology
Every score in this post comes from @qaguardian/playwright-score, scoring version sqs-v1, standard profile, threshold 80 — the same package anyone can npm install -D today. The scorer is fully deterministic and AI-free: it wraps eslint-plugin-playwright for community best practices, then adds a versioned 0–100 score, a locator-ratio metric, and assertion-delegation tracing through local imports. No LLM is in the scoring path.
Each repo was scored via a fresh, shallow, sparse git clone of the exact subdirectory holding its Playwright suite — nothing from any of these repos is vendored into ours. The rule-frequency breakdown in this post comes from the raw JSON findings output of that same run, aggregated by rule across all 17 reports. Full methodology, weights, and the frozen scoring formula are documented in METHODOLOGY.md. Scores will drift slightly over time as these projects' own suites change — that's expected, and the whole point of scoring live code instead of a frozen snapshot.
What to do with this if you maintain a Playwright suite
Run npx -p @qaguardian/playwright-score playwright-score ./tests --format text against your own suite before you assume it's fine. Given that 74% of every finding in this corpus was a locator anti-pattern, that's the highest-value place to look first — a raw page.locator('.btn-primary') breaks the moment a class name changes; getByRole('button', { name: 'Submit' }) survives a restyle. If your suite is CI-gated, the tool exits non-zero below your threshold, so this is a five-minute add to an existing pipeline, not a rewrite.
If you'd rather have engineers own that quality bar for you — the same bar we hold AI-generated Playwright to internally — book a demo and we'll score your actual suite live, on the call.
Tags
See QA Guardian in action
Everything we write about is what we build and run every day. Book a demo and we'll show you on your own codebase.