Frontend & Web

Playwright LiveView Navigation Timeouts: The Fix

Your Playwright tests just died overnight. Every single one. Welcome to the peculiar hell of testing LiveView apps.

A stylized representation of code breaking and being fixed, with Playwright and LiveView logos.

Key Takeaways

  • Playwright's default `waitUntil: 'load'` fails with LiveView due to its WebSocket-based navigation.
  • Use `waitUntil: 'commit'` in Playwright `page.goto()` and `page.waitForURL` for LiveView apps to proceed after server commitment.
  • Always follow navigations with a `waitForLiveView` helper to ensure the WebSocket connection is established before interacting with the page.

And just like that, the entire overnight test suite shatters. Every. Single. Test. Navigating. Timed out. Overnight. Picture this: a beautiful, seemingly bulletproof Playwright suite, humming along like a well-oiled machine. Then, the lights go out. Every test, across the board, hits a wall. The culprit? Navigation. Specifically, it’s Playwright’s fundamental misunderstanding of how Phoenix LiveView handles page transitions.

Look, Playwright, bless its little heart, assumes a quaint, old-fashioned web. It waits for the load event. You know, the traditional request/response tango where the browser gets a full HTML document. LiveView, however, is having none of that. It’s a WebSocket party. The URL changes, the page refreshes itself — all without the expected new document. So Playwright sits there, staring at a blank wall, waiting for a ghost event that will never arrive. Timeout. Boom. Test failed.

The waitUntil Escape Hatch

The waitUntil option in Playwright is supposed to be the magic wand. It tells the browser what to wait for. Playwright’s default is load. LiveView, as we’ve established, doesn’t do load in the traditional sense. The solution? Switch it to 'commit'. This tells Playwright to bail as soon as the server acknowledges the navigation. No more waiting for a phantom full page load that’s simply not happening. It’s the digital equivalent of saying, “Okay, I saw your message, I’m moving on, you handle the rest.”

This isn’t a subtle tweak. It’s a full-blown surgical strike across the entire test suite. Twelve commits. Every single page.goto() and page.waitForURL call needs this little { waitUntil: 'commit' } appended. One slip-up, one forgotten navigation, and your entire automated regimen grinds to a halt again. Consistency is key, and LiveView demands it.

But here’s the kicker: 'commit' gets you past the initial hang, sure. But LiveView still needs to do its thing. It needs to establish that WebSocket connection, mount the view, and get everything set up. If your test immediately tries to interact with elements that aren’t ready yet, you’re just trading one flaky test for another. You’ve escaped the timeout, only to land in a new minefield of unpredictable behavior.

The waitForLiveView Dance

This is where the waitForLiveView helper comes in. It’s the missing piece of the puzzle. You navigate, you commit, and then you wait for LiveView to actually be ready. It’s about synchronizing your test with the framework’s lifecycle. Think of it as a polite tap on the shoulder: “Hey, are you ready yet? No? Okay, I’ll wait.”

// goto
await page.goto('/', { waitUntil: 'commit' });
await waitForLiveView(page);

// or after a click-triggered navigation
await page.waitForURL(//library//, { waitUntil: 'commit' });
await waitForLiveView(page);

There’s a slight nuance, though. Not all transitions are LiveView-driven. A plain old href link? That’s business as usual for Playwright. waitForURL is enough there. waitForLiveView isn’t needed. Knowing the difference saves you unnecessary steps and keeps your tests lean. The original author even flagged this in the code: // href link — waitForURL governs this transition, not waitForLiveView. Pay attention. It matters.

Embracing the Chaos (with Retries)

Now, even waitForLiveView can get a bit shaky, especially during deployments. The original helper includes a retry mechanism. If the socket connection flakes out, it’ll reload the page and try again. Smart. But be judicious. Don’t use retryWithReload: true when a modal is open or a form is half-filled. You don’t want a reload to wipe out user progress.

Playwright’s defaults are reasonable for the apps they were designed around. When you add a new framework to your suite, those defaults are the first thing worth checking.

This isn’t Playwright’s fault, really. It’s a tool built for a certain paradigm. LiveView breaks that paradigm. The configuration has to adapt. It’s on you, the developer, to understand your app’s architecture and tell your tools what’s what. The tool doesn’t magically know your secrets.

Is this a new problem? Not really. It’s a classic case of mismatched expectations between a general-purpose tool and a specialized architecture. The solution is always the same: understand the system, then configure the tool to match. It’s the pragmatic approach.

What’s Next?

This is a simple fix, but it reveals a deeper truth: automation tools aren’t magic oracles. They’re just that — tools. And like any tool, they need to be wielded with understanding. LiveView isn’t a trend; it’s a fundamentally different way of building interactive web applications. Playwright, and other testing frameworks, will need to evolve or be adapted. For now, the 'commit' flag is your best friend.

Why Does This Matter for Developers?

It matters because brittle tests are worse than no tests. They erode confidence and waste precious development time. Understanding the nuances of your framework and how your testing tools interact with it is paramount. This isn’t just about Playwright; it’s about the craft of software development itself. Get it right, and your tests become reliable allies. Get it wrong, and they’re just noise.

Is This a LiveView Bug?

No, this isn’t a bug in LiveView. It’s a mismatch in how Playwright, by default, interprets navigation events. LiveView operates on a persistent WebSocket, which doesn’t trigger the traditional load event Playwright expects.


🧬 Related Insights

Written by
DevTools Feed Editorial Team

Curated insights and analysis from the editorial team.

Worth sharing?

Get the best Developer Tools stories of the week in your inbox — no noise, no spam.

Originally reported by dev.to

Stay in the loop

The week's most important stories from DevTools Feed, delivered once a week.