Skip to content

VRT Tests for PDF Generation #12957

Description

@benbowler

Feature Description

The generated PDF is a visual artefact, but it currently has no per-component visual regression coverage (the widget tickets explicitly skip Storybook/VRT for their @react-pdf/renderer components, and #12558 covers only a few full-document golden and fault snapshots end-to-end). This ticket adds a cheap, combinatorial layout matrix in the existing Storybook + BackstopJS system: stories render each PDF component, and the assembled report, from mock data, rasterise the resulting PDF to a <canvas> via pdfjs-dist, and let Backstop pixel-diff the canvas.

Rasterising to a canvas (rather than embedding the browser's native PDF viewer) keeps the snapshot free of viewer chrome and uses the same pdfjs-dist technique #12558 applies to its E2E capture. Chart tiles render through the real renderGoogleChartToDataURI capture path with Google Charts animation disabled and fixed data, so the snapshots also guard the chart-capture helper and surface Google Charts library changes, on top of catching layout regressions. Determinism comes from fixed mock data, a pinned reference date, and disabled chart animation. This complements #12558: the E2E suite owns the in-app flow and a few real end-to-end renders; this owns the per-variant layout matrix. Stories cover the shared PDF primitives and widget components delivered by their respective epic tickets, so this lands once those components exist.


Do not alter or remove anything below. The following sections will be managed by moderators only.

Acceptance criteria

  • The generated PDF's layout is covered by visual regression tests in the existing Storybook and Backstop system, so an unintended change to a rendered PDF fails CI.
  • Each PDF section and widget is captured in isolation and within the full assembled report.
  • Variants cover the states that change layout: populated data, empty or zero data, the "Data unavailable" placeholder, long text values, and right-to-left locales.
  • Charts render through the real capture path so the tests also catch regressions in the chart-capture helper and changes in the charting library.
  • Snapshots are deterministic run to run, using fixed mock data, a pinned reference date, and disabled chart animation.
  • The PDF is rasterised to an image for the diff rather than shown through the browser's native PDF viewer, so the snapshot is free of viewer chrome.
  • Running the visual-approval flow commits the PDF reference images.

Implementation Brief

Files to modify

Rasterisation helper

  • Create file assets/js/components/pdf-export/vrt/PDFVRTPreview.tsx - story helper that renders a given @react-pdf/renderer document to a Blob in the browser (react-pdf v4 pdf( doc ).toBlob() / usePDF), draws every page to stacked <canvas> elements at a fixed pixel width via pdfjs-dist, and sets a data-pdf-vrt-ready attribute once all pages are drawn (the Backstop readySelector for these async stories). Mirror the pdfjs page-render approach in E2E tests for PDF generation #12558's tests/playwright/specs/pdf-generation/capture-pdf.ts, factoring the shared page-to-canvas logic into a common helper where practical so there is one rendering path.
  • Update file assets/package.json - add pdfjs-dist (consumed by the Storybook build for the rasteriser).

VRT stories

  • Create Storybook files co-located with the PDF components, each exporting one VRT story wrapped in PDFVRTPreview and setting VRTStory.scenario = { readySelector: '[data-pdf-vrt-ready]', delay: <ms> } so tests/backstop picks it up and defers the screenshot until the canvas is drawn:
    • Assembled document: assets/js/components/pdf-export/shared-react-pdf-components/DashboardReport.stories.tsx, the full report with all sections populated, plus an RTL variant.
    • Shared primitives under assets/js/components/pdf-export/shared-react-pdf-components/: stories for PDFHeader, PDFFooter, PDFSection, PDFMetricTile, PDFMetricChartTile, PDFTable, and PDFAudienceTile, each covering its populated, empty/zero, and (where applicable) "Data unavailable" placeholder and long-text states.
    • Per-widget PDF components: a layout story for each widget's PDF component (the indexPDF.tsx per widget) that carries meaningful layout.
  • In each story, supply mock props and render chart tiles through the real renderGoogleChartToDataURI / ensureGoogleChartsLoaded path with Google Charts animation disabled and fixed data; the helper must await chart capture completing before drawing the canvas so the chart image is baked into the snapshot. Set a fixed reference date through the existing registry story decorator so date-range labels are stable.

Test Coverage

  • This ticket is itself the test coverage: the VRT stories above are the deliverable, discovered by tests/backstop/scenarios.js via their .scenario property and committed as reference images through npm run test:visualapprove.
  • No Jest unit tests; PDFVRTPreview is exercised through the VRT stories it renders.

QA Brief

Changelog entry

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1Medium priorityTeam SIssues for Squad 1Type: EnhancementImprovement of an existing feature

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions