Wiki

← Tất cả bài wiki

Downloads & Uploads (Tải xuống và tải lên file)

automationplaywright

Downloads & Uploads là hai hướng file trong test: download được bắt bằng page.waitForEvent('download') rồi download.saveAs() lưu về máy để kiểm tra nội dung; upload dùng locator.setInputFiles() cho <input type="file"> hoặc bắt event filechooser khi input bị giấu sau nút bấm custom.

Khái niệm & Bản chất

  • Download: khi trang tải file (click link download, nút export Excel), Playwright phát event 'download' trên page — bạn nhận object Download để lấy tên file, save về đĩa, hoặc đọc stream.
  • Download trong Playwright chạy được cả headless và đi qua cơ chế của browser — khác việc fetch URL bằng axios vì nó mang session/cookie của context, đúng luồng người dùng.
  • Upload: <input type="file"> được điều khiển trực tiếp bằng setInputFiles() — không cần mở file picker của OS (thứ automation không chạm tới).
  • File chooser: nhiều UI giấu input file, chỉ hiện nút đẹp → khi click, browser mở file picker. Playwright bắt qua page.waitForEvent('filechooser') rồi chooser.setFiles().
  • Có thể upload từ buffer trong bộ nhớ ({ name, mimeType, buffer }) — test không cần file thật trên đĩa, rất tiện cho CI.

Cách hoạt động & API chính

  • Download flow: const downloadPromise = page.waitForEvent('download') → trigger click → const download = await downloadPromise → download.suggestedFilename(), download.path(), download.saveAs('/tmp/report.xlsx'), download.createReadStream().
  • download.failure() trả về lỗi nếu download bị hủy (null nếu OK) — nên check trước khi assert file.
  • Upload trực tiếp: page.locator('input[type=file]').setInputFiles('fixtures/avatar.png') — nhận path, mảng path (multi-file), hoặc object buffer.
  • File chooser: const chooserPromise = page.waitForEvent('filechooser') → click nút upload → (await chooserPromise).setFiles(...).
  • setInputFiles([]) clear file đã chọn; expect(input).toHaveValue(/\.png$/) verify tên file đã set.
  • Bật acceptDownloads: true trên context (mặc định đã true từ lâu) — nếu dùng context custom kiểu cũ thì kiểm tra option này.

Ví dụ & Cú pháp thực tế

  • Download và verify file: ``ts test('export báo cáo Excel', async ({ page }) => { await page.goto('/reports'); const downloadPromise = page.waitForEvent('download'); await page.getByRole('button', { name: 'Xuất Excel' }).click(); const download = await downloadPromise; expect(download.suggestedFilename()).toMatch(/\.xlsx$/); expect(await download.failure()).toBeNull(); const path = await download.path(); const stat = await import('node:fs/promises').then(fs => fs.stat(path)); expect(stat.size).toBeGreaterThan(1000); }); ``
  • Lưu file về chỗ mình muốn: ``ts await download.saveAs(test-results/${download.suggestedFilename()}); ``
  • Upload bằng input file: ``ts await page.locator('input[type="file"]#avatar') .setInputFiles('tests/fixtures/avatar.png'); await expect(page.locator('.preview img')).toBeVisible(); ``
  • Upload qua nút custom (input bị giấu): ``ts const chooserPromise = page.waitForEvent('filechooser'); await page.getByRole('button', { name: 'Chọn ảnh' }).click(); const chooser = await chooserPromise; await chooser.setFiles('tests/fixtures/avatar.png'); ``
  • Upload file giả từ buffer + nhiều file: ``ts await page.locator('#files').setInputFiles([ { name: 'a.txt', mimeType: 'text/plain', buffer: Buffer.from('hello') }, { name: 'b.csv', mimeType: 'text/csv', buffer: Buffer.from('1,2,3') }, ]); ``

Best practices & Khi nào dùng

  • Promise trước, click sau cho cả download lẫn filechooser — cùng bệnh race condition như mọi waitForEvent.
  • Verify download ở 3 mức: tên file (suggestedFilename), download không lỗi (failure()), nội dung (đọc file: size, magic bytes, hoặc parse CSV/PDF bằng lib).
  • Dùng download.path() để đọc thẳng từ temp của Playwright, hoặc saveAs khi cần giữ file làm artifact trong test-results/.
  • Upload test nên có sẵn tests/fixtures/ chứa file thật (ảnh nhỏ, CSV, PDF) — commit vào repo để CI chạy giống local.
  • File lớn upload qua mạng chậm: test upload quá trình (progress bar, cancel giữa chừng) nên mock route để kiểm soát tốc độ thay vì dùng file thật vài chục MB.

Gotchas, So sánh & Lưu ý

  • Download trigger bằng target="_blank" + blob URL hoặc JS a.download đều bắt được qua event download — nhưng link download mở sang tab mới (PDF viewer) là navigation, không phải download event; bắt bằng context.waitForEvent('page').
  • suggestedFilename là tên server/CDN đề xuất — có thể khác tên khi save; luôn kiểm tra bằng regex vì server hay gắn timestamp.
  • Remote browser (Browserless, connect qua CDP): file download nằm trên máy remote — download.path() trả path remote, phải dùng saveAs (nó stream về local) hoặc createReadStream().
  • setInputFiles cần input tồn tại trong DOM — input display:none vẫn set được, nhưng input render sau khi click thì phải dùng filechooser.
  • Drag-drop upload (dropzone): Playwright có page.dispatchEvent('drop', { dataTransfer }) nhưng phải build DataTransfer qua evaluateHandle — case này nhiều team chọn mock API upload thay vì test UI drop.
  • Kiểm tra file PDF/Excel thực sự đúng nội dung: dùng pdf-parse/xlsx parse file save về — đừng chỉ check tồn tại, file corrupt vẫn có size > 0.