Resource library

QA How-To

How to Assert a downloaded PDF in Cypress (2026)

Learn cypress how to assert a downloaded PDF using cy.readFile, a PDF.js Node task, content checks, API downloads, dynamic filenames, and CI-safe cleanup.

24 min read | 3,191 words

TL;DR

Click or request the PDF, wait for it in the configured downloads folder with cy.readFile(path, null), verify the %PDF signature, then pass the path to a Node task that extracts pages and text with PDF.js. Assert stable business facts, page count, and response headers, while testing visual layout separately when it matters.

Key Takeaways

  • Assert the HTTP response, saved file, PDF signature, parsed structure, and business content as separate layers.
  • Use cy.readFile with null encoding to read downloaded bytes and let its retry behavior wait for the file.
  • Parse PDFs in a setupNodeEvents task with a maintained PDF library instead of trying to parse binary data in the browser test.
  • Prefer stable semantic assertions because extracted whitespace and text order can differ from the visual layout.
  • Use API-driven downloads when the test concerns document generation, and keep one UI test for the real download wiring.
  • Prevent stale-file passes with deterministic cleanup, unique data, and worker-specific paths in parallel CI.
  • Treat scanned, signed, encrypted, and visually regulated PDFs as distinct testing problems.

Cypress how to assert a downloaded PDF is a layered problem: prove the server returned a PDF, prove the browser or test saved the intended file, parse it outside the browser, and assert stable business content. A file-exists check alone can pass with a stale, empty, HTML, or wrong customer's file.

The reliable 2026 pattern uses core Cypress commands for the download and file wait, then a Node task registered in setupNodeEvents for PDF parsing. This guide uses PDF.js through the maintained pdfjs-dist package and avoids non-existent commands such as cy.parsePdf().

TL;DR

const pdfPath = 'cypress/downloads/invoice-INV-2048.pdf'

cy.contains('a', 'Download PDF').click()

cy.readFile(pdfPath, null, { timeout: 20_000 }).then((bytes) => {
  expect(bytes.length).to.be.greaterThan(100)
  expect(bytes.subarray(0, 5).toString()).to.equal('%PDF-')
})

cy.task('readPdf', pdfPath).then((pdf) => {
  const result = pdf as { pageCount: number; text: string }
  expect(result.pageCount).to.be.greaterThan(0)
  expect(result.text).to.include('Invoice INV-2048')
  expect(result.text).to.include('Total: $125.00')
})
Assertion layer What it proves What it does not prove
Response headers and status Correct download contract File reached disk or content is correct
File size and %PDF- bytes A nontrivial PDF-like file exists Pages render or values are correct
Parsed page count and text Structural and semantic content Exact visual layout
Rendered page comparison Visual appearance Business meaning by itself

The example needs the Node task configured below. Keep assertions aligned with the actual PDF requirement rather than treating one technique as complete coverage.

1. Cypress How to Assert a Downloaded PDF by Risk

Start with the reason the PDF matters. An invoice must contain the correct account, line items, taxes, currency, total, issue date, and identifier. A report may need a title, filters, generated timestamp, rows, totals, and page headers. A certificate may require a recipient, qualification, date, and verification value. Those facts define the oracle.

Break the path into contracts:

  1. The UI exposes the download only to an authorized user.
  2. The request uses the intended document identifier and returns a successful response.
  3. Headers describe a PDF attachment and safe filename.
  4. The saved bytes form a readable PDF rather than an error page.
  5. Parsed pages contain the expected semantic values.
  6. Visual, accessibility, form, signature, or archival requirements pass through specialized checks if applicable.

This decomposition makes failures useful. A 403 is not reported as "missing invoice text." A malformed PDF is not confused with a wrong total. A font clipping issue does not need to be diagnosed from raw extracted text.

Decide whether the test needs the browser download. If you are verifying the button, filename, and browser integration, trigger the UI. If you are verifying many document data combinations, call the download endpoint with cy.request(), write the bytes, and parse them. Keep a small UI path plus broader service-level document tests.

2. Install PDF.js and Configure the Downloads Folder

Install Cypress and PDF.js as development dependencies in an existing Node project. Use the version selected and locked by your repository rather than copying an unreviewed global tool:

npm install --save-dev cypress pdfjs-dist

Set a deterministic downloads folder. downloadsFolder is a supported Cypress configuration option, and the default is cypress/downloads. An explicit value makes helper code and CI artifacts easier to understand.

// cypress.config.ts
import { defineConfig } from 'cypress'

export default defineConfig({
  e2e: {
    baseUrl: 'http://localhost:3000',
    downloadsFolder: 'cypress/downloads',
    setupNodeEvents(on, config) {
      return config
    },
  },
})

Cypress clears screenshots, videos, and downloads before cypress run when trashAssetsBeforeRuns is enabled, which is the normal default behavior. Do not make an individual test depend only on run-level cleanup. Interactive sessions, reruns, and a failed setup can leave files behind. Remove the expected file before the action or create a unique document and filename.

Review the Cypress configuration guide when a monorepo or multiple testing types use different artifact paths. Resolve every task path from the project root rather than assuming the shell's working directory.

Add cypress/downloads to version-control ignore rules. Treat generated PDFs as test artifacts, not source. In CI, upload a failed document only when it is safe. Invoices, reports, and identity documents can contain sensitive data, so apply redaction, access control, and retention policy.

3. Trigger the UI Download and Wait for the File

If the filename is deterministic, the simplest path is to click the real link and use cy.readFile(). The command retries reading until the file exists and its chained assertions pass, subject to its timeout. Passing null as encoding returns bytes through Cypress's Buffer support.

describe('invoice PDF', () => {
  const invoiceId = 'INV-2048'
  const pdfPath = 'cypress/downloads/invoice-INV-2048.pdf'

  it('downloads a valid PDF from the invoice page', () => {
    cy.intercept('GET', '/api/invoices/INV-2048/pdf').as('invoicePdf')

    cy.visit('/invoices/INV-2048')
    cy.contains('a', 'Download PDF').click()

    cy.wait('@invoicePdf').then(({ response }) => {
      expect(response?.statusCode).to.equal(200)
      expect(response?.headers['content-type']).to.match(/^application\/pdf\b/i)
    })

    cy.readFile(pdfPath, null, { timeout: 20_000 }).then((bytes) => {
      expect(bytes.length).to.be.greaterThan(100)
      expect(bytes.subarray(0, 5).toString()).to.equal('%PDF-')
    })
  })
})

Register the intercept before clicking so the request cannot outrun the observer. Match the narrowest stable route. Do not assert an exact content length unless the requirement guarantees it, because PDF metadata, compression, and library updates can change bytes without changing the document.

A %PDF- prefix is a useful format sanity check, not a full validator. Parsing in the next step proves that a PDF library can open the document and enumerate its pages. If your application streams a file slowly, cy.readFile may see an intermediate file. Chained assertions retry the entire query, but a parser task called too early will not. First wait until bytes and any known minimum size are stable enough for your product.

4. Parse the PDF in a Cypress Node Task

Cypress test commands execute in the browser-side runner, while setupNodeEvents tasks execute in Node. PDF parsing and direct filesystem access belong in the Node process. Register a task that accepts a project-relative path, confines reads to the configured downloads directory, opens the bytes with PDF.js, and returns JSON-serializable data.

// cypress.config.ts
import { defineConfig } from 'cypress'
import { readFile } from 'node:fs/promises'
import { resolve, sep } from 'node:path'

export default defineConfig({
  e2e: {
    baseUrl: 'http://localhost:3000',
    downloadsFolder: 'cypress/downloads',
    setupNodeEvents(on, config) {
      on('task', {
        async readPdf(relativePath: string) {
          const downloadsRoot = resolve(config.projectRoot, config.downloadsFolder)
          const absolutePath = resolve(config.projectRoot, relativePath)

          if (!absolutePath.startsWith(downloadsRoot + sep)) {
            throw new Error('PDF path must be inside the downloads folder')
          }

          const bytes = await readFile(absolutePath)
          const pdfjs = await import('pdfjs-dist/legacy/build/pdf.mjs')
          const loadingTask = pdfjs.getDocument({ data: new Uint8Array(bytes) })
          const document = await loadingTask.promise

          try {
            const pages: string[] = []
            for (let pageNumber = 1; pageNumber <= document.numPages; pageNumber += 1) {
              const page = await document.getPage(pageNumber)
              const content = await page.getTextContent()
              const text = content.items
                .filter((item): item is typeof item & { str: string } => 'str' in item)
                .map(item => item.str)
                .join(' ')
              pages.push(text)
            }

            return {
              pageCount: document.numPages,
              pages,
              text: pages.join('\n'),
            }
          } finally {
            await document.destroy()
          }
        },
      })

      return config
    },
  },
})

The path guard prevents a test input from turning the task into an arbitrary file reader. PDF.js receives a plain Uint8Array rather than Node's Buffer subclass. The task destroys the document in finally so parser resources are released even when extraction fails.

5. Assert Stable Text, Page Count, and Business Values

Add a TypeScript shape for the serialized task result and normalize only what the requirement permits. PDF text extraction may insert different whitespace or return visual columns in content-stream order. Do not compare the entire extracted document to one giant string unless exact text serialization is itself a contract.

type ParsedPdf = {
  pageCount: number
  pages: string[]
  text: string
}

const normalizeWhitespace = (value: string) =>
  value.replace(/\s+/g, ' ').trim()

it('contains the expected invoice facts', () => {
  const pdfPath = 'cypress/downloads/invoice-INV-2048.pdf'

  cy.task('readPdf', pdfPath).then((value) => {
    const pdf = value as ParsedPdf
    const text = normalizeWhitespace(pdf.text)

    expect(pdf.pageCount).to.be.within(1, 3)
    expect(text).to.include('Invoice INV-2048')
    expect(text).to.include('Customer: Ada Example')
    expect(text).to.include('Subtotal $100.00')
    expect(text).to.include('Tax $25.00')
    expect(text).to.include('Total $125.00')
    expect(text).not.to.include('customer-secret-token')
  })
})

Derive expected values from controlled fixture data or the authoritative service response, not by copying the same presentation code used to generate the PDF. If the test calculates tax with the production helper and the PDF uses that helper, both can reproduce the same bug. Use explicit examples or an independent oracle.

For tables, assert stable row keys and totals. Extracted order may interleave columns, so a raw substring can be fragile. If table structure is a business requirement, consider a parser that exposes coordinates and reconstruct rows, or validate the source report data at the API layer and keep targeted content plus visual PDF checks.

6. Download the PDF Through cy.request for Broader Coverage

Browser downloads are appropriate for one or a few wiring tests. Data-rich document verification is often faster and easier through the endpoint. cy.request() is a supported Cypress command and can request a binary response. The browser's cookies are normally available to the request, so it can exercise the authenticated endpoint in the same test context.

it('requests and saves an invoice PDF', () => {
  const invoiceId = 'INV-2048'
  const pdfPath = 'cypress/downloads/invoice-INV-2048.pdf'

  cy.request({
    method: 'GET',
    url: '/api/invoices/INV-2048/pdf',
    encoding: 'binary',
  }).then((response) => {
    expect(response.status).to.equal(200)
    expect(response.headers['content-type']).to.match(/^application\/pdf\b/i)
    expect(response.headers['content-disposition']).to.include(
      'filename="invoice-INV-2048.pdf"'
    )
    cy.writeFile(pdfPath, response.body, 'binary')
  })

  cy.readFile(pdfPath, null).then((bytes) => {
    expect(bytes.subarray(0, 5).toString()).to.equal('%PDF-')
  })

  cy.task('readPdf', pdfPath).its('text').should('include', invoiceId)
})

This method does not prove that clicking the UI initiates the download or that the browser uses the suggested filename. Keep an end-to-end test for that contract. In return, API-driven tests make it easier to generate invoices with boundary values, call the document service, and assert results without navigating repeatedly.

For a POST-based export, send the documented body and verify anti-forgery requirements. Avoid inventing a test-only download method if the application already exposes a supported authenticated endpoint.

7. Handle Dynamic and Content-Disposition Filenames

Timestamps and random suffixes make file discovery harder and stale-file mistakes more likely. The best product contract often uses a deterministic business identifier in the filename. If the server must generate a dynamic name, observe Content-Disposition and verify it safely.

Header values can use quoted filename or RFC 5987 filename* syntax. Production parsing should use a maintained content-disposition parser rather than a casual split when international names matter. In a focused test with a documented simple header, extract the expected known pattern and reject path separators.

When the UI download filename cannot be known before the response, a Node task can list PDF files with names and modification times. Capture the directory snapshot before clicking, wait for the network response, then poll until exactly one new file appears. Do not select "the newest PDF" from an unclean shared folder because another parallel test may win the race.

An even safer design creates a worker-specific subfolder, if the browser and environment support directing downloads there, or uses a unique invoice identifier and expected filename. Cypress parallel workers generally have separate checkout processes in CI, but local and custom runners can share storage. Design explicitly rather than relying on an accident of the runner.

Sanitize filenames before building paths. Reject .., slash, backslash, NUL, and unexpected extensions. The parser task should continue enforcing that the resolved path stays under downloads.

8. Assert Metadata, Links, Annotations, and Forms Deliberately

Text and page count do not cover every PDF feature. PDF.js can expose document metadata with document.getMetadata() and page annotations with page.getAnnotations(). Extend the Node task only for requirements you actually own, and return a small serialized representation rather than library objects.

Metadata can include title, author, subject, keywords, creation date, and custom information, but producers vary. Do not fail a business test on incidental generator metadata unless a standard or requirement controls it. Creation timestamps are especially poor snapshot values.

Links should be checked for label, target, protocol, and authorization risk. An extracted annotation may identify the target even when visible text extraction is imperfect. For external links, avoid calling third-party production services from a routine test. Validate the URL structure or use an approved controlled endpoint.

Interactive forms require field-level assertions and possibly rendering or viewer integration. A flattened form may appear visually correct but have no editable fields, which can be correct or incorrect depending on the requirement. Digital signatures need a library and trust policy that can validate cryptographic integrity, certificate chain, signing time, revocation behavior, and allowed modifications. A visible image of a signature is not a digital-signature validation.

PDF/A or accessibility conformance also requires specialized validators and human review. Cypress can orchestrate those command-line tools through a narrowly scoped Node task, but it does not provide a built-in conformance assertion.

9. Separate Text Assertions From Visual PDF Testing

PDF.js text extraction does not tell you whether a total is clipped, a logo overlaps a heading, a font is missing, a table runs off the page, or a footer covers the last row. If layout is contractually important, render selected pages to images in a controlled Node or CI step and compare them with reviewed baselines or assert regions.

Visual comparison needs stable inputs: fixed data, fonts, locale, time zone, renderer, page size, and PDF producer version. Define an intentional update process for baselines. A broad pixel tolerance can hide a small but important digit change, while zero tolerance can fail on harmless rasterization differences. Combine visual comparison with semantic assertions.

Test responsive HTML before PDF generation when the source template is web-based. Component tests can catch layout and accessibility problems faster. Then retain PDF rendering checks for page breaks, print CSS, embedded fonts, headers, footers, and final output.

Scanned PDFs contain page images rather than an extractable text layer. PDF.js may return little or no text. Use a controlled OCR pipeline when the requirement is to recognize scanned content, and validate OCR confidence and language. OCR output is probabilistic, so do not reuse text-extraction expectations blindly.

For regulated output, ask whether byte identity, visual identity, semantic values, conformance, or human approval is required. These are different oracles and deserve different tooling.

10. Prevent Stale Files and Parallel CI Collisions

A stale PDF is the most dangerous false pass in this pattern. The test can skip or fail the download, then parse yesterday's correct file. Remove the expected artifact before the action through a safe Node task, or create a unique business object whose filename has never existed in the run.

The same isolation principles apply to other files. See the Cypress file download testing guide for broader filename, browser, and CI scenarios beyond PDF parsing.

Register a restricted cleanup task beside readPdf. Resolve the path under the downloads root and call Node's rm(path, { force: true }). Return null, because Cypress tasks must not resolve to undefined. Keep the same path guard used for reads.

In parallel CI, give each worker unique application data and artifact names. Include a CI node index or test-run identifier that is not secret. Never share one invoice record if tests can regenerate or mutate it. Upload PDFs on failure selectively and protect access.

Do not use an arbitrary cy.wait(5000) to wait for disk. cy.readFile retries. For especially large streaming output, add a Node task that reports size and modification time, and poll until the documented completion signal is reached. A stable size across a short interval can help, but the network response completing plus successful parser open is a stronger signal.

If parsing intermittently reports a truncated document, preserve the response timing, file size sequence, and raw file. Determine whether the browser finished writing after the intercepted response, the server ended early, or the parser ran before rename from a temporary download.

11. Build a Reusable Cypress How to Assert a Downloaded PDF Helper

Keep helpers small and typed. A command can wait for bytes and call the task, but business assertions should remain in the test so expected content is visible. Avoid a universal helper with dozens of booleans for text, snapshots, signatures, metadata, and OCR.

// cypress/support/commands.ts
type ParsedPdf = {
  pageCount: number
  pages: string[]
  text: string
}

Cypress.Commands.add('readDownloadedPdf', (relativePath: string) => {
  cy.readFile(relativePath, null, { timeout: 20_000 }).then((bytes) => {
    expect(bytes.length, 'PDF byte length').to.be.greaterThan(100)
    expect(bytes.subarray(0, 5).toString(), 'PDF signature').to.equal('%PDF-')
  })
  return cy.task('readPdf', relativePath) as Cypress.Chainable<ParsedPdf>
})
// cypress/support/index.d.ts
declare global {
  namespace Cypress {
    interface Chainable {
      readDownloadedPdf(relativePath: string): Chainable<{
        pageCount: number
        pages: string[]
        text: string
      }>
    }
  }
}

export {}

The helper first establishes format and size, then parses. The test can normalize whitespace and assert its own domain facts. Because cy.task does not retry its internal filesystem read, the preceding cy.readFile is important.

If the task input grows to include a password or options, pass a plain object and validate every property. Do not log passwords. Keep task code under unit test where parsing rules become complex.

12. Debug Failures and Protect Document Data

Classify failure before editing timeouts. A missing request suggests UI wiring, authorization, routing, or selector failure. A non-200 response belongs to the service or test setup. An HTML prefix often means a login page or proxy error was saved as .pdf. A valid signature with parser failure can indicate truncation, encryption, corruption, or unsupported features. Correct text with visual failure belongs to rendering.

Log safe facts: status, content type, sanitized filename, byte length, page count, and a correlation identifier. Avoid dumping the entire extracted document, response body, or PDF into console output. CI logs can have broader access and longer retention than the source application.

Password-protected PDFs require the parser's supported password option and secret-safe task input. Test wrong-password behavior deliberately. Never commit a real customer's password or document. For signed and confidential documents, coordinate artifact policy before enabling upload-on-failure.

Use fixture builders to create synthetic names, addresses, account numbers, and line items. Keep the data realistic enough to expose wrapping, Unicode, right-to-left, long values, and pagination defects without using production records. Test malicious text such as markup-like characters at the source layer to ensure the PDF generator escapes and renders it safely.

Finally, pin dependencies through the lockfile and review PDF parser security updates. Parsing is complex input handling. Run untrusted documents only in an appropriately isolated test environment.

Interview Questions and Answers

Q: Why should PDF parsing run in a Cypress task?

The task runs in Node, where filesystem and PDF parsing libraries fit naturally. The browser-side test keeps Cypress command flow and assertions, while the task returns a small serializable result. This separation also lets the task restrict paths and centralize parser lifecycle. I would unit test complex parsing logic outside the end-to-end suite.

Q: Is checking that the PDF file exists enough?

No. A stale, empty, truncated, HTML, unauthorized, or wrong document can exist at the expected path. I verify response contract, bytes, PDF signature, successful parsing, page structure, and business values. Visual or signature requirements need additional specialized checks.

Q: How do you avoid stale downloaded files?

I remove the expected file through a path-restricted task before the action or create a unique document and deterministic filename. In parallel runs, every worker receives unique data and artifact names. The test also waits for the current network response before reading the file.

Q: Why can extracted PDF text differ from what a user sees?

PDF content streams position glyphs rather than storing one canonical reading-order string. Columns, ligatures, hidden text, fonts, and drawing order can affect extraction. I normalize permitted whitespace and assert stable semantic facts. Layout gets a separate rendered-page check.

Q: When should you use cy.request instead of clicking Download?

I use cy.request for broad document-content combinations because it is faster and deterministic. I retain at least one UI download test to prove the control, request wiring, and filename behavior. The layers protect different contracts.

Q: How would you test a scanned PDF?

First I confirm that the requirement expects a text layer or scanned images. For image-only pages, I use an approved OCR pipeline with controlled languages, inputs, and confidence handling. I keep OCR assertions tolerant to its probabilistic nature and use visual or source-data checks as complementary evidence.

Q: How do you test a digitally signed PDF?

I use a signature-validation library or service that verifies cryptographic integrity, certificate trust, signing time, revocation policy, and allowed modifications. A visible signature image is not proof. Test identities and keys stay in protected infrastructure, and results avoid leaking document content.

Q: What should a PDF failure report include?

Include document type and synthetic identifier, request status, content type, sanitized disposition, byte length, parser result, page count, failed semantic assertion, environment version, and safe correlation data. Attach the document only when policy permits. Distinguish generation, download, parsing, content, and visual failures.

Common Mistakes

  • Calling a file-exists check complete PDF validation.
  • Parsing an old file left by an interactive or failed run.
  • Inventing cy.parsePdf() or assuming Cypress has built-in PDF text extraction.
  • Calling the parser task before the current download is complete.
  • Comparing the entire extracted text despite unstable whitespace and reading order.
  • Using production logic as the only expected-value calculator.
  • Testing every PDF data combination through the browser UI.
  • Claiming text extraction proves visual layout, accessibility, signature, or PDF/A conformance.
  • Selecting the newest PDF from a shared folder during parallel execution.
  • Allowing a Node task to read arbitrary filesystem paths.
  • Printing full documents, tokens, or personal data into CI logs.
  • Uploading sensitive failure artifacts without access and retention controls.

Conclusion

Cypress how to assert a downloaded PDF is solved reliably by layering evidence. Verify the response, wait for the new file with cy.readFile, check its bytes, parse it in a restricted Node task, and assert stable business facts. Add visual, OCR, signature, form, or conformance tools only when the requirement calls for them.

Implement the PDF.js task first, then add one deterministic invoice or report test. Once it is stable, split broad content combinations into API-driven downloads and retain a small UI path. That design is faster, safer, and far easier to diagnose than a single file-exists assertion.

Interview Questions and Answers

Why parse PDFs in setupNodeEvents?

The task runs in Node with direct filesystem and parser-library access. Cypress retains command flow in the browser while the task returns a small JSON-safe result. The boundary also supports path validation and centralized resource cleanup.

What layers would you assert for a PDF download?

I assert authorization and response headers, current file creation, nontrivial bytes and PDF signature, parser success and page count, then stable business content. Visual, accessibility, form, conformance, and signature checks remain separate requirements.

How do you prevent a stale PDF false pass?

I remove the expected file safely before the action or create unique data and a deterministic filename. I observe the current request and isolate paths by worker. I never choose an arbitrary newest file from a shared directory.

Why is full extracted-text equality fragile?

PDF text items can have unstable whitespace and content-stream order even when rendering is unchanged. I normalize permitted whitespace and assert meaningful fields independently. Coordinate-aware or visual assertions cover structure where required.

When would you use cy.request for a PDF?

I use it for broad content and boundary coverage at the document endpoint. It gives direct headers and binary response and avoids repeated navigation. A smaller UI test still proves user-facing download wiring.

How would you validate an image-only PDF?

I first confirm the expected absence of a text layer, then use a controlled OCR pipeline if recognition is required. OCR output needs language and confidence policy. Visual and source-data assertions complement it.

How would you test PDF layout?

I render selected pages in a controlled environment with fixed fonts, data, locale, and producer version. Reviewed image baselines cover clipping and page breaks, while semantic assertions protect exact values. Baseline changes require intentional review.

What security controls belong in a PDF test task?

Resolve and restrict paths to the downloads directory, validate inputs, avoid logging passwords or content, return only needed fields, and destroy parser resources. Untrusted documents should be parsed in an appropriately isolated environment with maintained dependencies.

Frequently Asked Questions

Can Cypress read a downloaded PDF directly?

Cypress can read the file bytes with cy.readFile(path, null), but it does not include a built-in PDF text parser. Register a Node task with a maintained library such as PDF.js and return serializable text and page data.

Where does Cypress save downloaded files?

The default downloadsFolder is cypress/downloads unless the project changes it in configuration. Use the configured value consistently and keep generated files out of source control.

How do I wait for a PDF download in Cypress?

Observe the download request and use cy.readFile on the expected path with an appropriate timeout. Its query and assertions retry, after which a Node task can parse the completed file.

Why does PDF text extraction have strange spaces or order?

PDFs position text items on a page and do not always store one logical reading sequence. Normalize only allowed whitespace, assert stable facts, and use coordinate-aware or visual checks when table structure matters.

Should I download a PDF through the UI or API?

Use a UI test to prove the real download control and browser wiring. Use cy.request for broader document-content coverage because it is faster and gives direct access to response headers and bytes.

Can Cypress validate the visual layout of a PDF?

Not through text extraction alone. Render controlled pages with a suitable Node or CI tool and compare reviewed images, while retaining semantic assertions for values and identifiers.

How do I test an encrypted or signed PDF?

Pass protected test credentials through secure configuration to a parser that supports encryption. Use a dedicated cryptographic validator for signatures, because a visible signature image and successful text extraction do not prove integrity.

Related Guides