File Download Steps

File-download steps capture and verify files a site produces - CSV exports, PDF reports, generated images, ZIP archives. They cover the two ways a download happens in practice: fetching a known URL directly, and clicking a link that triggers the browser's download event. Either way, the result (saved path, filename, and raw content) is kept on the scenario's world as the "last download" so follow-up Then steps can assert against it without re-downloading.

URL downloads go through the browser's request context (page.request.get), which means they carry the same cookies and auth state as the logged-in session - an export behind a login works without extra setup. Link downloads use Playwright's download event, so they only work for links that actually offer a file (via Content-Disposition or the download attribute), not links that merely navigate.

Files are saved to a per-process temporary directory (webship-dl-<pid> under the OS temp dir), so parallel workers never overwrite each other's downloads. Failures raise friendly errors with a hint - for example, the link step tells you to switch to the URL step if the link only navigates.

Quick reference

StepWhat it does
When I download the file from the URL "/exports/users.csv"Fetch a URL with the session's request context and capture the result.
When I download the file from the link "Download report"Click a link by visible text and capture the browser download.
Then the downloaded file should contain:Assert the last download's body (UTF-8) contains a doc-string substring.
Then the downloaded file name should be "report.pdf"Assert the last download's filename exactly.
Then the downloaded file name should contain "report"Assert the filename contains a substring.
Then the downloaded file should be a zip archive containing the following files named:(Pending) Assert exact ZIP member names.
Then the downloaded file should be a zip archive containing the following files partially named:(Pending) Assert ZIP members by name substring.
Then the downloaded file should be a zip archive not containing the following files partially named:(Pending) Assert ZIP members are absent by name substring.

Steps in detail

When I download the file from the URL "..."

Issues a GET through the browser's request context and asserts a 200 status before saving. Relative paths are joined to the configured launchUrl; absolute http(s):// URLs are used as-is. The filename comes from the response's Content-Disposition header when present, otherwise from the last segment of the URL path. Query strings are fine - an export endpoint like /api/report?format=pdf works.

When I download the file from the URL "/exports/users.csv"
Then the downloaded file name should be "users.csv"
When I download the file from the URL "https://example.com/doc.pdf"

When I download the file from the link "..."

Resolves the link by accessible role and name first (getByRole('link')), falling back to a:has-text(...), then clicks it while waiting for Playwright's download event. The file is saved under its suggestedFilename(). If the link opens in a new tab or just navigates instead of downloading, the step fails with a hint to use the URL variant instead.

Given I am on "/reports"
When I download the file from the link "Export CSV"
Then the downloaded file name should contain ".csv"

Then the downloaded file should contain:

Takes a Gherkin doc string and asserts it appears in the last download's body decoded as UTF-8. Works for any text-based format - CSV headers, JSON keys, SVG markup - and even for magic prefixes of binary formats such as %PDF-.

When I download the file from the URL "/exports/users.csv"
Then the downloaded file should contain:
  """
  name,email
  """

Then the downloaded file name should be "..." / should contain "..."

Exact and substring assertions on the captured filename. Use the substring form when filenames embed timestamps or hashes (report-2026-07-21.pdf). Both steps fail with "No download has been performed." if no download step ran earlier in the scenario.

When I download the file from the URL "/files/logo.svg"
Then the downloaded file name should be "logo.svg"
And the downloaded file name should contain "logo"

ZIP archive assertions (pending)

Three table-driven steps are reserved for inspecting ZIP contents: exact member names, members partially named, and members that must be absent. They are currently marked pending - running them reports the scenario as pending rather than passing silently. Enabling them requires adding a ZIP library (yauzl or adm-zip) to the project.

Then the downloaded file should be a zip archive containing the following files named:
  | report.pdf  |
  | summary.csv |
Then the downloaded file should be a zip archive not containing the following files partially named:
  | secret |
  | .env   |

Complete example

Feature: Export downloads

  Scenario: A logged-in user can export the user list as CSV
    Given I am on the homepage
     When I fill in "Username" with "webshipco"
      And I fill in "Password" with "s3cret"
      And I press "Log in"
      And I download the file from the URL "/exports/users.csv"
     Then the downloaded file name should be "users.csv"
      And the downloaded file should contain:
      """
      name,email
      """

  Scenario: The report link serves a PDF
    Given I am on "/reports"
     When I download the file from the link "Download report"
     Then the downloaded file name should contain ".pdf"
      And the downloaded file should contain:
      """
      %PDF-
      """

Tips

  • Downloads are scoped to the scenario: each new download step overwrites the "last download", and assertions always target the most recent one. Download and assert in adjacent steps.
  • Because the URL step reuses the page's request context, log in first (see auth steps) and access-controlled exports just work.
  • To assert a download endpoint's status or headers without saving the file, the API steps group is often a better fit.

Back to all step groups