CtrlK
BlogDocsLog inGet started
Tessl Logo

testland/pdf-snapshot-tester

Test PDF outputs by converting per-page to images (`pdftocairo` / pdf2image / Poppler) and running pixel-diff (pixelmatch / Resemble.js / Pillow `ImageChops`) against approved baselines. Per-page-range targeting, threshold tuning, font-substitution warnings, byte-stable PDF metadata stripping (CreationDate, /ID); references/ carry cross-engine HTML→PDF regression (Chromium `page.pdf()` / WeasyPrint / wkhtmltopdf per-engine baselines, engine-agreement tests, font-embedding checks, engine-version pinning). Use when a product generates invoices, contracts, or regulatory filings whose layout must not shift, when a PDF template, font pack, or generation library is about to change, or when swapping / upgrading the PDF engine.

69

Quality

87%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Passed

No findings from the security scan

Overview
Quality
Evals
Security
Files

cross-engine-regression.mdreferences/

Cross-engine HTML-to-PDF regression

Companion reference for pdf-snapshot-tester. Consult when migrating from one HTML→PDF engine to another (wkhtmltopdf → Chromium, wkhtmltopdf → WeasyPrint), when shared templates render through more than one engine, or after an engine version upgrade (Chromium revs change PDF output; WeasyPrint major versions break layout subtly).

Different engines produce different output for the same input - fonts embed differently, @page support varies, page-break algorithms differ. Tests verify the chosen engine produces the expected output AND (optionally) that two engines agree on the critical pages.

Set up the three engines

Chromium via Playwright:

npm install -D @playwright/test
const browser = await chromium.launch();
const page = await browser.newPage();
await page.setContent(loadInvoiceHTML('inv_001'));
const pdf = await page.pdf({
  format: 'A4',
  printBackground: true,
  preferCSSPageSize: true,
});
await writeFile('out/chromium.pdf', pdf);

WeasyPrint (per the WeasyPrint docs; requires Python 3.10+):

pip install weasyprint
from weasyprint import HTML
HTML(string=html_str, base_url="https://localhost:3000/").write_pdf("out/weasyprint.pdf")
# CLI: weasyprint invoice.html out/weasyprint.pdf

wkhtmltopdf (no longer actively maintained; verify suitability):

apt-get install -y wkhtmltopdf
wkhtmltopdf --page-size A4 \
  --margin-top 20mm --margin-right 20mm \
  --margin-bottom 20mm --margin-left 20mm \
  --enable-local-file-access \
  invoice.html out/wkhtmltopdf.pdf

Per-engine baseline assertion

Each engine gets its own baseline set - don't expect engines to be identical to each other. The pixel-diff mechanics are SKILL.md's job:

import pytest
from pathlib import Path

ENGINES = ["chromium", "weasyprint", "wkhtmltopdf"]

@pytest.mark.parametrize("engine", ENGINES)
def test_invoice_per_engine(engine, tmp_path):
    actual = generate_invoice(engine, "inv_001", tmp_path)
    baseline_dir = Path(f"tests/pdf-baselines/{engine}/inv_001")
    assert_pdf_matches(actual, baseline_dir, threshold=0.005)

Cross-engine agreement test (advisory)

For pages where layout MUST be identical across engines (regulatory filings, forms with strict positioning), compare extracted positions with tolerance - never pixel-perfect across engines:

def test_form_field_positions_agree_across_engines():
    chromium_fields = extract_form_fields(generate("chromium"))
    weasyprint_fields = extract_form_fields(generate("weasyprint"))

    for field_name, chrome_pos in chromium_fields.items():
        weasy_pos = weasyprint_fields[field_name]
        # Allow ~2mm tolerance
        assert abs(chrome_pos.x - weasy_pos.x) < 5
        assert abs(chrome_pos.y - weasy_pos.y) < 5

Font embedding verification

pdfinfo -list-embedded-fonts out/chromium.pdf
def test_required_fonts_embedded(engine):
    fonts = list_embedded_fonts(generate("invoice", engine))
    assert "InterVariable" in fonts or any("Inter" in f for f in fonts)
    # System fallbacks indicate a font miss
    assert "Times" not in fonts
    assert "Helvetica" not in fonts

CSS feature support matrix

Capture which @page features each engine handles for your templates (verify per current engine version - features evolve; per MDN Paged Media, "marks" / "bleeds" support is browser-limited):

FeatureChromiumWeasyPrintwkhtmltopdf
@page :first / :left / :rightpartialfullnone
running() headersnonefullnone
target-counter()nonefullnone
bleeds, marksnonepartialnone

Engine-version pinning in CI

Engine upgrades change output - pin in CI; bump intentionally with baseline updates in the same PR:

- name: Install WeasyPrint
  run: pip install weasyprint==68.1

- name: Install Playwright (with pinned Chromium)
  run: |
    npm install -D @playwright/test@1.50.0
    npx playwright install --with-deps chromium

Anti-patterns

Anti-patternWhy it failsFix
Same baseline for all enginesOutput differs per enginePer-engine baseline sets
Skip font-embedding checkOS-default fonts substitute silentlypdfinfo -list-embedded-fonts assertion
Test only the chosen engine during a migrationMigration sandbaggedPer-engine baselines for both engines
Auto-bump engine version in CIOutput silently shiftsPin versions
Compare engines pixel-perfectThey differ naturally; test always failsCross-engine = positions + counts with tolerance

Limitations

  • WeasyPrint is the most CSS-Paged-Media-complete engine; Chromium is the most modern-CSS-complete. They have non-overlapping strengths.
  • wkhtmltopdf uses an old WebKit fork (~2014); modern CSS features often unsupported.
  • Headless rendering may not match printer output for proofing; for print-critical work, sample a real printer pass.

References

SKILL.md

tile.json