Skip to content
>_ TrueFileSize.com
··8 min read

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 | ✅ | ✅ | ✅ |

Sample files: PDF · JPG · PNG · MP4 · Corrupted