7 Playwright Adoption Failures Every QA Engineer Should Know | Real-World Pitfalls and Fixes

7 real-world failure stories from a QA engineer who introduced Playwright for E2E test automation. Covers over-reliance on auto-waiting, wrong language choice, CI environment issues, team resistance, and more — Playwright-specific pitfalls with concrete fixes based on hands-on experience.

“Switching to Playwright will fix everything” — that assumption was the beginning of the next set of failures. Changing the tool doesn’t help if you use it the wrong way.

📌 Who This Article Is For

  • QA engineers considering Playwright for E2E test automation
  • Engineers planning a migration from Selenium to Playwright
  • Anyone who has introduced Playwright and felt “this isn’t what I expected”
  • Leads and managers trying to roll out Playwright across a team

✅ What You Will Learn

  • 7 Playwright-specific failure patterns and their root causes
  • Decision-level and adoption-level mistakes — not just coding pitfalls
  • Concrete avoidance strategies and fixes for each failure

👤 About the Author

Written by Yoshi, a QA engineer and test automation engineer with over 15 years of hands-on experience using Selenium, Playwright, and pytest in real production projects. These are the “I wish I’d known this” moments from actual Playwright adoption — shared without filter.

📖 How This Article Differs from Related Content

  • Top 5 Test Automation Failures: Tool-agnostic strategy and design mistakes → Start here if you failed at “what to automate”
  • Playwright × pytest Best Practices: Code-level implementation pitfalls → Start here if you’re stuck on how to write tests
  • This article: Experience-level failures in choosing, introducing, and running Playwright as a team

📌 Key Takeaways

  • Most Playwright adoption failures are not tool problems — they are adoption planning, language selection, and team design problems
  • auto-waiting, page.route(), and CI environments have Playwright-specific traps that are easy to avoid if you know them in advance
  • Playwright itself is an excellent tool. Knowing these failures before you start lets you avoid repeating them

“We’re drowning in flaky Selenium tests — let’s switch to Playwright.” My decision two years ago was half right and half wrong.

Playwright really is an excellent tool. But charging ahead with the assumption that “switching will fix everything” led to a whole new set of Playwright-specific failures. This article shares those experiences — all seven of them — without holding back.

7 Playwright Adoption Failures: Quick Reference

#FailureRoot Cause
“Switching to Playwright will fix everything” — the over-confidence trapFundamental misunderstanding of what the tool solves
Over-relying on auto-waiting and drowning in timeoutsMisunderstanding of how Playwright’s auto-waiting actually works
Starting with TypeScript and ending with a codebase no one could maintainLanguage choice that ignored team skill level
Thinking page.route() mocked the API — but it was hitting the real oneMisunderstanding of how network mocking works
Leaving browser launch failures in CI unresolvedLocal-only development habits
A version upgrade that crashed 30 tests overnightNo dependency management or update strategy
Not being able to explain “why Playwright” to the teamRationale that existed only in one person’s head

① What Is the “Switching Will Fix Everything” Over-Confidence Trap?

What happened

When flaky Selenium tests hit a breaking point, I believed the hype: “Playwright has auto-waiting so flakiness goes away” and “the modern API makes maintenance easier.” I launched a project to rewrite all 200 existing Selenium tests in Playwright. The result: flakiness decreased, but never reached zero. And the three months spent rewriting never touched the underlying test design problems.

⚠️ The real problem: Most of those flaky Selenium tests had test design problems. Strong external dependencies, unstable specs, fragile selectors — none of these are fixed by switching tools. The right call was to fix the test design first, then migrate.
💡 Fix: Before migrating to Playwright, analyze “why is flakiness happening?” If the cause is Selenium’s synchronization limitations, Playwright helps. If the cause is test design problems, fix the design first — then migrate. The long-term cost is much lower that way.

Flaky issues Playwright fixes vs. ones it doesn’t

Root Cause of FlakinessImproved by Playwright?Reason
Insufficient implicit waits in Selenium✅ Yesauto-waiting handles this automatically
ChromeDriver version mismatch✅ YesBrowser binary managed alongside the library (though install + CI cache setup is still required)
Animation timing during interactions△ PartiallyStable check exists but isn’t complete for all CSS animations
Unstable external API responses❌ NoExternal dependencies can’t be fixed by the tool
DB or state conflicts between tests❌ NoTest design problems can’t be fixed by the tool
Unstable specs at early development stage❌ NoSpec changes break the tests themselves
CI machine CPU/memory constraints△ PartiallyCan mitigate with timeout tuning, but not a root fix
Network latency / jitter❌ NoMock the network instead
Race conditions during parallel test execution❌ NoFix test isolation in the design — not in the tool

② What Is Over-Relying on Auto-Waiting and Drowning in Timeouts?

What happened

“Playwright has auto-waiting, so you don’t need explicit waits.” Tests written with that assumption started producing mysterious timeout errors a few weeks later.

⚠️ Understand this first: Playwright’s Locator API automatically runs an actionability check (visible, stable, enabled, receives events, etc.) before actions like click. However, this is a check of the current state — it does not wait for an async state change to occur. For things like “wait until the API response arrives” or “wait until a button transitions from disabled to enabled,” explicit waits using expect() are still required.
# ❌ Over-relying on auto-waiting
# locator.click() runs an actionability check, but for async state changes
# or custom UI components, explicit waits may still be needed
page.locator("#submit-btn").click()
# ↑ If the button transitions from disabled → enabled asynchronously,
#   this may time out depending on timing

# ✅ Modern Locator API + expect() to make intent explicit
from playwright.sync_api import expect

submit_btn = page.locator("#submit-btn")
expect(submit_btn).to_be_enabled()   # wait until enabled
submit_btn.click()                    # then click
⚠️ Cases where actionability check doesn’t fully help:

  • Buttons that transition disabled → enabled asynchronously: May attempt the click while still disabled, causing a timeout. Use expect(locator).to_be_enabled() first
  • Content that appears after an API response: Playwright waits while the element doesn’t exist in the DOM, but a slow API can still hit the overall timeout
  • Elements mid-animation: The stable check exists, but certain CSS animation implementations may not behave as expected
  • Shadow DOM elements: Depending on how selectors are written, they may not be recognized as attached
💡 Fix: auto-waiting is powerful, but it does not resolve every async state change. Combining expect(locator).to_be_enabled() or expect(locator).to_be_visible() makes the intent explicit and reduces flakiness. Note: page.wait_for_selector() is not fully deprecated — it still has legitimate uses for waiting on hidden or detached states — but for new code, Locator API + expect() is the current recommended style.

③ What Is Starting with TypeScript and Ending with a Codebase No One Could Maintain?

What happened

“Playwright’s TypeScript/Node.js version has the most samples and documentation” and “type safety makes maintenance easier” — so I chose TypeScript. I could write it, having JavaScript experience. But the rest of the team was almost entirely Python engineers. Six months later, I was the only person reviewing test code. Defacto knowledge silos: complete.

Decision FactorTypeScriptPython
Official samples and documentation depth✅ Most abundant✅ Well supported
Type safety and autocomplete✅ Powerful△ Type hints available
Maintainability on a Python-heavy team❌ High silo risk✅ Everyone can contribute
pytest integration❌ Separate ecosystem✅ Native pytest support
💡 Fix: The cardinal rule: choose the language most of the team can read and write. “Most documentation” is not more important than “team can maintain it.” If your team runs on Python, Playwright + pytest (Python) gives you everything you need. If you want type safety later, add Python type hints incrementally — that’s far more realistic than asking a Python team to maintain TypeScript.

④ What Is Thinking page.route() Mocked the API — but It Was Hitting the Real One?

What happened

I used page.route() to mock the API, but test results kept depending on real production data. After investigation, it turned out the API was being called server-side during SSR — not through browser JavaScript.

⚠️ Understand this first: page.route() intercepts HTTP traffic within the Browser Context only. Playwright’s API testing request fixture operates as a completely separate APIRequestContext — an independent HTTP client at a different network layer. The assumption “it’s all Playwright, so page.route() covers everything” is what causes this failure.
# page.route() intercepts Browser Context HTTP traffic only
await page.route(
    "**/api/users",
    lambda route: route.fulfill(status=200, body='{"users": []}')
)

# ⚠️ Does NOT intercept:
# - SSR / SSG (Next.js getServerSideProps, etc.) — server-side fetch
# - Playwright API Testing request fixture (APIRequestContext)
# - Some Service Worker-proxied fetch calls
⚠️ Main cases where page.route() cannot intercept:

  • SSR/SSG: Server-side fetch calls (e.g., Next.js getServerSideProps, Nuxt server routes). These run in Node.js, not the browser. Upstream API mocking or server-side mock libraries are needed instead
  • Service Worker: In PWA or MSW (Mock Service Worker) environments, if fetch is proxied through the SW, page.route() may not reach it
  • WebSocket: Use page.on("websocket") for WebSocket traffic
  • Playwright request fixture (APIRequestContext): Runs as an independent HTTP client — completely separate from the browser layer
💡 Fix: Always verify that your mock is actually being hit. Check whether route.fulfill() responses are coming back using the network tab or by asserting the mock response content. For SSR or Service Worker environments, combine with server-side or SW-level mock libraries (msw, Nock, WireMock, etc.). Never assume “page.route() covers everything” as a design premise.

⑤ What Is Leaving Browser Launch Failures in CI Unresolved?

What happened

Tests ran perfectly locally, but pushing to GitHub Actions produced a cascade of “browser won’t launch” and “missing dependency” errors. I knew the cause but kept deferring it — and spent two weeks running tests locally and manually reporting results to CI in the most backwards way possible.

Common Playwright launch errors in CI

Error TypeCauseFix
BrowserType.launch: Executable doesn't existBrowser binary not installedRun npx playwright install --with-deps
error while loading shared librariesMissing Linux system librariesUse the --with-deps flag
More timeouts than locallyCI machine has lower CPU/memory than localSet timeout 2–3x higher in CI config
Different behavior in headless modeViewport differences, no GPU rendering, hover not triggered, animation timing gaps, Intersection Observer behavior differencesDebug with --headed flag and revisit test design
💡 Fix: On day one of adopting Playwright, get it running in CI — don’t defer it. “Works locally, CI later” is the biggest time sink of all. The Playwright official docs include a minimal GitHub Actions YAML — use it as-is as a starting point. It’s the fastest path.

⑥ What Is a Version Upgrade That Crashed 30 Tests Overnight?

What happened

After running pip install playwright --upgrade, the next morning’s CI run had 30 tests failing. The cause: API changes between versions and a version mismatch between the library and browser binary.

# ❌ No version pinning (breaks with every upgrade)
playwright

# ✅ Pin the version in requirements.txt
playwright==1.44.0

# After updating the library, ALWAYS reinstall the browser binary
# playwright install chromium
⚠️ Playwright’s version management quirk: Playwright has a 1-to-1 relationship between library version and browser binary version. Upgrading the library without updating the browser binary creates a mismatch — “new API, old browser” — and breaks tests in unpredictable ways.
💡 Fix: Follow this upgrade process: create a dedicated upgrade branch → run the full test suite locally → verify in CI → merge to main only if everything passes. Pin versions in requirements.txt (non-negotiable). Tools like Renovate or Dependabot can automate upgrade PRs, making version management much easier to track.

⑦ What Is Not Being Able to Explain “Why Playwright” to the Team?

What happened

When I decided unilaterally that “Playwright is better” and tried to roll it out, a senior engineer asked: “Why can’t we just keep using Selenium?” I had no good answer. “It feels more modern” and “I’ve seen good things on Twitter” don’t hold up as technical decision-making. The result: “let’s just keep Selenium for now” — and the migration project floated in limbo for six months.

The evidence that can actually convince a team

Comparison PointSeleniumPlaywright
auto-waitingManual time.sleep / wait calls requiredActionability check runs automatically on interactions
ChromeDriver managementMust be manually matched to Chrome versionBrowser binary managed with the library
API test integrationRequires a separate library (requests, etc.)E2E + API in one suite via request fixture
Network mockingRequires a separate mock serverpage.route() for browser-layer mocking
HTML Report / TraceRequires separate Allure setupBuilt-in HTML Report and Trace Viewer
💡 Fix: When proposing a tool migration, prepare a three-part case: “current pain points → how Playwright solves them → migration cost estimate.” In particular, “the pain of managing ChromeDriver” and “HTML Report makes flaky test debugging much easier” resonate with teammates who feel those problems daily. Note: Selenium is not bad — the problems are in test design and operations. Playwright is a choice based on operational fit, not a statement that Selenium is wrong.

FAQ

Q. Is it worth migrating from Selenium to Playwright?

Yes — but don’t fall for the “migration alone will fix everything” trap. Eliminating ChromeDriver management, auto-waiting, and built-in HTML reports do meaningfully reduce operational overhead. The key is to clearly identify “what specifically is painful about our current Selenium setup” before deciding. That framing maximizes the value you’ll get from migrating.

Q. Where is the right place to start with Playwright adoption?

Write 1–2 new tests in Playwright and get them all the way to running in CI — that’s the best first step. Starting with a full migration of existing tests bogs down in time and learning cost. Build a small success story first, then expand from there. That approach has the lowest failure rate.

Q. Should I choose Python or TypeScript for Playwright?

Choose whichever language most of your team can read and write. If your team runs on Python, the Python version (playwright-pytest) is easier to maintain and integrates natively with your existing pytest stack. TypeScript has the richest documentation and samples, but test code that nobody can maintain is the worst outcome. Team readability beats documentation richness every time.

Q. Why do flaky tests still happen when auto-waiting exists?

auto-waiting runs an actionability check at the moment of action — it does not track async state changes over time. For things like “wait until the API response arrives” or “wait until this button becomes enabled,” explicit waits using expect() are still necessary. When flakiness persists, check whether any async state changes are slipping past what auto-waiting covers.

Q. How should Playwright versions be managed?

Pin the version in requirements.txt and only merge upgrades after confirming all tests pass in CI on a dedicated upgrade branch. Since Playwright’s library version and browser binary version are tightly coupled, always run playwright install after upgrading the library. Renovate or Dependabot can automate upgrade PRs and make this process much more manageable.

Playwright really is an excellent tool. Every failure in this article was not a problem with Playwright — it was a problem with how it was used, how it was introduced, and how the team was set up around it. Knowing these failure patterns before you start means you don’t have to repeat them. Start small, get it running in CI, build to a state where the whole team can use it — that’s the sequence that makes Playwright adoption succeed.

📋 Summary

  • “Migrating to Playwright will fix everything” is half true. Test design problems don’t go away by switching tools
  • auto-waiting runs an actionability check — it does not track async state changes. Use expect() for those
  • Language choice should prioritize what most of the team can maintain. Documentation richness comes second
  • page.route() mocks Browser Context traffic only. SSR, Service Workers, and request fixture have separate layers
  • Get CI working on day one. “I’ll deal with it later” is the biggest time sink of any Playwright project
  • Version upgrades require both library and browser binary to be updated together — always pin versions
  • Team adoption requires a “pain points → solutions → cost estimate” three-part case — not just “it’s better”
Copied title and URL