IKivan-kozenko -aqa
Try yourself as a QA tester
All topics·Intermediate·21 / 27

Downloads

If your app has an Export CSV button, you need to test that the file actually downloads and contains the right data. Playwright intercepts downloads before they hit the filesystem.

Intercept and verify a download

The pattern is the same as waitForResponse — set up the promise BEFORE the click, then await it after. If you click first and then call waitForEvent('download'), the download event may fire before you start listening and you'll miss it.

The Download object gives you the filename, a path to the temp file, and methods to save it or read its content.

ts
test('export orders as CSV', async ({ page }) => {
  await page.goto('/orders')

  // Встановлюємо очікування ДО кліку
  const downloadPromise = page.waitForEvent('download')

  await page.getByRole('button', { name: 'Export CSV' }).click()

  // Чекаємо завантаження
  const download = await downloadPromise

  // Перевіряємо ім'я файлу
  expect(download.suggestedFilename()).toMatch(/orders-d{4}-d{2}-d{2}.csv/)

  // Зберегти куди треба
  await download.saveAs('/tmp/test-export.csv')
})

Read the downloaded file content

After intercepting the download, you can read the file content to assert on what's actually inside. download.path() returns the path to the temp file Playwright saved. Then use Node.js fs to read it.

ts
import fs from 'fs'

test('exported CSV contains correct order data', async ({ page }) => {
  await page.goto('/orders')

  // Фільтруємо по статусу перед експортом
  await page.getByRole('combobox', { name: 'Status' }).selectOption('pending')

  const downloadPromise = page.waitForEvent('download')
  await page.getByRole('button', { name: 'Export CSV' }).click()
  const download = await downloadPromise

  // Читаємо вміст
  const filePath = await download.path()
  const content = fs.readFileSync(filePath!, 'utf-8')

  // Перевіряємо структуру CSV
  const lines = content.split('\n')
  expect(lines[0]).toContain('Order ID,Customer,Status,Total')

  // Всі рядки мають статус pending
  for (const line of lines.slice(1).filter(Boolean)) {
    expect(line).toContain('pending')
  }
})

Handle multiple downloads

If one action triggers multiple downloads (a zip archive plus a manifest, for example), or if downloads happen at unpredictable moments, use the event listener pattern instead of waitForEvent.

ts
test('bulk export downloads all files', async ({ page }) => {
  await page.goto('/reports')

  const downloads: string[] = []
  page.on('download', async download => {
    downloads.push(download.suggestedFilename())
  })

  await page.getByRole('button', { name: 'Export all reports' }).click()

  // Чекаємо поки завантаження завершаться
  await expect.poll(() => downloads.length, { timeout: 10000 }).toBe(3)

  expect(downloads).toContain('orders.csv')
  expect(downloads).toContain('customers.csv')
  expect(downloads).toContain('summary.pdf')
})