You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
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.
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/renderercomponents, 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>viapdfjs-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-disttechnique #12558 applies to its E2E capture. Chart tiles render through the realrenderGoogleChartToDataURIcapture 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
Implementation Brief
Files to modify
Rasterisation helper
assets/js/components/pdf-export/vrt/PDFVRTPreview.tsx- story helper that renders a given@react-pdf/rendererdocument to aBlobin the browser (react-pdf v4pdf( doc ).toBlob()/usePDF), draws every page to stacked<canvas>elements at a fixed pixel width viapdfjs-dist, and sets adata-pdf-vrt-readyattribute once all pages are drawn (the BackstopreadySelectorfor these async stories). Mirror the pdfjs page-render approach in E2E tests for PDF generation #12558'stests/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.assets/package.json- addpdfjs-dist(consumed by the Storybook build for the rasteriser).VRT stories
PDFVRTPreviewand settingVRTStory.scenario = { readySelector: '[data-pdf-vrt-ready]', delay: <ms> }sotests/backstoppicks it up and defers the screenshot until the canvas is drawn:assets/js/components/pdf-export/shared-react-pdf-components/DashboardReport.stories.tsx, the full report with all sections populated, plus an RTL variant.assets/js/components/pdf-export/shared-react-pdf-components/: stories forPDFHeader,PDFFooter,PDFSection,PDFMetricTile,PDFMetricChartTile,PDFTable, andPDFAudienceTile, each covering its populated, empty/zero, and (where applicable) "Data unavailable" placeholder and long-text states.indexPDF.tsxper widget) that carries meaningful layout.renderGoogleChartToDataURI/ensureGoogleChartsLoadedpath 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
tests/backstop/scenarios.jsvia their.scenarioproperty and committed as reference images throughnpm run test:visualapprove.PDFVRTPreviewis exercised through the VRT stories it renders.QA Brief
Changelog entry