Printing Pages to PDF
Generate PDFs of any page with the W3C print command: orientation, scale, page ranges, margins, backgrounds, and how to assert on the PDF's contents in your tests.
Invoices, reports, tickets and certificates are usually reviewed as PDFs, and “the print layout is broken” is a bug that screenshots never catch. The W3C WebDriver specification includes a print command, exposed in Selenium 4, that renders the current page to PDF the same way the browser’s Print dialog would, including @media print styles. It works headless, which is where you will run it in CI.
Basic Usage
import org.openqa.selenium.Pdf;import org.openqa.selenium.PrintsPage;import org.openqa.selenium.print.PrintOptions;
driver.get("https://app.example.com/invoices/INV-1042");
PrintOptions options = new PrintOptions();Pdf pdf = ((PrintsPage) driver).print(options);
byte[] bytes = Base64.getDecoder().decode(pdf.getContent());Files.write(Path.of("invoice-1042.pdf"), bytes);import base64from selenium.webdriver.common.print_page_options import PrintOptions
driver.get("https://app.example.com/invoices/INV-1042")
options = PrintOptions()pdf_base64 = driver.print_page(options)
with open("invoice-1042.pdf", "wb") as f: f.write(base64.b64decode(pdf_base64))const fs = require('fs');
await driver.get('https://app.example.com/invoices/INV-1042');
const pdfBase64 = await driver.printPage({});fs.writeFileSync('invoice-1042.pdf', Buffer.from(pdfBase64, 'base64'));using OpenQA.Selenium;
driver.Navigate().GoToUrl("https://app.example.com/invoices/INV-1042");
var options = new PrintOptions();PrintDocument pdf = driver.Print(options);pdf.SaveAsFile("invoice-1042.pdf");Chrome and Edge require headless mode for the print command (headed Chrome returns an error); Firefox prints in both modes.
Print Options
| Option | Values | Default |
|---|---|---|
| Orientation | portrait, landscape | portrait |
| Scale | 0.1 to 2.0 | 1.0 |
| Background | print background colours and images | false |
| Shrink to fit | scale content to page width | true |
| Page size | width and height in centimetres | 21.59 x 27.94 (US Letter) |
| Margins | top, bottom, left, right in centimetres | 1.0 each |
| Page ranges | e.g. "1-3", "5" | all |
PrintOptions options = new PrintOptions();options.setOrientation(PrintOptions.Orientation.LANDSCAPE);options.setScale(0.9);options.setBackground(true);options.setShrinkToFit(true);options.setPageSize(new PageSize(29.7, 21.0)); // A4 landscape, height x width in cmoptions.setPageMargin(new PageMargin(1.5, 1.5, 1.0, 1.0)); // top, bottom, left, rightoptions.setPageRanges("1-2");
Pdf pdf = ((PrintsPage) driver).print(options);options = PrintOptions()options.orientation = "landscape"options.scale = 0.9options.background = Trueoptions.shrink_to_fit = Trueoptions.page_height = 21.0 # cmoptions.page_width = 29.7options.margin_top = 1.5options.margin_bottom = 1.5options.margin_left = 1.0options.margin_right = 1.0options.page_ranges = ["1-2"]
pdf_base64 = driver.print_page(options)const pdfBase64 = await driver.printPage({orientation: 'landscape',scale: 0.9,background: true,shrinkToFit: true,pageWidth: 29.7,pageHeight: 21.0,margin: { top: 1.5, bottom: 1.5, left: 1.0, right: 1.0 },pageRanges: ['1-2'],});var options = new PrintOptions{ Orientation = PrintOrientation.Landscape, ScaleFactor = 0.9, OutputBackgroundImages = true, ShrinkToFit = true, PageDimensions = new PrintOptions.PageSize { Width = 29.7, Height = 21.0 }, PageMargins = new PrintOptions.Margins { Top = 1.5, Bottom = 1.5, Left = 1.0, Right = 1.0 },};options.AddPageRangeToPrint("1-2");
PrintDocument pdf = driver.Print(options);Asserting on the PDF
A saved file is only useful if the test checks it. Extract the text with a PDF library and assert on it, or count pages, or compare to a baseline with a tolerance.
// Apache PDFBox: org.apache.pdfbox:pdfboximport org.apache.pdfbox.Loader;import org.apache.pdfbox.pdmodel.PDDocument;import org.apache.pdfbox.text.PDFTextStripper;
byte[] bytes = Base64.getDecoder().decode(pdf.getContent());try (PDDocument doc = Loader.loadPDF(bytes)) { assertEquals(2, doc.getNumberOfPages()); String text = new PDFTextStripper().getText(doc); assertTrue(text.contains("Invoice INV-1042")); assertTrue(text.contains("Total due: $1,240.00"));}# pip install pypdfimport iofrom pypdf import PdfReader
reader = PdfReader(io.BytesIO(base64.b64decode(pdf_base64)))assert len(reader.pages) == 2text = "".join(page.extract_text() for page in reader.pages)assert "Invoice INV-1042" in textassert "Total due: $1,240.00" in text// npm install pdf-parseconst pdfParse = require('pdf-parse');
const data = await pdfParse(Buffer.from(pdfBase64, 'base64'));assert.strictEqual(data.numpages, 2);assert.ok(data.text.includes('Invoice INV-1042'));assert.ok(data.text.includes('Total due: $1,240.00'));// NuGet: PdfPigusing UglyToad.PdfPig;
using var doc = PdfDocument.Open(Convert.FromBase64String(pdf.AsBase64EncodedString));Assert.That(doc.NumberOfPages, Is.EqualTo(2));var text = string.Join("", doc.GetPages().Select(p => p.Text));Assert.That(text, Does.Contain("Invoice INV-1042"));Assert.That(text, Does.Contain("Total due: $1,240.00"));Text extraction merges words unpredictably across columns, so assert on distinctive substrings rather than whole sentences.
Waiting Before Printing
The print command captures the current DOM. If the page is still fetching data, you print a spinner. Wait for the content, and for fonts and images if the layout depends on them:
WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(15));wait.until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector("[data-testid='invoice-total']")));wait.until(d -> Boolean.TRUE.equals(((JavascriptExecutor) d).executeScript( "return document.fonts.status === 'loaded' && [...document.images].every(i => i.complete)")));
Pdf pdf = ((PrintsPage) driver).print(new PrintOptions());wait = WebDriverWait(driver, 15)wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, "[data-testid='invoice-total']")))wait.until(lambda d: d.execute_script( "return document.fonts.status === 'loaded' && [...document.images].every(i => i.complete)"))
pdf_base64 = driver.print_page(PrintOptions())await driver.wait(until.elementLocated(By.css("[data-testid='invoice-total']")), 15000);await driver.wait(() => driver.executeScript("return document.fonts.status === 'loaded' && [...document.images].every(i => i.complete)"), 15000);
const pdfBase64 = await driver.printPage({});var wait = new WebDriverWait(driver, TimeSpan.FromSeconds(15));wait.Until(d => d.FindElement(By.CssSelector("[data-testid='invoice-total']")).Displayed);wait.Until(d => (bool)((IJavaScriptExecutor)d).ExecuteScript( "return document.fonts.status === 'loaded' && [...document.images].every(i => i.complete)"));
var pdf = driver.Print(new PrintOptions());Alternatives and Related Techniques
- Chrome’s
--print-to-pdfflag produces a PDF at launch without WebDriver; fine for one-off documents, useless for authenticated pages. - CDP
Page.printToPDFoffers header and footer templates that the W3C command lacks; Chromium only. - Emulating print media before a screenshot: BiDi’s
emulationmodule or CDPEmulation.setEmulatedMedia("print")lets you screenshot the print layout for visual comparison without producing a PDF.
Summary
printis a W3C command available in Selenium 4 on all major browsers; Chromium needs headless.- Options cover orientation, scale, page size, margins, ranges and backgrounds.
- Wait for content, fonts and images before printing, then assert on the PDF with a parsing library.
- Use CDP only when you need header/footer templates.
Copy-paste recipes for this topic
- Verify the Contents of a Downloaded File
Assert on a downloaded CSV, PDF or Excel file: read it after download completes, check headers and rows, and clean up.