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 objectDownloadđể 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ằngsetInputFiles()— 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ồichooser.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: truetrê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ả
downloadlẫnfilechooser— 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ặcsaveAskhi cần giữ file làm artifact trongtest-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 JSa.downloadđều bắt được qua eventdownload— nhưng link download mở sang tab mới (PDF viewer) là navigation, không phải download event; bắt bằngcontext.waitForEvent('page'). suggestedFilenamelà 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ùngsaveAs(nó stream về local) hoặccreateReadStream(). setInputFilescần input tồn tại trong DOM — inputdisplay:nonevẫn set được, nhưng input render sau khi click thì phải dùngfilechooser.- Drag-drop upload (dropzone): Playwright có
page.dispatchEvent('drop', { dataTransfer })nhưng phải buildDataTransferquaevaluateHandle— 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/xlsxparse file save về — đừng chỉ check tồn tại, file corrupt vẫn có size > 0.
