PDF backends: Chromium counts as usable when only the playwright package is installed, and every render then fails unexplained #514

Closed
opened 2026-10-01 10:47:13 +00:00 by tiagoagueda · 0 comments
Owner

Found on 2026-09-29 in a read-only review of postulo at 95348908d, reading the PDF backends and every caller that catches their refusal, and rendering through ChromiumBackend with the playwright package installed and no browser downloaded.

Checked again on 2026-10-01 against main at 0a5cad587: still present. The report's draft PDF is now at applications/views.py:1220-1225; documents/pdf.py, documents/views.py, the two slow.py modules, core/errands.py:148 and core/server_views.py:92-98 have not moved.

ChromiumBackend.is_available() asks only whether the playwright package imports, not whether a Chromium binary is there to launch. The two are separate install steps (CHROMIUM_HINT names both), so an operator who did the first and not the second gets a backend that auto chooses, that Server settings reports as the one in use, and that fails on every render with an exception nothing turns into the clear message pdf.py's docstring promises ("a backend that cannot be used produces a clear message rather than an error").

  • Where: documents/pdf.py:184-185 (ChromiumBackend.is_available), documents/pdf.py:187-196 (ChromiumBackend.session, which launches with no except), documents/pdf.py:264-267 (get_pdf_backend, the auto loop); the callers that catch only PDFBackendUnavailable: documents/views.py:127-131 (DraftMixin.draft), applications/views.py:1180-1185 (the report's draft PDF), documents/slow.py:41-44 and documents/slow.py:96-108 (the two errands), applications/slow.py:40-47.
  • What happens: on a machine without Pango (the case the Chromium backend exists for; the container image installs Pango, so it is not affected), the operator runs uv sync --extra chromium but not playwright install chromium. WeasyPrintBackend.is_available() is false, ChromiumBackend.is_available() is true, so auto returns Chromium. playwright.chromium.launch() then raises playwright._impl._errors.Error ("Executable doesn't exist at …"), a plain Exception subclass. Checked in the snapshot with PLAYWRIGHT_BROWSERS_PATH pointing at an empty folder: is_available() returned True and render() raised that error, not PDFBackendUnavailable. As a result Download draft PDF on a CV or letter, and the report's draft PDF, answer 500; Export PDF and Record what you sent end with "Something went wrong. The server log has the details." (core/errands.py:148); and Server settings names chromium as the working backend (core/server_views.py:92-98).
  • Why it matters: the person sees an error page or a generic failure instead of the install hint that would fix it, and the operator's own status page says the renderer is fine.
  • Fix: in ChromiumBackend.session(), catch Playwright's Error around playwright.chromium.launch() and raise PDFBackendUnavailable carrying CHROMIUM_HINT from it; render() without a session goes through session(), so that one place covers every caller. In addition, make is_available() check that playwright.chromium.executable_path exists (inside sync_playwright(), or by the same lookup Playwright uses), so that auto falls through to the "No PDF backend is usable" message and Server settings says none is usable.

Done when

  • A test in tests/test_documents.py that stubs sync_playwright so that chromium.launch() raises Playwright's Error asserts that ChromiumBackend().render(...) raises PDFBackendUnavailable whose message contains the playwright install chromium hint, and that DraftMixin.draft redirects with that message rather than answering 500.
Found on 2026-09-29 in a read-only review of postulo at 95348908d, reading the PDF backends and every caller that catches their refusal, and rendering through `ChromiumBackend` with the `playwright` package installed and no browser downloaded. Checked again on 2026-10-01 against `main` at `0a5cad587`: still present. The report's draft PDF is now at `applications/views.py:1220-1225`; `documents/pdf.py`, `documents/views.py`, the two `slow.py` modules, `core/errands.py:148` and `core/server_views.py:92-98` have not moved. `ChromiumBackend.is_available()` asks only whether the `playwright` package imports, not whether a Chromium binary is there to launch. The two are separate install steps (`CHROMIUM_HINT` names both), so an operator who did the first and not the second gets a backend that `auto` chooses, that Server settings reports as the one in use, and that fails on every render with an exception nothing turns into the clear message `pdf.py`'s docstring promises ("a backend that cannot be used produces a clear message rather than an error"). - **Where:** `documents/pdf.py:184-185` (`ChromiumBackend.is_available`), `documents/pdf.py:187-196` (`ChromiumBackend.session`, which launches with no `except`), `documents/pdf.py:264-267` (`get_pdf_backend`, the `auto` loop); the callers that catch only `PDFBackendUnavailable`: `documents/views.py:127-131` (`DraftMixin.draft`), `applications/views.py:1180-1185` (the report's draft PDF), `documents/slow.py:41-44` and `documents/slow.py:96-108` (the two errands), `applications/slow.py:40-47`. - **What happens:** on a machine without Pango (the case the Chromium backend exists for; the container image installs Pango, so it is not affected), the operator runs `uv sync --extra chromium` but not `playwright install chromium`. `WeasyPrintBackend.is_available()` is false, `ChromiumBackend.is_available()` is true, so `auto` returns Chromium. `playwright.chromium.launch()` then raises `playwright._impl._errors.Error` ("Executable doesn't exist at …"), a plain `Exception` subclass. Checked in the snapshot with `PLAYWRIGHT_BROWSERS_PATH` pointing at an empty folder: `is_available()` returned `True` and `render()` raised that error, not `PDFBackendUnavailable`. As a result *Download draft PDF* on a CV or letter, and the report's draft PDF, answer 500; *Export PDF* and *Record what you sent* end with "Something went wrong. The server log has the details." (`core/errands.py:148`); and Server settings names `chromium` as the working backend (`core/server_views.py:92-98`). - **Why it matters:** the person sees an error page or a generic failure instead of the install hint that would fix it, and the operator's own status page says the renderer is fine. - **Fix:** in `ChromiumBackend.session()`, catch Playwright's `Error` around `playwright.chromium.launch()` and raise `PDFBackendUnavailable` carrying `CHROMIUM_HINT` from it; `render()` without a session goes through `session()`, so that one place covers every caller. In addition, make `is_available()` check that `playwright.chromium.executable_path` exists (inside `sync_playwright()`, or by the same lookup Playwright uses), so that `auto` falls through to the "No PDF backend is usable" message and Server settings says none is usable. ## Done when - A test in `tests/test_documents.py` that stubs `sync_playwright` so that `chromium.launch()` raises Playwright's `Error` asserts that `ChromiumBackend().render(...)` raises `PDFBackendUnavailable` whose message contains the `playwright install chromium` hint, and that `DraftMixin.draft` redirects with that message rather than answering 500.
tiagoagueda added this to the 0.6.0 milestone 2026-10-01 10:47:13 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
Postulo/postulo#514
No description provided.