None of this is theory. It's scar tissue. Most of these are the difference between a suite that works on your laptop and one that survives CI, real traffic, and a login wall. Here they are, each with the fix the thread landed on.
01 Hard-coded waits everywhere
The most-cited mistake by a mile: waitForTimeout(3000) sprinkled through the suite to "make it stable." It passes locally because your machine is fast, then breaks in CI because the container is slower. You've traded a real fix for a timer that's wrong on both ends — too slow when things are fine, too fast when they aren't.
expect(locator).toBeVisible() and let the framework wait for actionability.
02 Brittle selectors
Targeting something like div.css-1a2b3c > span:nth-child(3) is asking for pain. That selector encodes your DOM's exact shape and a hashed class name — both change the moment a developer touches the component. Your test breaks, and it isn't even a real bug.
getByRole, getByText, and getByTestId. They describe intent, not structure, so they survive UI refactors.03 Over-abstracting the Locator API
This one drew a crowd. Playwright's Locator API is already rich, but people wrap it in layers of helpers anyway — defining a locator, then an accessor method, then a method that calls the accessor. One commenter described building a beautiful page-object hierarchy in year one and spending year two deleting it. The worst offenders come from Selenium and bring fifteen years of habits to a framework that already does the work.
FIX → Default to Playwright's built-ins. Delete a wrapper before you add one — earn every layer of abstraction.04 Trusting storageState naively
A sharp production story: relying on storageState while the app rotates tokens or triggers step-up auth mid-session. Tests pass in staging and fail live, because the real auth flow only kicks in against production. Anything behind a real login is exposed to this.
05 Running the full suite serially in CI
Someone ran their suite serially for nine months to "save resources." The result: 90-minute pipelines, and a 1-in-50 flake dragging average merge time to roughly 2.5 hours. Moving to parallel workers with proper isolation cut it to twelve minutes — and made flakes diagnosable on their own instead of as a cascade.
FIX → Run parallel workers with real test isolation from day one. Serial-to-save-money almost always costs more.06 Building test data through the UI
Clicking through signup to create a user for every test is slow, flaky, and couples your setup to the very UI you're testing. When it breaks, the whole run cascades and you can't tell setup failures from real ones.
FIX → Pre-create users and state via API, not the UI. Every test gets its own clean data, and failures stay isolated.07 Screenshot diffs full of noise
A first visual baseline that picked up a relative timestamp ("2 hours ago") and a chart that animated over 800ms. Every diff came back 100% noise, and the team learned to ignore visual failures — which defeats the point of having them.
FIX → Make the page deterministic first. Freeze the clock inbeforeEach, set animation-duration: 0, and mask anything backed by a third-party widget.
08 Treating test code as second-class
The most underrated point in the thread: suites die from neglect more often than from any selector strategy. Tests that never get reviewed or refactored rot fast. A test goes red on a Friday, someone adds test.skip() with a comment nobody revisits, and after enough of those the suite is a vibes-only signal.
09 Shipping codegen output as-is
Record-and-playback is genuinely useful, but the generated code is a starting point, not a suite. Ship it raw and you inherit brittle selectors and hard waits by default — the exact things at the top of this list.
FIX → Treat codegen as a scaffold. Rewrite the selectors and structure before you commit it.10 Ignoring production realities
Real traffic brings slower API responses, rate limits, and captcha or MFA challenges that never existed in lower environments. A suite tuned only against a quiet staging box falls over the first time it meets production conditions.
FIX → Simulate production-like load before release, monitor slow endpoints, and plan for the auth challenges you'll actually hit.11 Wasting type safety in TypeScript
If you're on TypeScript but writing it like untyped JavaScript — any everywhere, no typed page objects, no typed fixtures — you've kept the compile step and thrown away the reason for it.
12 Over-testing
Chasing a coverage number with low-value tests that don't map to real user flows. Each one is a maintenance liability forever, and together they slow the suite and bury the failures that matter.
FIX → Cover the flows that matter and skip the vanity ones. Fewer, meaningful tests beat a wall of green noise.Blindly relying on AI-generated code
AI writes Playwright fast, and that's exactly the trap. The output looks plausible, so it lands without a second read — and quietly ships the brittle selectors and hard waits from the top of this list. When an agent generates your suite and nobody reviews it, you don't have tests, you have a pile of guesses that happened to pass once. The thread's take was blunt: the discipline that keeps production code clean matters more than whatever tool wrote it.
FIX → Treat AI output like a junior dev's pull request. Read every line, prune it, and make sure you understand it before it lands.Not learning Playwright properly in the first place
You can absolutely piece this together from Reddit threads, docs, and trial-and-error in CI. It works — slowly, and usually after you've made most of the thirteen mistakes above yourself. The faster path is a structured one that covers the whole picture: end-to-end, API testing, and the AI and MCP side of modern automation, in the order that actually builds on itself.
FIX → Learn it in one structured path — my Playwright TypeScript Automation Testing: E2E, API, AI, MCP course.The one that split the room
Page objects: discipline or dead weight?
The thread agreed on almost everything above. On one topic it went to war: the Page Object Model. Half the room called skipping POM the cardinal sin. The other half argued that fixating on OOP layers rots suites just as fast — factory functions are simpler, and heavy class hierarchies fight you when the app changes constantly.
The most interesting version of the argument tied it to AI: when an agent is generating your test code and your pages change weekly, deep abstraction becomes a liability, not an asset. There's no settled answer here — which is exactly why it's worth thinking through for your own suite instead of copying a template. Where do you land?
If there's a thread running through all twelve, it's this: the framework isn't your problem. Determinism, isolation, and treating tests like real code are. Get those right and most of this list never happens to you.
I go deep on the right way to do this — auto-waiting, resilient locators, API-driven setup, and CI — in my Playwright with TypeScript course, including the AI and MCP side of modern test automation. If this list was useful, the course is the structured version of it. Check it out here.




