Claude Code excels at creating Playwright tests for file uploads and downloads because it understands Playwright’s file chooser APIs and download handling mechanisms. When prompted with your upload/download flow, Claude generates tests using setInputFiles(), download event handling, and blob management that require deep API knowledge and proper async handling.
This guide explores how AI assistants can help you create effective Playwright tests for file upload and download flows, with practical examples you can apply immediately.
What Makes an AI Assistant Effective for Playwright Test Generation
Not all AI assistants handle code generation equally. When evaluating AI tools for creating Playwright tests, several capabilities matter most.
An effective AI assistant should understand Playwright’s API thoroughly, including the file chooser APIs, download handling, and blob management. It should generate tests that follow Playwright best practices, such as using proper selectors, handling async operations correctly, and implementing reliable assertions.
Context awareness matters significantly. The best AI assistants can maintain conversation context across multiple turns, allowing you to refine tests iteratively. They should also understand your specific testing framework setup, whether you use Jest, Mocha, or another test runner.
File Upload Testing with Playwright
Playwright provides strong APIs for handling file uploads. The key is using setInputFiles() to programmatically select files for upload input elements.
Here is a practical example of testing a file upload flow:
import { test, expect } from '@playwright/test';
test('should upload a file successfully', async ({ page }) => {
// Navigate to the upload page
await page.goto('/upload');
// Upload a test file
await page.setInputFiles('input[type="file"]', {
name: 'test-document.pdf',
mimeType: 'application/pdf',
buffer: Buffer.from('mock pdf content')
});
// Submit the upload form
await page.click('button[type="submit"]');
// Verify upload success
await expect(page.locator('.upload-success')).toBeVisible();
await expect(page.locator('.file-name')).toContainText('test-document.pdf');
});
AI assistants can generate variations of this test for different scenarios, such as multiple file uploads or different file types. You can ask an AI to modify the test to handle drag-and-drop uploads by adding the appropriate interaction patterns.
File Download Testing with Playwright
Testing downloads requires a different approach. Playwright’s download API allows you to intercept and verify downloaded files.
test('should download a file successfully', async ({ page }) => {
// Start waiting for the download before triggering it
const downloadPromise = page.waitForEvent('download');
// Trigger the download
await page.click('button.download-primary');
// Wait for download to complete
const download = await downloadPromise;
// Verify download details
expect(download.suggestedFilename()).toBe('exported-data.csv');
// Save the file temporarily for verification
const path = await download.path();
expect(path).toBeTruthy();
// Read and verify file contents
const fs = require('fs');
const content = fs.readFileSync(path, 'utf-8');
expect(content).toContain('expected data');
});
AI assistants excel at generating these download tests because they can incorporate error handling and cross-browser considerations that you might otherwise overlook.
Handling Dynamic File Names and Paths
Real-world applications often generate dynamic filenames or use temporary directories. An AI assistant can help you write flexible tests that handle these scenarios.
test('should handle dynamically named downloads', async ({ page }) => {
const downloadPromise = page.waitForEvent('download');
// Trigger download with timestamp in filename
await page.click('button.export-report');
const download = await downloadPromise;
// Verify the filename follows the expected pattern
const filename = download.suggestedFilename();
expect(filename).toMatch(/^report-\d{8}\.pdf$/);
// Handle path in temporary directory
const downloadPath = download.path();
expect(downloadPath).toContain('playwright-downloads');
});
Testing Upload Validation and Error Handling
test coverage includes negative test cases. AI can help generate tests for validation scenarios efficiently.
test('should reject oversized files', async ({ page }) => {
await page.goto('/upload');
// Create a file exceeding the size limit (e.g., 10MB)
const largeBuffer = Buffer.alloc(11 * 1024 * 1024);
await page.setInputFiles('input[type="file"]', {
name: 'huge-file.pdf',
mimeType: 'application/pdf',
buffer: largeBuffer
});
await page.click('button[type="submit"]');
// Verify error message appears
await expect(page.locator('.error-message')).toBeVisible();
await expect(page.locator('.error-message')).toContainText('File too large');
});
test('should reject invalid file types', async ({ page }) => {
await page.goto('/upload');
await page.setInputFiles('input[type="file"]', {
name: 'malicious.exe',
mimeType: 'application/x-executable',
buffer: Buffer.from('malicious content')
});
await page.click('button[type="submit"]');
await expect(page.locator('.error-message')).toContainText('Invalid file type');
});
Using AI to Accelerate Test Development
When working with an AI assistant, provide clear context to get better results. Include your Playwright version, test runner setup, and any specific libraries you use.
Instead of a vague request like “write a download test,” try something more specific: “Write a Playwright test using Jest that downloads a CSV file, verifies the filename matches the pattern report-*.csv, and asserts the file contains at least 10 rows of data.”
The AI can then generate a test tailored to your exact requirements, saving you from adapting generic code.
Best Practices for AI-Generated Tests
AI-generated tests require review and refinement. Always verify the generated code handles edge cases relevant to your application.
Maintain your test files in version control and run them consistently in your CI pipeline. AI assistants can help you add new test cases quickly, but human oversight ensures coverage remains.
Consider creating a library of reusable test utilities for common upload and download scenarios. You can ask AI to help design these utilities based on patterns that emerge across your tests.
Advanced File Upload Scenarios
Real-world applications require testing complex upload scenarios beyond basic file selection:
// Testing chunked/resumable uploads
test('should handle resumable upload with retry', async ({ page }) => {
await page.goto('/upload');
// Create a large file (simulate 100MB upload in chunks)
const largeBuffer = Buffer.alloc(100 * 1024 * 1024);
await page.setInputFiles('input[type="file"]', {
name: 'large-dataset.bin',
mimeType: 'application/octet-stream',
buffer: largeBuffer
});
// Monitor upload progress
const progressUpdates = [];
await page.on('console', msg => {
if (msg.text().includes('upload progress')) {
progressUpdates.push(msg.text());
}
});
// Simulate network interruption and resume
await page.click('button[type="submit"]');
// Wait for initial upload progress
await page.waitForFunction(
() => window.uploadProgress > 25,
{ timeout: 30000 }
);
// Kill network connection
await page.context().setOffline(true);
await page.waitForTimeout(2000);
// Resume upload
await page.context().setOffline(false);
// Verify upload completes
await expect(page.locator('.upload-success')).toBeVisible();
expect(progressUpdates.length).toBeGreaterThan(5);
});
// Testing drag-and-drop uploads
test('should accept files via drag-and-drop', async ({ page }) => {
await page.goto('/upload');
const dropZone = page.locator('[data-drop-zone]');
// Simulate drag-and-drop event
await dropZone.evaluate((element) => {
const event = new DragEvent('drop', {
bubbles: true,
cancelable: true,
dataTransfer: new DataTransfer()
});
element.dispatchEvent(event);
});
// Attach file during drop event
await page.setInputFiles('input[type="file"]', {
name: 'drag-and-drop-file.pdf',
mimeType: 'application/pdf',
buffer: Buffer.from('PDF content')
});
await expect(page.locator('.file-name')).toContainText('drag-and-drop-file');
});
// Testing multiple file uploads with ordering
test('should maintain file order in multi-file upload', async ({ page }) => {
await page.goto('/upload');
const files = [
{ name: 'first.txt', buffer: Buffer.from('First file') },
{ name: 'second.txt', buffer: Buffer.from('Second file') },
{ name: 'third.txt', buffer: Buffer.from('Third file') }
];
// Upload all files
await page.setInputFiles('input[type="file"]', files);
// Verify order in UI
const fileList = await page.locator('.file-list li');
const count = await fileList.count();
for (let i = 0; i < count; i++) {
const text = await fileList.nth(i).textContent();
expect(text).toContain(files[i].name);
}
});
Testing Upload Security and Validation
Security testing is critical for file upload functionality:
// Testing security boundaries
test('should sanitize uploaded filenames', async ({ page }) => {
await page.goto('/upload');
// Attempt path traversal in filename
await page.setInputFiles('input[type="file"]', {
name: '../../../etc/passwd',
mimeType: 'text/plain',
buffer: Buffer.from('malicious')
});
await page.click('button[type="submit"]');
// Verify filename is sanitized in the response
const response = await page.waitForResponse(
response => response.url().includes('/api/upload') && response.status() === 200
);
const data = await response.json();
expect(data.filename).not.toContain('..');
expect(data.filename).not.toContain('/');
});
test('should enforce MIME type restrictions', async ({ page }) => {
await page.goto('/upload');
// Attempt to upload executable with fake extension
const maliciousFile = {
name: 'document.pdf',
mimeType: 'application/x-executable',
buffer: Buffer.from('MZ\x90\x00\x03') // EXE header
};
await page.setInputFiles('input[type="file"]', maliciousFile);
await page.click('button[type="submit"]');
// Verify rejection
await expect(page.locator('.error-message')).toContainText(
'File type not allowed'
);
});
test('should rate limit uploads per user', async ({ page }) => {
await page.goto('/upload');
// Attempt rapid sequential uploads
const uploadPromises = [];
for (let i = 0; i < 50; i++) {
await page.setInputFiles('input[type="file"]', {
name: `file${i}.txt`,
mimeType: 'text/plain',
buffer: Buffer.from('test content')
});
uploadPromises.push(
page.click('button[type="submit"]')
);
}
const results = await Promise.allSettled(uploadPromises);
// Expect rate limiting to trigger
const rejected = results.filter(r => r.status === 'rejected');
expect(rejected.length).toBeGreaterThan(0);
});
Performance and Stress Testing for Uploads
Test upload performance under various conditions:
// Measure upload speed and resource usage
test('upload performance benchmarking', async ({ page }) => {
await page.goto('/upload');
const fileSizes = [
{ size: 1024 * 1024, name: '1MB' },
{ size: 10 * 1024 * 1024, name: '10MB' },
{ size: 50 * 1024 * 1024, name: '50MB' }
];
const benchmarks = [];
for (const { size, name } of fileSizes) {
const buffer = Buffer.alloc(size);
const startTime = Date.now();
const startMetrics = await page.evaluate(() => ({
memory: performance.memory?.usedJSHeapSize || 0,
time: performance.now()
}));
await page.setInputFiles('input[type="file"]', {
name: `benchmark-${name}.bin`,
mimeType: 'application/octet-stream',
buffer
});
await page.click('button[type="submit"]');
await expect(page.locator('.upload-success')).toBeVisible();
const duration = Date.now() - startTime;
const endMetrics = await page.evaluate(() => ({
memory: performance.memory?.usedJSHeapSize || 0,
time: performance.now()
}));
benchmarks.push({
fileSize: name,
duration: duration,
memoryIncrease: (endMetrics.memory - startMetrics.memory) / 1024 / 1024,
throughput: (size / 1024 / 1024) / (duration / 1000)
});
}
console.table(benchmarks);
});
Integrating with CI/CD Pipelines
Ensure upload tests run reliably in CI environments:
// Configuration for CI-compatible file uploads
const config = {
// Use memory-based file system in CI
uploadDir: process.env.CI
? '/tmp/uploads'
: './test-uploads',
// Disable browser cache in CI
launchOptions: process.env.CI ? {
args: [
'--disable-blink-features=AutomationControlled',
'--no-first-run',
'--no-default-browser-check'
]
} : {},
// Timeout adjustments for slower CI runners
timeout: process.env.CI ? 60000 : 30000
};
test.describe('File upload tests (CI-compatible)', () => {
test.beforeEach(async ({ page }) => {
// Create temporary upload directory if needed
if (!fs.existsSync(config.uploadDir)) {
fs.mkdirSync(config.uploadDir, { recursive: true });
}
});
test('upload works in CI environment', async ({ page }) => {
await page.goto('/upload');
await page.setInputFiles('input[type="file"]', {
name: 'ci-test.txt',
mimeType: 'text/plain',
buffer: Buffer.from('CI test content')
});
await page.click('button[type="submit"]');
await expect(page.locator('.upload-success')).toBeVisible({ timeout: config.timeout });
});
});
Related Articles
- Best AI Assistant for Creating Playwright Tests for Multi
- Best AI Assistant for Creating Playwright Tests for Table
- Best AI Assistant for Writing Playwright Tests
- Best AI Assistant for Creating Jest Tests That Verify Error
- AI Tools for Writing Playwright Tests That Verify Accessibil
Built by theluckystrike — More at zovo.one