HOW TO TEST
How to Test File Upload.
Core Features A complete testing guide for file upload features: valid uploads, type and size validation, special characters in filenames, multiple files, drag-and-drop, progress indicators, preview, download, delete, permission checks, and backend security.
File upload is a high-risk feature combining user experience challenges (progress, error feedback, large file handling) with serious security exposure (malware upload, path traversal, MIME-type spoofing). A weak upload endpoint can be weaponised to host malicious files, overwrite server-side resources, or exhaust storage. This guide covers the full upload surface: valid uploads for all accepted types, type enforcement at MIME and extension levels, size limits, special characters and long filenames, multiple uploads, drag-and-drop, progress and preview, download and delete, permission-scoped access, direct-URL access prevention, and backend API validation that cannot be bypassed by the frontend. All test cases are written for a Cypress/Playwright engineer.
Risks
Malicious file upload bypassing type validation
If type validation relies only on the file extension or the Content-Type header (both attacker-controlled), a renamed .php or .js file can be uploaded and executed server-side if the storage path is web-accessible. Validation must include magic-byte (file signature) inspection.
Path traversal via crafted filename
A filename like '../../etc/passwd' or '../config.js' uploaded without sanitisation can overwrite server files if the upload path is constructed from the original filename. Always generate a server-side UUID filename.
No file size limit — storage exhaustion
An endpoint with no file size limit can be exploited to upload multi-gigabyte files, exhausting disk space, memory, or network bandwidth and taking the service down for all users.
Private uploaded files accessible via guessable direct URL
If uploaded files are stored in a predictable path (e.g. /uploads/{original-filename}) and the directory is publicly accessible, any user can access any other user's uploaded files by guessing or enumerating URLs.
MIME type spoofing — server trusts Content-Type header
An attacker can send a .html file with Content-Type: image/png. If the server stores and serves it with the original MIME type from the header, browsers will execute the HTML as a web page, enabling stored XSS.
Insufficient permissions on file operations
If file download, delete, or metadata endpoints do not verify that the requesting user owns or has access to the file, any authenticated user can download or delete any file by guessing or enumerating its ID.
Files not deleted from storage when the record is deleted
Deleting a file record from the database without also deleting the file from storage (S3, GCS, local disk) leaves orphaned files that accumulate indefinitely, increasing storage costs and retaining data that should be purged.
Test Scenarios
Valid file of each accepted type uploads successfully
CriticalfunctionalFully automatedUpload one file for each accepted type (e.g. PDF, JPEG, PNG, DOCX). Assert each upload completes, the file record is created, and the file can be downloaded back correctly.
Rejected file type returns an error
CriticalnegativeFully automatedUpload a file with a disallowed extension (e.g. .exe, .php, .sh). Assert the server rejects the upload with a 400/415 and a clear error message. Verify the file is NOT stored.
MIME-type spoofed file is rejected
CriticalsecurityFully automatedUpload a file with a valid extension (e.g. .jpg) but with the actual content of a different type (e.g. HTML or PHP). Assert the server inspects the file content (magic bytes) and rejects if the content type does not match the declared extension.
Oversized file is rejected with a size error
CriticalnegativeFully automatedUpload a file larger than the configured limit. Assert the upload is rejected (413 or 400) with a clear error message stating the limit. Assert no partial file is stored.
Zero-byte file is rejected or handled gracefully
Highedge-caseFully automatedUpload a file with 0 bytes. Assert it is either rejected with a meaningful error or handled as a known edge case with a specific message. An empty file accepted silently causes confusion on download.
Files with special characters in the filename are handled safely
CriticalsecurityFully automatedUpload files named '../../../etc/passwd', '<script>.txt', and 'file name with spaces.pdf'. Assert the server sanitises the filename, stores the file safely, and the displayed name is escaped correctly.
Multiple concurrent file uploads succeed
HighfunctionalFully automatedUpload the maximum number of allowed files simultaneously. Assert all files are uploaded, all records are created, and no files are silently dropped.
Drag-and-drop upload works and shows a drop-zone indicator
HighfunctionalFully automatedDrag a valid file over the upload zone. Assert a visible drop-zone indicator appears. Release — assert the file uploads and a progress indicator or completion state is shown.
Upload progress indicator reflects actual upload state
MediumfunctionalFully automatedUpload a large file (minimum 5 MB). Assert a progress indicator appears and advances during upload. Assert it reaches 100% or shows a completion state. Assert it does not get stuck at 99%.
Uploaded file can be downloaded and is byte-identical to the original
CriticalfunctionalFully automatedUpload a file, then download it via the download endpoint. Assert the downloaded file content is byte-identical to the uploaded file (compare SHA-256 hashes). Assert correct Content-Type and Content-Disposition headers.
Deleting a file removes it from storage and the UI
HighfunctionalFully automatedDelete a previously uploaded file via the UI and API. Assert the file no longer appears in the file list. Assert a direct GET request to the file's URL returns 404 or 403. Assert the file is removed from storage.
User cannot access another user's uploaded file
CriticalsecurityFully automatedUser A uploads a private file. User B obtains the file's URL or ID. Assert User B's GET request returns 403 or 404, not the file content.
Upload control is keyboard-accessible and screen-reader announced
HighaccessibilityManual onlyTab to the file input. Assert it has an accessible label. Activate it with Enter/Space, select a file, confirm the filename is announced by screen reader. Assert upload errors are also announced via aria-live.
Detailed Test Cases
Preconditions
- Authenticated user with upload permission
- Test PDF file available (< 1 MB)
- SHA-256 hash of the test PDF pre-computed
Steps
- 1.POST /api/files with multipart form-data: file=test.pdf, Content-Type: application/pdf
- 2.Assert HTTP 201 Created
- 3.Assert response body contains fileId, filename, and url fields
- 4.GET {url} or GET /api/files/{fileId}/download
- 5.Assert HTTP 200
- 6.Assert Content-Type header is 'application/pdf'
- 7.Assert Content-Disposition header contains 'attachment'
- 8.Compute SHA-256 hash of the downloaded bytes
- 9.Assert downloaded hash === original file hash
Expected result
File uploaded, stored, and downloaded with byte-identical content and correct headers.
Test data
- File: test-upload.pdf (< 1 MB)
- SHA-256 must match pre-computed value
Edge Cases
File with a 255-character filename
Most filesystems support up to 255 characters. A filename at exactly this limit should be accepted (or truncated gracefully), not cause a database or filesystem error.
File with no extension
A file with no extension (e.g. 'Makefile', 'LICENSE') should be handled by inspecting content type, not returning an error because no extension is present.
Duplicate filename from the same user
Uploading two files with identical names should either generate unique storage paths automatically (UUID naming) or notify the user of a conflict. Never silently overwrite the first file.
Upload interrupted mid-way (network disconnect)
If the network drops during a large upload, the incomplete file must not be stored or serve partially. The UI should offer a retry option.
Very large image that exceeds the size limit but passes the type check
A valid JPEG of 50 MB should be rejected by the size limit, not passed to the image processing pipeline (resizing, EXIF stripping) before the size check, which could cause memory exhaustion.
File with EXIF GPS data — privacy risk
Images taken on mobile devices often embed GPS coordinates in EXIF metadata. If uploaded images are publicly accessible, they expose the user's location. The application should strip EXIF data on ingest.
Uploading to a shared storage bucket with guessable paths
Files stored in S3 or GCS with paths like /uploads/{original-filename} are guessable. All private files must use UUIDs or pre-signed URLs with short TTLs.
File upload via mobile browser with camera capture
On mobile, the file input can trigger the camera directly. The resulting file may have a generic name ('image.jpg') and vary in size. The upload flow should handle camera captures identically to file-picker uploads.
Re-upload of a previously deleted file
Uploading a file with the same name as a previously deleted file should create a new record with a new ID, not resurrect the deleted record or reuse its storage path.
Automation Ideas
File type validation matrix
Drive a table of {filename, mimeType, fileContent, expectedStatus} through the upload API. Include: valid accepted types, disallowed extensions, MIME-spoofed files, empty files, and oversized files.
Tools: playwright, postman
Path traversal sweep
Submit uploads with a set of traversal payloads as the filename (../../../etc/passwd and URL-encoded variants). Assert no file is stored outside the upload directory. Verify by inspecting the stored path in the response.
Tools: playwright, burp-suite
Permission boundary assertion
Upload a file as User A, extract the fileId, authenticate as User B, and assert GET /api/files/{fileId}/download returns 403. Run as a security regression on every release.
Tools: playwright, cypress
SHA-256 round-trip integrity test
For each file type in the acceptance matrix, compute the SHA-256 hash before upload, upload, download, and assert the hash is identical. Catches silent corruption in storage or CDN transforms.
Tools: playwright, cypress
Concurrent upload race condition test
Fire N concurrent uploads via Promise.all() and assert all succeed. Then fire N+1 (over the limit) and assert the excess returns a 429 or 400. Ensures the concurrency limit is enforced server-side.
Tools: playwright, k6
Drag-and-drop simulation via Playwright
Use Playwright's event dispatching to simulate dragenter, dragover, and drop on the upload zone. Assert the visual state and the resulting file record. Eliminates flaky manual drag-and-drop testing.
Tools: playwright
Security scan with OWASP ZAP
Run OWASP ZAP's active scan against the file upload endpoint with a set of malicious payloads. Integrate into the nightly security build.
Tools: owasp-zap, burp-suite
Accessibility audit on the upload component
Run axe-core against the upload form. Assert the file input has an accessible label, drop-zone has an aria-label, and error messages are associated via aria-describedby.
Tools: axe-core, playwright, pa11y
Common Bugs
File stored despite a server-side validation failure
Type or size validation occurs after the file is written to a temp directory but before the response is sent. On validation failure, the temp file is not cleaned up, accumulating rejected files in storage.
Filename reflected in download Content-Disposition without sanitisation
A filename containing a quote or semicolon (e.g. 'file";name=evil.exe') can break the Content-Disposition header, tricking the browser into downloading the file with a different name.
Upload button enabled again after a failure — no state reset
After a failed upload, the file input still shows the previously selected file. Clicking upload again re-attempts silently, confusing the user who expected a fresh selection.
Progress bar jumps to 100% before upload is confirmed server-side
The progress bar reflects the bytes sent to the network (client-side), not the bytes received and processed by the server. It reaches 100% while the server is still processing, then shows an error — a confusing sequence.
File deleted from the database but not from cloud storage
The DELETE endpoint removes the database record but does not call the cloud storage SDK to delete the underlying blob. The file remains accessible at its direct URL indefinitely.
Multiple file upload loses files on a slow connection
Multiple simultaneous uploads on a slow connection hit a browser connection concurrency limit, causing some uploads to queue and eventually time out. Only some files are stored; no error is shown for the dropped ones.
EXIF GPS metadata not stripped from uploaded images
Images taken on smartphones embed GPS coordinates, device model, and timestamp in EXIF headers. If served directly from storage, any user who downloads the image can extract the original photographer's location.
Impact: Privacy violation — reveals user location data to anyone with access to the file.
File type validated on extension only — executable renamed to .txt
The server checks file.name.endsWith('.txt') and accepts it. An attacker renames 'malware.exe' to 'malware.txt' and uploads it. The file is stored and downloadable, potentially bypassing endpoint security on victim machines.
Error message reveals the server's upload directory path
A validation error or exception stack trace includes the server's filesystem path (e.g. '/var/www/uploads/...'), revealing the directory structure to an attacker.
Useful Tools
End-to-end file upload tests including drag-and-drop simulation, network interception, and SHA-256 integrity checks.
File upload E2E with cy.intercept for simulating upload failures and cy.fixture for test file management.
Intercept and modify upload requests — MIME spoofing, path traversal filenames, and oversized payload testing.
Active security scan of the upload endpoint for injection, traversal, and malicious file acceptance.
WCAG 2.1 AA audit of the upload UI — labels, error announcements, and drop-zone accessibility.
API-level upload tests: type validation bypasses, path traversal filenames, size limit enforcement.
Concurrent upload load tests — verify the server handles simultaneous large uploads without errors.
// Related resources
Glossary terms
- XSS (Cross-Site Scripting)
- OWASP
- DAST (Dynamic Application Security Testing)
- SAST (Static Application Security Testing)
- Penetration Testing
- Fuzzing
- Accessibility
- WCAG
- ARIA Attributes
- Boundary Value Analysis
- Equivalence Partitioning
- Exploratory Testing
- Regression Test
- Smoke Test
- Schema Validation
- Status Code
- MIME Type
- Validation
- Access Control
- Authorization
- Permission
- Negative Testing