Don’t show users a raw Playwright error — translating exceptions into friendly messages
When something fails in Playwright, the exception message comes with a long block of debug information starting with Call log:. That’s useful while debugging, but showing it as-is to someone using a maintenance tool tells them nothing about what actually went wrong.
Note: Stringifying a Playwright exception object usually produces not just the description of what failed, but several lines logging what the library internally tried to do along the way.
The shape of the problem
When Playwright times out, the exception message looks something like this:
Timeout 30000ms exceeded.
Call log:
- navigating to "https://example.com/wp-admin/", waiting until "load"
- navigation interrupted by another navigation to "https://example.com/wp-login.php"
...
The first line alone conveys something meaningful. Everything after Call log: is an internal trace of processing steps — information the end user doesn’t need. Showing this raw puts “what Playwright was attempting” front and center instead of “what actually broke,” leaving the user with no clear next step.
The fix — branch by error type, return a fixed phrase
Look at the exception message, match it against a list of common error patterns, and return a short type-specific explanation for whichever one matches. If nothing matches, fall back to just the first line, truncated if it’s too long.
def _friendly_error(e) -> str:
msg = str(e)
if 'Timeout' in type(e).__name__ or 'timeout' in msg.lower():
return "Connection to the site timed out (no response within 30 seconds)"
if 'net::ERR_NAME_NOT_RESOLVED' in msg or 'Name or service not known' in msg:
return "Could not resolve the domain (DNS error)"
if 'net::ERR_CONNECTION_REFUSED' in msg:
return "Connection to the server was refused"
if 'SSL' in msg or 'certificate' in msg.lower():
return "An SSL certificate error occurred"
if 'Login' in msg or 'login' in msg.lower():
return "Failed to log in to the WordPress admin screen"
# Fall back to the first line only (everything after Call log: gets dropped)
first_line = msg.splitlines()[0] if msg else "Unknown error"
return first_line[:120] if len(first_line) > 120 else first_line
The order of these checks matters. A timeout can be detected from the exception’s type name (TimeoutError), but the word “timeout” can also show up inside the message string itself, so the code checks both. DNS errors, connection refusals, and SSL certificate errors each correspond to a fixed Chromium error code string (net::ERR_...), which is reliable enough to match with a simple substring check.
Why “just the first line” is a safe fallback
Playwright’s exception messages follow a consistent shape: the first line is the actual error, everything from Call log: onward is debug trace. There’s no way to enumerate every possible error pattern ahead of time, so the fallback for anything unrecognized is “return the first line.” That alone reliably strips out the Call log: noise even for errors the code has never seen before.
What this design gets right
This isn’t just string formatting — it encodes the premise that what a developer wants to know and what a user wants to know are two different things. The raw exception still gets logged for debugging; what reaches the screen is a short message telling the user what to do next. The error-handling layer keeps those two responsibilities cleanly separated.
Summary
| Aspect | Detail |
|---|---|
| Problem | Raw Playwright errors come with Call log: internal trace noise that users don’t need |
| Fix | Map common error types to short fixed phrases; fall back to the first line for anything unrecognized |
| Detection | Check both the exception’s type name and substrings in the message; Chromium error codes (net::ERR_...) match reliably |
| Design principle | Keep “raw info for developers” and “short explanation for users” as clearly separate concerns |
This is a slightly different angle from the select-all checkbox trap or the version-scraping false positive in update-core.php, but the underlying stance is the same: don’t trust, and don’t display, whatever raw information Playwright hands back without thinking about it first. If you’re writing a tool that automates something, deciding what to show when it fails is part of the design, not an afterthought.