Skip to content

serverless pdf export with playwright#517

Open
viseshrp wants to merge 18 commits intomainfrom
codex/feat-browser-pdf-export
Open

serverless pdf export with playwright#517
viseshrp wants to merge 18 commits intomainfrom
codex/feat-browser-pdf-export

Conversation

@viseshrp
Copy link
Collaborator

No description provided.

viseshrp added 18 commits March 21, 2026 15:33
Add a new optional 'pdf' dependency group for Playwright and update the uv lockfile so the repository's dependency metadata remains consistent under make check.

Introduce the new serverless PlaywrightPDFRenderer component in a dedicated module. The renderer keeps Playwright as a lazy runtime dependency, loads exported HTML from a local file:// URL, forces screen media, waits on signal-based browser readiness checks, and then generates a PDF through Chromium's page.pdf API. The implementation includes explicit handling for missing Playwright installs and wraps browser failures in ADRException with contextual logging.

Add isolated unit tests for the renderer in a new serverless test file. These cover the import-error path directly and exercise the real browser-rendering path when Playwright/Chromium are installed, while skipping those browser-dependent cases when the local environment does not provide Chromium.
Add two new serverless ADR methods: render_report_as_browser_pdf and export_report_as_browser_pdf. These methods leave the existing WeasyPrint-based APIs untouched and instead build a parallel Playwright-based pipeline that renders browser HTML, exports it to a self-contained temporary directory with ServerlessReportExporter, and then converts that exported report to PDF through the new renderer.

Keep Playwright lazy at the ADR layer by importing the renderer only inside the new code path. Validate required template-identifying kwargs and static directory configuration before starting work, preserve ADRException behavior for known failures, and wrap unexpected browser-export failures with a browser-specific ADRException message.

Append new ADR tests at the end of tests/serverless/test_adr.py only. The new tests are intentionally unit-style so they exercise the added browser-PDF methods without depending on the Docker-backed adr_serverless fixture in this environment. They verify the success path, missing kwargs, missing static directory, wrapped render failure, file export, and page-option plumbing into the renderer.
Move Playwright out of the optional pdf extra and into the main runtime dependency set so browser PDF export works without an extra install target.

This updates pyproject.toml and refreshes uv.lock to keep the lockfile consistent with the new dependency policy.
Refine the Playwright PDF renderer so browser-fidelity exports keep report content intact instead of clipping wide plots and viewer output.

This commit does three things:
- injects print-time capture styles only at the rendered-item level so parent panels can still paginate normally,
- waits for the ADR browser-render readiness signals used by the serverless HTML export path,
- computes a content-aware PDF width with unit conversion and a small right-edge buffer so Plotly legends and other browser-rendered elements remain fully visible.

The accompanying test updates cover the CSS injection target set, CSS unit conversion, width expansion behavior, and the revised missing-install guidance.
Remove the fixed right-edge buffer used by the browser PDF renderer and derive the page width from the true visible bounds of rendered report content instead.

The updated measurement walks both light DOM and shadow DOM, collects visible client rects, and computes the horizontal span relative to the report root. This keeps the page-width adjustment adaptive to future ADR and Plotly layout changes instead of depending on a hard-coded padding constant.

Tests were updated to validate the new bounds-driven width calculation and the fallback path when no visible bounds are found.
Replace the hard-coded PDF width buffer with an explicit browser viewport policy for the branch-added browser PDF export path. The renderer now lays out the exported HTML at a configurable headless Chromium viewport before navigation, which makes responsive content such as Plotly charts more reproducible across machines and removes the need for a fixed right-edge legend buffer.

Also narrow the width probe to content-bearing selectors instead of scanning the entire DOM tree. This keeps the measurement step bounded while still accounting for plot containers, legends, tables, media, and active viewers. Thread the new viewport options through the branch-added ADR browser PDF methods and update the new tests to cover viewport argument plumbing and viewport validation.
Reduce the branch-added browser PDF default viewport from 1920x1080 to 1600x900. This keeps the layout deterministic across machines while lowering render cost for the common export path.

Update the renderer defaults, ADR browser PDF method defaults, docstrings, and the related test scaffolding to use the new viewport policy. No tests were run in this step.

fix: preserve browser layout width in PDF fit

Use the effective browser layout width as a lower bound when computing the PDF page width for browser-fidelity exports. This keeps the PDF content area at least as wide as the Chromium viewport that responsive content used during layout, which prevents right-edge Plotly legends from being clipped when the report root is narrower than the browser canvas.

Add a dedicated layout-width measurement helper and update the unit tests to cover both viewport-driven fitting and wider-than-viewport content. No tests were run in this step.
Remove the branch-added viewport width and height knobs from the public browser PDF APIs and keep the export viewport as an internal renderer policy. This keeps the API focused on document-level controls while preserving deterministic browser layout for exports.

Also validate PDF margins eagerly with clear ADRException messages and instantiate the renderer before the HTML render/export pipeline starts, so invalid margin input fails before any expensive work is performed. Update the page_width documentation to describe the current minimum-width behavior and replace the brittle exact-width unit tests with stable margin-validation coverage.
Remove page_width and page_height from the branch-added browser PDF methods and the renderer constructor. The browser PDF export path now uses a fixed internal page-size policy together with the existing layout-preserving width fit, which keeps the public API aligned with the decision to expose only sensible document controls.

Update the focused tests to drop custom page-size plumbing and keep coverage on the remaining browser PDF options. No tests were run in this step.
Remove the internal A4 page width and height defaults from the Playwright PDF renderer. The renderer now lets Playwright choose its default page size unless an explicit width is needed to preserve the rendered browser layout and avoid clipping.

This keeps the browser PDF path aligned with the rule to avoid specifying parameters unless they are necessary for fidelity. No tests were run in this step.
Restore an internal explicit PDF page height in the Playwright renderer so pagination remains under ADR's control instead of falling back completely to Playwright's default page format. Width remains conditional and is only passed when needed to preserve browser layout and avoid clipping.

This keeps the browser PDF path aligned with the request to avoid unnecessary parameters while retaining deterministic page-break behavior. No tests were run in this step.
Copilot AI review requested due to automatic review settings March 21, 2026 23:35
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR introduces a new “browser-fidelity” serverless PDF export path that renders ADR’s offline HTML export in headless Chromium via Playwright, producing PDFs that more closely match on-screen browser output.

Changes:

  • Add Playwright as a dependency and update the uv.lock resolution accordingly.
  • Add a new PlaywrightPDFRenderer that loads exported HTML from disk, waits for ADR rendering signals, and prints to PDF.
  • Add new ADR APIs (render_report_as_browser_pdf, export_report_as_browser_pdf) plus unit/integration-style tests and accompanying spec/plan docs.

Reviewed changes

Copilot reviewed 11 out of 12 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
pyproject.toml Adds Playwright to project dependencies.
uv.lock Updates locked dependency set for Playwright (and transitive deps).
src/ansys/dynamicreporting/core/serverless/pdf_renderer.py New Playwright-based HTML→PDF renderer with readiness checks and PDF sizing logic.
src/ansys/dynamicreporting/core/serverless/adr.py Adds new serverless ADR browser-PDF render/export methods that use ServerlessReportExporter + PlaywrightPDFRenderer.
tests/serverless/test_pdf_renderer.py New unit tests for Playwright renderer behavior (with skip logic).
tests/serverless/test_adr.py Adds tests covering the new ADR browser-PDF APIs and option plumbing.
docs/prompts/feat/serverless-pdf/spec.md Adds a technical spec for the feature (currently mismatched with packaging).
docs/prompts/feat/serverless-pdf/implementation_plan.md Adds an implementation plan (currently mismatched with packaging).
docs/prompts/feat/serverless-pdf/* (other new docs) Adds supporting prompt/analysis documentation for readiness signals and implementation guidance.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +170 to +197
page.add_style_tag(
content="""
@media print {
adr-data-item,
.nexus-plot,
.nexus-plot > .plot-container,
.js-plotly-plot,
.plot-container,
.svg-container {
break-inside: avoid !important;
page-break-inside: avoid !important;
}

adr-data-item,
.nexus-plot,
.plot-container,
.svg-container,
.main-svg,
.table-responsive {
overflow: visible !important;
max-height: none !important;
}

.adr-spinner-loader-container,
.modebar {
display: none !important;
}
}
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

render_pdf() sets page.emulate_media(media="screen"), but the injected CSS is wrapped in @media print { ... }. With screen media emulation, these rules won’t apply, so the PDF-only overrides (page-break avoidance/overflow fixes/hiding spinners) may be ignored. Consider removing the @media print wrapper, switching it to @media screen, or duplicating the rules for both media types so they apply during page.pdf() in screen mode.

Copilot uses AI. Check for mistakes.
Comment on lines +692 to +711
### Playwright as a Dependency

Playwright is **not** a lightweight dependency. It requires:
1. The `playwright` Python package.
2. A Chromium browser binary downloaded via `playwright install chromium`.

**Decision**: Add `playwright` as an **optional dependency** under a new `[project.optional-dependencies]` group named `pdf`:

```toml
[project.optional-dependencies]
pdf = [
"playwright>=1.40.0",
]
```

This keeps the core package lightweight. Users who want Playwright-based PDF export must:
```bash
pip install ansys-dynamicreporting-core[pdf]
playwright install chromium
```
Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This spec says Playwright is an optional extra (ansys-dynamicreporting-core[pdf] via [project.optional-dependencies]), but this PR adds playwright>=1.40.0 to core [project].dependencies in pyproject.toml and the runtime error message instructs pip install ansys-dynamicreporting-core (no extra). Please update this section (and the later file summary table) to match the actual packaging decision to avoid confusing install instructions.

Copilot uses AI. Check for mistakes.
Comment on lines +31 to +44
### Task 1.1: Add `playwright` as an optional dependency

**File**: `pyproject.toml`

Add a new optional dependency group. Find the `[project.optional-dependencies]` section and add:

```toml
pdf = [
"playwright>=1.40.0",
]
```

**Validation**: `uv sync --all-extras` succeeds. `python -c "from playwright.sync_api import sync_playwright"` works after running `playwright install chromium`.

Copy link

Copilot AI Mar 21, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The plan instructs adding Playwright as an optional pdf extra (ansys-dynamicreporting-core[pdf]) and shows the corresponding install instructions, but pyproject.toml in this PR adds Playwright to the main dependency list. Please reconcile this plan with the actual packaging choice (core dep vs optional extra) so future implementers don’t follow incorrect steps.

Suggested change
### Task 1.1: Add `playwright` as an optional dependency
**File**: `pyproject.toml`
Add a new optional dependency group. Find the `[project.optional-dependencies]` section and add:
```toml
pdf = [
"playwright>=1.40.0",
]
```
**Validation**: `uv sync --all-extras` succeeds. `python -c "from playwright.sync_api import sync_playwright"` works after running `playwright install chromium`.
### Task 1.1: Ensure `playwright` is available as a core dependency
**File**: `pyproject.toml`
In this PR, Playwright is added as a standard runtime dependency, not as an optional `[project.optional-dependencies]` extra.
Do not introduce a `pdf` extra or change the existing packaging strategy.
Instead, verify that `playwright>=1.40.0` is present in the main dependency list (for example, under `[project]``dependencies`)
and keep it there so that serverless PDF export code can rely on Playwright being installed by default.
**Validation**: `uv sync` succeeds. `python -c "from playwright.sync_api import sync_playwright"` works after running `playwright install chromium`.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants