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.

13
scenarios
9
test cases
14 min
read
intermediate4–6 hours (full suite); 1 hour (smoke pass covering happy path + type validation + oversized + permission check) testingQA engineersSDETsSecurity engineers

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 automated

Upload 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 automated

Upload 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 automated

Upload 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 automated

Upload 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 automated

Upload 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 automated

Upload 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 automated

Upload 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 automated

Drag 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 automated

Upload 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 automated

Upload 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 automated

Delete 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 automated

User 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 only

Tab 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. 1.POST /api/files with multipart form-data: file=test.pdf, Content-Type: application/pdf
  2. 2.Assert HTTP 201 Created
  3. 3.Assert response body contains fileId, filename, and url fields
  4. 4.GET {url} or GET /api/files/{fileId}/download
  5. 5.Assert HTTP 200
  6. 6.Assert Content-Type header is 'application/pdf'
  7. 7.Assert Content-Disposition header contains 'attachment'
  8. 8.Compute SHA-256 hash of the downloaded bytes
  9. 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

Playwright

End-to-end file upload tests including drag-and-drop simulation, network interception, and SHA-256 integrity checks.

Cypress

File upload E2E with cy.intercept for simulating upload failures and cy.fixture for test file management.

Burp Suite

Intercept and modify upload requests — MIME spoofing, path traversal filenames, and oversized payload testing.

OWASP ZAP

Active security scan of the upload endpoint for injection, traversal, and malicious file acceptance.

axe-core

WCAG 2.1 AA audit of the upload UI — labels, error announcements, and drop-zone accessibility.

Postman

API-level upload tests: type validation bypasses, path traversal filenames, size limit enforcement.

k6

Concurrent upload load tests — verify the server handles simultaneous large uploads without errors.

Practice this → Try it hands-on in the Buggy Web App.