Testing File Upload with Playwright — Cross-Browser E2E Guide
Playwright makes cross-browser file upload testing straightforward. This guide shows how to test upload functionality across Chromium, Firefox, and WebKit using real sample files from TrueFileSize.
Prerequisites
- Playwright Test (
npm init playwright@latest) - Node.js 18+
- Sample files from TrueFileSize
Step 1: Set Up Fixtures
Create a setup script that downloads sample files before tests run:
// tests/setup/download-fixtures.ts
import { existsSync, mkdirSync } from 'fs';
import { execSync } from 'child_process';
const FIXTURES_DIR = 'tests/fixtures';
const CDN = 'https://cdn.truefilesize.com';
const FILES = [
{ url: `${CDN}/pdf/sample-1mb.pdf`, name: 'sample-1mb.pdf' },
{ url: `${CDN}/jpg/sample-500kb.jpg`, name: 'sample-500kb.jpg' },
{ url: `${CDN}/png/transparent-logo.png`, name: 'transparent-logo.png' },
{ url: `${CDN}/mp4/sample-5mb.mp4`, name: 'sample-5mb.mp4' },
{ url: `${CDN}/corrupted/sample-corrupt.pdf`, name: 'corrupt.pdf' },
];
if (!existsSync(FIXTURES_DIR)) mkdirSync(FIXTURES_DIR, { recursive: true });
for (const file of FILES) {
const dest = `${FIXTURES_DIR}/${file.name}`;
if (!existsSync(dest)) {
console.log(`Downloading ${file.name}...`);
execSync(`curl -sL -o "${dest}" "${file.url}"`);
}
}
Add to playwright.config.ts:
export default defineConfig({
globalSetup: './tests/setup/download-fixtures.ts',
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
],
});
Step 2: Basic Upload Test (Cross-Browser)
// tests/file-upload.spec.ts
import { test, expect } from '@playwright/test';
import path from 'path';
const fixture = (name: string) => path.join('tests/fixtures', name);
test.describe('File Upload', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/upload');
});
test('uploads a PDF successfully', async ({ page }) => {
await page.setInputFiles('input[type="file"]', fixture('sample-1mb.pdf'));
await expect(page.locator('.file-name')).toContainText('sample-1mb.pdf');
await page.click('button[type="submit"]');
await expect(page.locator('.upload-status')).toContainText('success', {
timeout: 30_000,
});
});
test('shows image preview for JPG upload', async ({ page }) => {
await page.setInputFiles('input[type="file"]', fixture('sample-500kb.jpg'));
const preview = page.locator('.image-preview img');
await expect(preview).toBeVisible();
await expect(preview).toHaveAttribute('src', /^blob:|^data:/);
});
test('uploads transparent PNG', async ({ page }) => {
await page.setInputFiles(
'input[type="file"]',
fixture('transparent-logo.png'),
);
await page.click('button[type="submit"]');
await expect(page.locator('.upload-status')).toContainText('success');
});
});
Step 3: Test File Size Validation
test('rejects oversized files', async ({ page }) => {
await page.setInputFiles('input[type="file"]', fixture('sample-5mb.mp4'));
await expect(page.locator('.error-message')).toContainText('too large');
});
Step 4: Test Drag and Drop
test('accepts drag and drop upload', async ({ page }) => {
const dropzone = page.locator('.dropzone');
// Create a DataTransfer with the file
const [fileChooser] = await Promise.all([
page.waitForEvent('filechooser'),
dropzone.click(),
]);
await fileChooser.setFiles(fixture('sample-1mb.pdf'));
await expect(page.locator('.file-name')).toContainText('sample-1mb.pdf');
});
Step 5: Upload Progress Tracking
test('shows upload progress for large files', async ({ page }) => {
await page.setInputFiles('input[type="file"]', fixture('sample-5mb.mp4'));
await page.click('button[type="submit"]');
// Progress bar should appear and advance
const progress = page.locator('.progress-bar');
await expect(progress).toBeVisible();
// Wait for completion
await expect(page.locator('.upload-status')).toContainText('success', {
timeout: 60_000,
});
});
Step 6: Corrupted File Handling
test('handles corrupted file gracefully', async ({ page }) => {
await page.setInputFiles('input[type="file"]', fixture('corrupt.pdf'));
await page.click('button[type="submit"]');
// Should show user-friendly error
const error = page.locator('.error-message');
await expect(error).toBeVisible();
// No stack traces or raw errors
const text = await error.textContent();
expect(text).not.toMatch(/Error:|stack|undefined/);
});
Cross-Browser Test Matrix
| Test | Chrome | Firefox | Safari | |------|--------|---------|--------| | PDF upload | ✅ | ✅ | ✅ | | Image preview | ✅ | ✅ | ✅ | | Size rejection | ✅ | ✅ | ✅ | | Drag & drop | ✅ | ✅ | ⚠️ Different API | | Progress bar | ✅ | ✅ | ✅ | | Corrupt file | ✅ | ✅ | ✅ |