PDF reftesting for paged-media engines with Playwright.
The runner processes a test document, prints it to PDF, prints its plain HTML
reference, and compares the rendered pages. References use WPT match,
mismatch, and fuzzy metadata.
Requires Node.js 22.13 or newer and Chromium for Playwright.
npm install --save-dev @pagedjs/test-runner @playwright/test
npx playwright install chromiumNo system PDF tools or build step are required.
Create reftest.config.js in the repository root:
import { defineConfig } from "@pagedjs/test-runner";
export default defineConfig({
suites: ["specs/*"],
server: {
command: "serve . -p 8080 --no-clipboard",
port: 8080,
cwd: ".",
reuseExistingServer: true,
},
processor: "/specs/helpers/process.js",
importMap: {
imports: {
"@pagedjs/engine": "/src/index.js",
},
},
});Config discovery checks .js, .mjs, and .cjs files under .config/. Use
--config <path> for any other location.
Each suite is a directory containing tests.yml and its HTML files:
specs/
css-page/
tests.yml
basic-pagination-001.html
basic-pagination-001-ref.html
# specs/css-page/tests.yml
tests:
- name: basic-pagination-001
- name: widows-orphans
fuzzy: maxDifference=0-2;totalPixels=0-400
- name: long-table
skip: "table row splitting is not implemented"name is a filename stem, without a path or .html suffix.
Declare the reference in the test document:
<link rel="match" href="basic-pagination-001-ref.html">The test is loaded through processor. The reference is loaded and printed as
authored; it does not receive the processor, import map, manifest data, or
testPrintCSS.
processor is a browser module that paginates the test document. It reads the
manifest entry from data-reftest-entry and marks the document ready when
processing finishes:
const root = document.documentElement;
try {
const entry = JSON.parse(root.dataset.reftestEntry || "{}");
await paginate(entry);
root.dataset.reftestReady = "true";
} catch (error) {
root.dataset.reftestError = `${error.message}\n${error.stack}`;
}The attribute names are configurable. A processor error or readiness timeout fails the test.
npx pagedjs-test-runner validate
npx pagedjs-test-runner list
npx pagedjs-test-runner runFilter by suite name:
npx pagedjs-test-runner run css-page css-breakRun options:
npx pagedjs-test-runner run --workers 4
npx pagedjs-test-runner run --grep "widows"
npx pagedjs-test-runner run --reporter htmlPass additional arguments to Playwright after --.
Use standard WPT metadata:
<link rel="match" href="expected.html">
<link rel="mismatch" href="must-differ.html">
<meta name="fuzzy" content="maxDifference=0-2;totalPixels=0-400">- Match references are alternatives; one must match.
- Every mismatch reference must differ.
maxDifferenceandtotalPixelsranges are inclusive.- A PDF page-count difference does not match.
Reference-specific fuzzy metadata uses the WPT prefix form:
<meta name="fuzzy" content="expected.html:maxDifference=0-2;totalPixels=0-400">Fuzzy settings are resolved in this order: manifest entry, reference-specific metadata, document metadata, suite default, repository default.
For documents that need additional work before printing, add reftest-wait to
the root element. The runner dispatches one bubbling TestRendered event and
waits for the class to be removed.
<html class="reftest-wait">
<script>
document.addEventListener("TestRendered", () => {
document.documentElement.classList.remove("reftest-wait");
});
</script>Test and reference documents both support reftest-wait. Set the timeout with
readyTimeout.
Use testPrintCSS for CSS required by the processed output but not by the
reference:
export default defineConfig({
// ...
testPrintCSS: `
paged-page { break-after: page }
paged-page:last-child { break-after: auto }
`,
});Page size and reference layout CSS should remain in the test files or their stylesheets.
| Key | Default | Description |
|---|---|---|
suites |
required | Suite directories. A trailing /* expands directories containing tests.yml. |
server |
— | Playwright web server configuration. |
baseURL |
derived from server.port |
HTTP(S) URL used to load documents. Path prefixes are supported. |
rootDir |
config directory | Base directory for relative paths. |
serverRoot |
rootDir |
Filesystem directory served at baseURL. |
processor |
null |
Browser module loaded in test documents. |
importMap |
null |
Import map installed before the processor. |
testPrintCSS |
"" |
CSS added to processed tests only. |
fuzzy |
exact | Default WPT fuzzy tolerance. |
entryAttribute |
data-reftest-entry |
Attribute containing the manifest entry as JSON. |
readyAttribute |
data-reftest-ready |
Processor completion attribute. |
errorAttribute |
data-reftest-error |
Processor error attribute. |
timeout |
30000 |
Playwright test timeout in milliseconds. |
readyTimeout |
15000 |
Processor and reftest-wait timeout in milliseconds. |
pdf.scale |
2 |
PDF rasterization scale. |
artifactsDir |
.pagedjs-test-runner |
Parent directory for run output. |
baseURL must be an HTTP(S) URL without credentials, a query, or a fragment.
Each run writes to a unique directory and prints its path:
.pagedjs-test-runner/runs/<uuid>/
results/
report/
results/ contains Playwright output and comparison attachments. report/
contains the HTML report. Artifacts are not pruned automatically.
Exact failures include Playwright expected, actual, and diff images for the failing page. Fuzzy failures include WPT comparison details and page images. No image or PDF baselines are committed or updated by the runner.
Use the runner from a repository-owned Playwright suite:
// reftest.spec.js
import { loadConfig, registerSuites } from "@pagedjs/test-runner";
const config = await loadConfig("./reftest.config.js");
registerSuites(config);Filter suites with registerSuites(config, { suites: ["css-page"] }).
Use the supplied Playwright settings from a repository config:
// playwright.config.js
import { defineConfig } from "@playwright/test";
import { loadConfig, playwrightConfig } from "@pagedjs/test-runner";
const config = await loadConfig("./reftest.config.js");
export default defineConfig({
...playwrightConfig(config),
testDir: ".",
testMatch: "reftest.spec.js",
});The runner uses one headless Chromium project.
MIT