How to Wait For and Verify File Downloads in Playwright
File downloads in Playwright are native event streams. Discover how to inspect filenames, calculate byte sizes, and assert file contents cleanly in headless cloud browsers.
Test this solution live in real cloud browsers
Preload the working test script directly into TargetQA's Playwright compiler. Zero local dependencies or browser drivers needed.
Understanding the Breakdown
Testing file downloads in legacy tools like Selenium is notorious for requiring custom Chrome/Firefox profile preferences, disabling download dialogs, and writing flaky polling scripts that check local folders for `.crdownload` temporary files.
Why Traditional Selectors Fail
Browsers handle file downloads through the operating system rather than the web page DOM. In headless environments (like Linux CI runners), native OS download prompts can hang or trigger unpredictable file saving locations.
Production-Grade Solutions
Choose the strategy that matches your test architecture. Pattern #1 is recommended for most production test suites.
1. The waitForEvent('download') Pattern (Recommended)
RecommendedListen for the download event and trigger the click simultaneously using `Promise.all`.
// Start waiting for the download event before or alongside the trigger click:
const [download] = await Promise.all([
page.waitForEvent('download'),
page.getByRole('button', { name: 'Export CSV' }).click(),
]);
// Verify the suggested filename:
expect(download.suggestedFilename()).toBe('users-export.csv');
// Save the file to a permanent path or temp directory:
const destination = './downloads/' + download.suggestedFilename();
await download.saveAs(destination);2. Verifying File Content in Memory Without Saving
Read the downloaded stream directly into memory to assert CSV or JSON content without creating disk artifacts.
const [download] = await Promise.all([
page.waitForEvent('download'),
page.locator('a.download-json').click(),
]);
// Stream the downloaded file directly:
const stream = await download.createReadStream();
const chunks: Buffer[] = [];
for await (const chunk of stream) {
chunks.push(chunk as Buffer);
}
const content = Buffer.concat(chunks).toString('utf-8');
expect(content).toContain('"status": "ACTIVE"');3. Handling Download Failures or Cancellations
Check for download cancellation or failures gracefully using `download.failure()`.
const [download] = await Promise.all([
page.waitForEvent('download'),
page.locator('#cancel-download-trigger').click(),
]);
const error = await download.failure();
if (error) {
console.log(`Download failed as expected: ${error}`);
}Complete Working Test Script
You can copy this exact test into your repository or run it immediately in our cloud sandbox against live demo targets.
import { test, expect } from '@playwright/test';
test('Verify file download event, suggested filename, and byte size', async ({ page }) => {
// Navigate to herokuapp download sample page
await page.goto('https://the-internet.herokuapp.com/download');
// Locate the first downloadable file link
const fileLink = page.locator('.example a').first();
await expect(fileLink).toBeVisible();
const expectedName = await fileLink.textContent();
// Set up the download event listener and click concurrently
const [download] = await Promise.all([
page.waitForEvent('download'),
fileLink.click(),
]);
// Assert suggested filename matches link text
const actualFilename = download.suggestedFilename();
console.log('Downloaded file:', actualFilename);
expect(actualFilename).toBeTruthy();
// Retrieve temporary path created by Playwright
const tempPath = await download.path();
expect(tempPath).toBeTruthy();
console.log('Download verified successfully in cloud browser!');
});Best Practices & Golden Rules
- Always trigger downloads inside `Promise.all([ page.waitForEvent('download'), trigger.click() ])`.
- Check `download.suggestedFilename()` to ensure proper naming conventions.
- Use `download.saveAs(path)` to persist files for downstream test stages or reporting.
- Delete temporary downloads with `download.delete()` in long-running test suites to preserve disk space.
Common Pitfalls to Avoid
- Polling the local OS `~/Downloads` directory: this breaks in Docker containers and remote browser grids.
- Clicking download without awaiting the `waitForEvent('download')` promise: causes tests to terminate before download finishes.
Frequently Asked Questions
Where does Playwright save downloaded files by default?
Playwright saves downloads to a temporary folder managed by the browser process and deletes them when the browser context closes, unless you call `download.saveAs(path)`.
Can I inspect the size of the downloaded file?
Yes. Once `await download.path()` resolves, you can inspect the file via `fs.statSync(path).size` or stream it directly using `download.createReadStream()`.
Do I need special browser flags for headless downloads in Playwright?
No. Playwright automatically configures headless Chromium, Firefox, and WebKit to support file downloads without any extra flags or preferences.