15 Days of Playwright - Day 5
File handling is a crucial aspect of modern web applications. From uploading profile pictures to downloading reports, users constantly interact with files.


Jhonatas Matos
File handling is a crucial aspect of modern web applications. From uploading profile pictures to downloading reports, users constantly interact with files. Playwright provides robust APIs to handle both file uploads and downloads seamlessly in your tests.
Today, I will explore how to work with file operations, covering everything from simple uploads to complex download verification.
File Uploads
File uploads are a critical user interaction that require careful testing. Playwright provides multiple approaches to handle different upload scenarios, from simple file inputs to complex drag-and-drop interfaces.
Method 1: Direct Input Setting (Most Common)
Ideal for standard file input elements, when I see a basic <input type="file">
, this is my go-to approach:
// Single file upload
await page.locator('input[type="file"]').setInputFiles('path/to/file.pdf');
// Multiple files upload
await page.locator('input[type="file"]').setInputFiles([
'file1.pdf',
'file2.jpg',
'file3.png'
]);
// Clear selected files
await page.locator('input[type="file"]').setInputFiles([]);
Method 2: File Chooser Dialog Handling
Use this when dealing with custom upload buttons that trigger the native file chooser dialog. This approach is essential for:
- Custom-designed upload buttons (not standard
<input type="file">
) - Applications that use JavaScript to trigger file selection
- Testing the complete user flow including dialog interaction
// Wait for and handle native file chooser
const [fileChooser] = await Promise.all([
page.waitForEvent('filechooser'),
page.locator('#upload-button').click() // Triggers file chooser
]);
await fileChooser.setFiles('my-file.pdf');
Method 3: Dynamic File Creation
Perfect for those times when you need to test without physical files or generate content on the fly. I use this for:
- Creating test files on the fly without maintaining fixture files
- Testing specific file content scenarios
- Avoiding file system dependencies in tests
// Create and upload a file without physical file
await page.locator('input[type="file"]').setInputFiles({
name: 'report.pdf',
mimeType: 'application/pdf',
buffer: Buffer.from('file content')
});
⚠️ File Path Warning: Always use absolute paths or paths relative to your test directory. Relative paths might work differently depending on where tests are executed from. Use path.join(__dirname, 'file.pdf') for reliability.
Want to dive deeper? Check out the File Uploads Guide for advanced techniques and best practices.
Handling File Downloads
Playwright makes download testing straightforward with event-based handling:
Basic Download Capture
// Wait for download and save it
const [download] = await Promise.all([
page.waitForEvent('download'),
page.locator('#download-button').click()
]);
// Save to specific location
await download.saveAs('/path/to/save/report.pdf');
Download Verification
test('should download and verify report content', async ({ page }) => {
const [download] = await Promise.all([
page.waitForEvent('download'),
page.click('#generate-report')
]);
// Verify download properties
expect(download.suggestedFilename()).toBe('financial-report.pdf');
// Read and verify file content
const filePath = await download.path();
const fileContent = await fs.promises.readFile(filePath, 'utf8');
expect(fileContent).toContain('Transaction Summary');
expect(fileContent).toContain('Balance: $1,500.00');
});
Explore more: The Download Handling documentation covers advanced scenarios like progress tracking and error handling.
Real-World Scenarios
Scenario 1: Profile Picture Upload
test('should upload profile picture successfully', async ({ page }) => {
await page.goto('/profile-settings');
// Upload image file
await page.locator('input[type="file"]').setInputFiles('tests/fixtures/avatar.jpg');
// Verify upload preview
await expect(page.locator('.image-preview')).toBeVisible();
await expect(page.locator('.file-name')).toHaveText('avatar.jpg');
// Submit form
await page.click('#save-changes');
// Verify success message
await expect(page.locator('.alert-success'))
.toHaveText('Profile picture updated successfully');
});
Scenario 2: Bulk Document Upload
test('should handle multiple file uploads', async ({ page }) => {
await page.goto('/document-upload');
// Upload multiple documents
await page.locator('#documents-input').setInputFiles([
'tests/fixtures/doc1.pdf',
'tests/fixtures/doc2.pdf',
'tests/fixtures/photo-id.jpg'
]);
// Verify all files are listed
const fileItems = page.locator('.file-list li');
await expect(fileItems).toHaveCount(3);
// Verify individual file names
await expect(fileItems.nth(0)).toContainText('doc1.pdf');
await expect(fileItems.nth(1)).toContainText('doc2.pdf');
await expect(fileItems.nth(2)).toContainText('photo-id.jpg');
});
Scenario 3: Download Verification with Mocking
test('should download report with mocked data', async ({ page }) => {
// Mock API to control download content
await page.route('**/api/report', async route => {
await route.fulfill({
status: 200,
headers: {
'Content-Type': 'application/pdf',
'Content-Disposition': 'attachment; filename="report.pdf"'
},
body: Buffer.from('Mock PDF content for testing')
});
});
const [download] = await Promise.all([
page.waitForEvent('download'),
page.click('#download-report')
]);
// Verify download metadata
expect(download.suggestedFilename()).toBe('report.pdf');
// Verify file content
const filePath = await download.path();
const content = await fs.promises.readFile(filePath, 'utf8');
expect(content).toBe('Mock PDF content for testing');
});
Advanced Techniques
1. Handling Drag-and-Drop Uploads
// Simulate drag-and-drop file upload
await page.dispatchEvent('.drop-zone', 'dragover');
await page.locator('.drop-zone').setInputFiles('file-to-upload.pdf');
await page.dispatchEvent('.drop-zone', 'drop');
2. File Type Validation Testing
test('should reject invalid file types', async ({ page }) => {
await page.goto('/upload');
// Try uploading invalid file type
await page.locator('input[type="file"]').setInputFiles('script.exe');
// Verify error message
await expect(page.locator('.error-message'))
.toHaveText('Executable files are not allowed');
// Verify no files were actually uploaded
await expect(page.locator('.file-list li')).toHaveCount(0);
});
3. Large File Upload Testing
test('should handle large file upload with progress', async ({ page }) => {
// Create a large test file (10MB)
const largeFile = await createTestFile('large-file.bin', 10 * 1024 * 1024);
await page.goto('/upload');
await page.locator('input[type="file"]').setInputFiles(largeFile);
// Verify progress indicator
await expect(page.locator('.progress-bar')).toBeVisible();
// Wait for upload completion
await expect(page.locator('.upload-status'))
.toHaveText('Upload completed', { timeout: 30000 });
});
Practical BugBank Examples
Bank Statement Upload
test('should upload bank statement for verification', async ({ page }) => {
await page.goto('https://bugbank.netlify.app/statement-upload');
// Upload statement PDF
await page.locator('#statement-file').setInputFiles('tests/fixtures/bank-statement.pdf');
// Verify file preview
await expect(page.locator('.file-preview')).toBeVisible();
await expect(page.locator('.file-size')).toContainText('PDF');
// Submit for processing
await page.click('#process-statement');
// Verify processing completion
await expect(page.locator('.verification-status'))
.toHaveText('Statement verified successfully', { timeout: 10000 });
});
Transaction Receipt Download
test('should download transaction receipt', async ({ page }) => {
await page.goto('https://bugbank.netlify.app/transactions');
// Click download button for a specific transaction
const [download] = await Promise.all([
page.waitForEvent('download'),
page.locator('tr:has-text("Transfer") .download-btn').click()
]);
// Verify download properties
expect(download.suggestedFilename()).toMatch(/receipt-\d+\.pdf/);
// Save download
const downloadPath = `./downloads/${download.suggestedFilename()}`;
await download.saveAs(downloadPath);
// Verify file exists and has content
expect(fs.existsSync(downloadPath)).toBe(true);
expect(fs.statSync(downloadPath).size).toBeGreaterThan(0);
});
Best Practices
1. File Management
// Use test-specific directories
const testUploadDir = './test-uploads';
const testDownloadDir = './test-downloads';
// Clean up before tests
beforeEach(async () => {
if (fs.existsSync(testUploadDir)) {
fs.rmSync(testUploadDir, { recursive: true });
}
fs.mkdirSync(testUploadDir, { recursive: true });
});
2. File Path Management
// Use path utilities for cross-platform compatibility
import path from 'path';
const filePath = path.join(__dirname, 'fixtures', 'test-file.pdf');
await page.locator('input[type="file"]').setInputFiles(filePath);
3. Mocking File Responses
// Mock file downloads for consistent testing
await page.route('**/api/export', async route => {
await route.fulfill({
status: 200,
headers: {
'Content-Type': 'application/vnd.ms-excel',
'Content-Disposition': 'attachment; filename="report.xlsx"'
},
body: Buffer.from('mock,excel,content\n1,2,3')
});
});
Debugging Tips
// Enable download logging
page.on('download', download => {
console.log(`Download started: ${download.suggestedFilename()}`);
});
// Monitor file inputs
await page.locator('input[type="file"]').evaluate(input => {
input.addEventListener('change', (e) => {
console.log('Files selected:', e.target.files);
});
});
The best way to learn is by doing! I've included a full test file with all the examples from this article. Clone it, run it, and experiment:
Link to GitHub project
Conclusion
In this guide, I've explored various strategies for testing file uploads and downloads using Playwright. By leveraging the power of Playwright's API, we can create robust tests that simulate real user interactions with file inputs and ensure our applications handle files correctly.
Key takeaways include:
- Utilizing fixtures for consistent test data
- Implementing thorough error handling and validation
- Mocking file responses for reliable testing
- Employing debugging techniques to troubleshoot issues effectively
With these practices in place, you can enhance the reliability and maintainability of your file upload and download tests, ultimately leading to a better user experience.
Thank you for reading and see you in the next lesson! ☕💜