How to Handle File Upload in Selenium and Playwright
Practice file upload automation — single file, multiple files, drag and drop, type restrictions, size validation, hidden inputs, and upload progress in Playwright, Selenium & Cypress.
Interactive Scenarios
Inner <span> has no data-testid. Target via [data-testid="fu-filename-display"] span[role="status"].
Automation tip: target [data-testid="fu-drop-input"] inside the zone and use setInputFiles().
Input has accept="image/*". Upload a non-image file to trigger the error.
Max size: 2 MB. Error paragraph has no data-testid — locate via .error-msg class inside the panel.
The styled button triggers a visually hidden input[type="file"]. The input has no data-testid. Target via: [data-testid="fu-hidden-zone"] input[type="file"].
Progress bar has no data-testid. Use getByRole('progressbar') to locate it.
| ID | Scenario | Type | Priority | |
|---|---|---|---|---|
| FU_001 | Upload a single file via native file input Expected: File input reflects the selected file; file name is displayed. ✅ positivehigh | positive | high | |
| FU_002 | Upload multiple files via multi-file input Expected: All selected files are reflected; count matches number of files set. ✅ positivehigh | positive | high | |
| FU_003 | Clear previously selected file Expected: File input is reset; display reverts to empty state. ✅ positivemedium | positive | medium | |
| FU_004 | Assert file name displayed after selection (no testid on label) Expected: Filename span inside fu-filename-display shows selected file name. ✅ positivehigh | positive | high | |
| FU_005 | Assert accept attribute on type-restricted input Expected: Input has accept='image/*' attribute. ✅ positivemedium | positive | medium | |
| FU_006 | Upload invalid file type triggers error message Expected: Error element fu-type-error is visible with descriptive text. ❌ negativehigh | negative | high | |
| FU_007 | Upload file within size limit succeeds Expected: No error shown; success state displayed. ✅ positivehigh | positive | high | |
| FU_008 | Upload file exceeding size limit shows error (no testid on error) Expected: Error paragraph inside fu-size-panel is visible. ❌ negativehigh | negative | high | |
| FU_009 | Drop zone accepts dragover and shows active state Expected: Drop zone has active/highlight class while dragging over. ✅ positivemedium | positive | medium | |
| FU_010 | File input inside drop zone can be targeted directly Expected: Hidden file input inside drop zone accepts setInputFiles. ✅ positivehigh | positive | high | |
| FU_011 | Custom button triggers visually hidden file input Expected: File is selected despite input being hidden. ✅ positivehigh | positive | high | |
| FU_012 | Upload progress bar role is assertable via ARIA Expected: Progressbar role is present; aria-valuenow reaches 100. ✅ positivehigh | positive | high | |
| FU_013 | Progress bar disappears after upload completes Expected: Progressbar is detached/hidden; success state shows. ✅ positivemedium | positive | medium | |
| FU_014 | No file selected — upload button remains disabled Expected: fu-upload-btn is disabled when no file is set. ⚠️ edgemedium | edge | medium |
File upload inputs are among the most common — and most misunderstood — elements to automate. Native <input type='file'> is handled differently by each framework: Playwright uses setInputFiles and bypasses the OS dialog entirely; Selenium uses sendKeys with the absolute file path; Cypress uses selectFile. Drag-and-drop zones and hidden inputs require extra care.
setInputFiles (Playwright) and sendKeys (Selenium) both write a file path directly to the file input without triggering the OS dialog. This is the preferred approach in CI environments where no OS dialog can appear. Pass an array to set multiple files. Pass an empty array to clear the selection.
// Upload a single file
await page.getByTestId('fu-single-input').setInputFiles('tests/fixtures/sample.pdf');
// Upload multiple files
await page.getByTestId('fu-multi-input').setInputFiles([
'tests/fixtures/invoice.pdf',
'tests/fixtures/photo.png',
]);
// Clear file selection
await page.getByTestId('fu-single-input').setInputFiles([]);
// Assert file name displayed
await expect(page.getByTestId('fu-filename-display').getByRole('status'))
.toContainText('sample.pdf');Drag-and-drop upload zones are typically built as a div with dragover and drop event listeners that read e.dataTransfer.files. In Playwright, the easiest approach is to find the hidden <input type='file'> inside the zone and use setInputFiles. Cypress provides selectFile with { action: 'drag-drop' }. In Selenium you need to use Robot or JavascriptExecutor to construct and dispatch the DataTransfer event.
// Easiest: target the hidden input inside the drop zone
const dropInput = page.locator('[data-testid="fu-drop-zone"] input[type="file"]');
await dropInput.setInputFiles('tests/fixtures/sample.pdf');
// XPath alternative for the hidden input
// //*[@data-testid="fu-drop-zone"]//input[@type="file"]
// Full drag-and-drop simulation via DataTransfer
const dataTransfer = await page.evaluateHandle(() => new DataTransfer());
await page.dispatchEvent('[data-testid="fu-drop-zone"]', 'dragover', { dataTransfer });
await page.dispatchEvent('[data-testid="fu-drop-zone"]', 'drop', { dataTransfer });Client-side validation (type restrictions, size limits) happens in JavaScript before any network call. Assert the error message element after setting an invalid file. Locate error elements via the parent container's data-testid when no direct testid exists on the error itself.
// Assert file type validation error
await page.getByTestId('fu-type-input').setInputFiles({
name: 'document.pdf',
mimeType: 'application/pdf',
buffer: Buffer.from('fake-content'),
});
await expect(page.getByTestId('fu-type-error')).toBeVisible();
await expect(page.getByTestId('fu-type-error')).toContainText('image');
// Assert size error (no testid — scope from parent)
const panel = page.getByTestId('fu-size-panel');
await expect(panel.locator('.error-msg')).toBeVisible();
await expect(panel.locator('.error-msg')).toContainText('2 MB');
// XPath for error without testid
// //*[@data-testid="fu-size-panel"]//p[contains(@class,"error-msg")]| Action | Selenium | Playwright JS | Playwright PY | Cypress |
|---|---|---|---|---|
| Upload single file | element.sendKeys('/abs/path') | setInputFiles('path') | set_input_files('path') | .selectFile('fixtures/f') |
| Upload multiple files | sendKeys('a\nb') or loop | setInputFiles(['a','b']) | set_input_files(['a','b']) | .selectFile(['a','b']) |
| Clear file selection | JS: input.value='' | setInputFiles([]) | set_input_files([]) | .invoke('val','') |
| Hidden input | JS removeStyle + sendKeys | setInputFiles() (works hidden) | set_input_files() (works hidden) | .selectFile(…, { force: true }) |
| Drag-and-drop zone | JS DataTransfer dispatch | dispatchEvent('drop', dt) | dispatch_event('drop', dt) | .selectFile(…, { action: 'drag-drop' }) |