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
| Step | What 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.