File Upload Testing Checklist (2026)
File Upload Testing Checklist (2026) provides a concrete, step‑by‑step matrix that teams can use to verify every aspect of a file upload feature from basic success paths to rare failure modes. The che
File Upload Testing Checklist (2026) provides a concrete, step‑by‑step matrix that teams can use to verify every aspect of a file upload feature from basic success paths to rare failure modes. The checklist below is organized into logical testing areas, each with pass criteria, real‑world examples, and guidance on both manual and automated execution. By following this guide you can catch defects early, ensure compliance with accessibility and security standards, and generate reliable regression scripts that survive platform updates.
Happy Path File Upload Testing Checklist
The happy path validates that a user can successfully select, upload, and confirm a file under normal conditions. This area forms the baseline for all other tests; any failure here blocks further progress.
Core Success Criteria
- The upload control accepts the file type(s) declared in the specification.
- After selection, the file name (or a truncated version) appears in the UI.
- The upload initiates without requiring a page reload (if using AJAX/fetch) or completes a full form submit as intended.
- A success indicator (toast, inline message, or progress bar reaching 100 %) is shown.
- The uploaded file is stored in the expected location (e.g.,
/uploads/2026/09/24/document.pdf) with correct permissions. - A server‑side response returns HTTP 200 (or 201) with a JSON payload containing at least an identifier and a download URL.
- The client can subsequently download or preview the file using the provided URL without additional authentication steps.
Manual Test Steps
- Open the page containing the upload widget.
- Click the “Choose File” button and select a valid file (e.g., a 500 KB PDF).
- Observe the UI update: file name displayed, progress indicator starts.
- Wait for the upload to finish and verify the success message.
- Use the browser’s network tab to confirm the request payload includes
Content‑Disposition: form-data; name="file"; filename="document.pdf". - Check the server storage location for the file and verify its size matches the source.
- Attempt to download the file via the provided URL and ensure it opens correctly.
Automated Test Snippet (Playwright)
const { test, expect } = require('@playwright/test');
test('happy path upload of PDF', async ({ page }) => {
await page.goto('https://example.com/upload');
const fileChooserPromise = page.waitForEvent('filechooser');
await page.click('input[type="file"]');
const fileChooser = await fileChooserPromise;
await fileChooser.setFile('tests/fixtures/sample.pdf');
await page.click('button#upload-btn');
// Success toast
await expect(page.locator('.toast-success')).toContainText('Upload complete');
// Progress bar disappears
await expect(page.locator('.progress-bar')).toBeHidden();
// Verify network response
const [response] = await Promise.all([
page.waitForResponse(resp => resp.url().includes('/api/upload') && resp.status() === 200),
page.waitForTimeout(500) // small buffer for UI update
]);
const json = await response.json();
expect(json).toHaveProperty('fileId');
expect(json).toHaveProperty('downloadUrl');
// Download and validate
await page.goto(json.downloadUrl);
await expect(page).toHaveURL(/.*\/sample\.pdf$/);
});
*Pass*: All assertions succeed; the file is stored and retrievable.
Test Matrix for Happy Path Variations
| File Type | Size | Expected UI | Pass Criteria |
|---|---|---|---|
| 100 KB | Name shown, progress bar | HTTP 200, file stored | |
| JPEG | 2 MB | Thumbnail preview | HTTP 200, correct MIME |
| ZIP | 10 MB | No preview, name shown | HTTP 200, extracted? (if applicable) |
| TXT | 500 B | Name shown | HTTP 200, file readable |
*Pass*: Each row must meet the pass criteria; any deviation flags a defect.
Error Handling and Validation Checklist
Error handling ensures the upload component gracefully rejects invalid input and communicates the problem to the user. This area often uncovers missing client‑side validation, misleading messages, or server‑side crashes.
Validation Rules to Test
- File type restrictions: Reject files with extensions not in the allowlist (e.g.,
.exe,.bat). - MIME type verification: Reject files whose actual content type differs from the extension (e.g., rename
.pngto.jpgbut keep PNG data). - Size limits: Enforce both minimum and maximum sizes; show a clear error when exceeded.
- Empty selection: Detect when the user opens the picker but cancels or selects zero‑byte file.
- Duplicate filenames: Decide whether to allow overwriting, rename automatically, or block with a conflict message.
- Virus‑scan integration: If applicable, confirm that infected files are blocked and a security warning appears.
Pass Criteria for Negative Tests
- The UI displays an inline error message adjacent to the upload control (not a generic alert).
- The message text matches the configured validation rule (e.g., “Only PDF files under 5 MB are allowed.”).
- No network request is sent to the server for invalid client‑side checks (saves bandwidth and prevents unnecessary load).
- For server‑side rejections, the response returns HTTP 400 with a JSON error object containing
codeandmessage. - The upload control remains interactive after an error, allowing the user to correct the selection.
Manual Test Steps (Size Limit Example)
- Set the server‑side max size to 2 MB.
- Choose a 2.5 MB PDF file.
- Click upload.
- Verify that the upload button stays disabled or shows an error instantly (client‑side) or that after a brief wait a toast appears: “File exceeds the 2 MB limit.”
- Confirm that no
POST /api/uploadrequest appears in the network log (if client‑side) or that a 400 response is received (if server‑side).
Automated Test Snippet (Cypress)
describe('File upload validation', () => {
it('rejects oversized file', () => {
cy.visit('/upload');
cy.get('input[type="file"]').attachFile('largeFile.pdf'); // 3 MB fixture
cy.contains('File exceeds the 2 MB limit').should('be.visible');
cy.request({
method: 'POST',
url: '/api/upload',
failOnStatusCode: false
}).its('status').should('eq', 400);
});
});
*Pass*: The error message is visible and the server returns 400.
Common Pitfalls Table
| Symptom | Likely Cause | Fix |
|---|---|---|
| Success toast appears for a blocked .exe | Client‑side validation missing | Add extension check before FormData creation |
| Server returns 500 when file is 0 bytes | No guard against empty file | Return 400 with “File must contain data” |
| Error message disappears after 2 seconds | Auto‑dismiss toast too aggressive | Keep error visible until user action |
| Duplicate file allowed overwriting silently | No conflict resolution | Implement “file‑exists” check and prompt |
*Pass*: Each row’s fix eliminates the symptom in a regression run.
Edge Cases and Boundary Conditions Checklist
Edge cases push the upload component beyond normal usage, exposing issues with encoding, special characters, concurrent actions, and browser quirks. These defects often surface only under load or with specific user agents.
Filename and Encoding Tests
- Unicode filenames: Verify that names containing emojis, accented characters, or non‑Latin scripts are preserved and correctly URL‑encoded.
- Leading/trailing spaces: Ensure spaces are not trimmed unintentionally, which could break downstream processing.
- Very long filenames: Test names approaching filesystem limits (e.g., 255 bytes) and beyond to confirm truncation or rejection.
- Control characters: Names containing
\n,\t, or null bytes should be sanitized or rejected. - Double extensions: Files like
image.jpg.phpshould be evaluated based on actual MIME, not just the last extension.
Concurrency and Race Conditions
- Simultaneous uploads: Open two file pickers in separate tabs or windows and upload large files concurrently; verify that each upload gets a unique identifier and storage path.
- Rapid retries: Simulate a user clicking upload repeatedly before the previous request finishes; ensure the UI queues or disables further attempts.
- Network interruption: Kill the upload mid‑stream (e.g., using Chrome DevTools → Network → throttling → offline) and confirm that a retry mechanism or clear error appears.
Browser‑Specific Quirks
- Safari on iOS: The native file picker may return a blob URL instead of a raw file; confirm that the
Fileobject is correctly handled. - Android WebView: Some versions fail to send the
filenameparameter in multipart/form‑data; validate that the server still extracts the name fromContent‑Disposition. - IE11 (if still supported): Ensure that
FormData.append('file', blob, filename)works; otherwise fall back toXMLHttpRequestwith manual header setting.
Pass Criteria for Edge Cases
- The uploaded file’s stored name matches the original after URL‑decoding (no loss of characters).
- The system returns a deterministic error code for invalid filenames (e.g., 422 Unprocessable Entity) rather than a generic 500.
- Concurrent uploads do not overwrite each other; each receives a distinct
fileId. - After a network interruption, the user can retry without re‑selecting the file (if client‑side caching is used) or receives a clear prompt to reselect.
- No JavaScript exceptions appear in the console for any of the above scenarios.
Manual Test Steps (Unicode Filename)
- Create a file named
📊_报告_2026.pdf(contains emoji and Chinese characters). - Upload via the widget.
- After success, locate the file in storage; confirm the name is exactly
📊_报告_2026.pdf(or URL‑encoded equivalent). - Attempt to download using the provided link; verify the downloaded file opens and displays correctly.
Automated Test Snippet (Appium Android)
@Test
public void uploadUnicodeFilename() throws Exception {
driver.get("https://example.com/upload");
WebElement input = driver.findElement(By.cssSelector("input[type='file']"));
// Push file to device
String devicePath = "/sdcard/Download/📊_报告_2026.pdf";
driver.pushFile(devicePath, new File("src/test/resources/📊_报告_2026.pdf"));
// Use sendKeys with the absolute path
input.sendKeys(devicePath);
driver.findElement(By.id("upload-btn")).click();
// Wait for success toast
new WebDriverWait(driver, Duration.ofSeconds(10))
.until(ExpectedConditions.visibilityOfElementLocated(By.className("toast-success")));
// Verify stored name via API
String fileId = driver.findElement(By.id("file-id")).getText();
Response resp = given()
.queryParam("id", fileId)
.when()
.get("/api/file/meta");
Assert.assertEquals(resp.jsonPath().getString("originalName"), "📊_报告_2026.pdf");
}
*Pass*: The stored original name matches the Unicode source.
Edge Case Table
| Edge Case | Test Data | Expected Result |
|---|---|---|
| Filename with emoji | 😀_test.txt | Stored name unchanged, downloadable |
| Filename 260 chars | a…a.txt (260) | Either truncated to 255 with notice or rejected (422) |
| File with LF in name | line\n.txt | Rejected or name sanitized (LF removed) |
| Simultaneous uploads (2×) | Two 5 MB PDFs started within 200 ms | Two distinct IDs, both succeed |
| Network drop‑off | Kill network at 50 % | Clear retry/error, no corrupted file |
*Pass*: Each row meets the expected result.
Accessibility Testing for File Upload Controls
Accessibility ensures that users relying on assistive technology (screen readers, keyboard navigation, voice control) can perceive, operate, and understand the upload feature. Overlooking accessibility can lead to legal risk and exclude a significant user base.
WCAG Success Criteria Relevant to Uploads
- 1.3.1 Info and Relationships: The upload button must have an accessible name that conveys its purpose (e.g., “Attach file”).
- 2.1.1 Keyboard: All controls must be reachable and operable via Tab/Shift+Tab and activatable with Enter or Space.
- 2.4.7 Focus Visible: Keyboard focus must be visibly indicated when navigating to the upload input or button.
- 3.3.2 Labels or Instructions: Instructions about allowed file types, size limits, and drag‑and‑drop area must be programmatically associated.
- 4.1.2 Name, Role, Value: Custom widgets (e.g., drag‑and‑drop zone built with ) must expose correct role (
buttonorlink) and state via ARIA.Manual Accessibility Checks
- Screen Reader: Using NVDA or VoiceOver, navigate to the upload area. Verify that the announcement includes the button label, allowed formats, and size limit.
- Keyboard Only: Tab to the upload control; pressing Enter/Space should open the file picker. Ensure that after file selection, focus moves to a logical next element (e.g., the upload button or a status message).
- High Contrast Mode: Switch OS to high contrast; confirm that the drag‑and‑drop zone border and text remain distinguishable.
- Reduced Motion: If the upload includes animations (e.g., spinner), verify that they respect the
prefers-reduced-motionmedia query. - Voice Control: Issue commands like “Click attach file” and “Choose file” to ensure the UI responds correctly.
Automated Accessibility Test (axe‑core with Playwright)
const { test, expect } = require('@playwright/test'); const { injectAxe, checkA11y } = require('jest-axe'); test.beforeEach(async ({ page }) => { await page.goto('/upload'); await injectAxe(page); }); test('upload component passes WCAG 2.1 AA', async ({ page }) => { const accessibilitySnapshot = await checkA11y(page, { rules: [ { id: 'label', enabled: true }, { id: 'color-contrast', enabled: true }, { id: 'keyboard', enabled: true }, { id: 'aria-allowed-attr', enabled: true } ] }); expect(accessibilitySnapshot.violations).toHaveLength(0); });*Pass*: No violations reported; any violation must be fixed before release.
Common Accessibility Issues Table
Issue WCAG Criterion Remediation Upload button uses only an icon without aria-label 1.3.1, 4.1.2 Add aria-label="Attach file"or visually hidden textDrag‑and‑drop zone is a lacking role4.1.2 Add role="button"andtabindex="0"Error message appears as a tooltip not announced 3.3.2, 4.1.2 Use aria-live="assertive"on the message containerFocus outline removed via CSS outline:none2.4.7 Provide a custom visible focus style File size limit conveyed only via placeholder text 3.3.2 Associate helper text with aria-describedbyon the input*Pass*: Each remediation eliminates the corresponding violation.
Security and Privacy Testing for File Uploads
File upload is a common attack vector; insufficient validation can lead to malware distribution, server compromise, or data leakage. This section enumerates security‑focused test items that should be part of any release checklist.
Threat Model Overview
- File‑type spoofing: Attacker renames a malicious script to an allowed extension.
- Path traversal: Filename includes
../sequences to write outside the intended directory. - Overflow / DoS: Extremely large files or many simultaneous uploads exhaust disk space or memory.
- Server‑side injection: Uploaded content is later executed (e.g., PHP, JSP) if stored in a web‑accessible folder.
- Privacy leak: Metadata (EXIF, embedded scripts) exposes personal data; or the upload endpoint returns internal paths in error messages.
- Insufficient authorization: Unauthenticated users can upload, or uploaded files are accessible without proper access controls.
Pass Criteria for Security Tests
- The server rejects any file whose MIME type does not match the allowlist, regardless of extension.
- Filenames are sanitized: path separators stripped, leading/trailing spaces removed, and any
..or/sequences neutralized. - Uploaded files are stored outside the web root or served only through a signed URL mechanism with expiration.
- Error messages never disclose stack traces, internal file paths, or database details.
- The upload endpoint enforces authentication and checks the user’s permission to upload to the target folder.
- Virus‑scan integration (if used) returns a clean result before the file is made available; infected files trigger a quarantine workflow and user notification.
- Rate limiting prevents a single IP from exceeding a configurable number of uploads per minute (e.g., 10/min).
Manual Security Test Steps (Path Traversal)
- Attempt to upload a file named
../../etc/passwd. - Observe the server response: it should return HTTP 400 with a message like “Invalid filename.”
- Verify that no file appears in
/etc/or any parent directory of the intended upload store. - Check server logs for a sanitized filename entry (e.g.,
etc_passwd) or a rejection log.
Automated Security Test (OWASP ZAP Active Scan via CLI)
# Start ZAP daemon zap-daemon.sh -port 8090 & # Spider the upload page curl -s "http://localhost:8090/JSON/spider/action/scan/?url=https%3A%2F%2Fexample.com%2Fupload&recurse=true&maxChildren=10" # Active scan targeting the upload endpoint curl -s "http://localhost:809090/JSON/ascan/action/scan/?url=https%3A%2F%2Fexample.com%2Fupload&recurse=true&inScopeOnly=true" # Wait for completion, then retrieve alerts curl -s "http://localhost:8090/JSON/alert/view/alerts/?baseurl=https%3A%2F%2Fexample.com%2Fupload" | jq .*Pass*: No alerts of type “Path Traversal”, “File Upload”, or “Cross‑Site Scripting” with high/medium severity.
Code Snippet: Server‑Side Validation (Node/Express)
const fileType = require('file-type'); const MAX_SIZE = 5 * 1024 * 1024; // 5 MB const ALLOWED_MIME = ['application/pdf', 'image/jpeg', 'image/png']; app.post('/upload', authenticate, async (req, res) => { if (!req.files || !req.files.file) { return res.status(400).json({ error: 'No file provided' }); } const upload = req.files.file; // Size check if (upload.size > MAX_SIZE) { return res.status(400).json({ error: `File exceeds ${MAX_SIZE / 1024 / 1024} MB` }); } // MIME verification via magic bytes const type = await fileType.fromBuffer(upload.data); if (!type || !ALLOWED_MIME.includes(type.mime)) { return res.status(400).json({ error: 'Unsupported file type' }); } // Sanitize filename const safeName = upload.name .replace(/[\\/:*?"<>|]/g, '_') // remove illegal Windows chars .replace(/\s+/g, '_') // spaces to underscore .replace(/^\.+/, ''); // strip leading dots // Prevent path traversal const finalName = path.basename(safeName); const destPath = path.join(UPLOAD_DIR, finalName); // Save file await upload.mv(destPath); // Optional virus scan const scanResult = await clamscan.scanFile(destPath); if (scanResult.isInfected) { fs.unlinkSync(destPath); return res.status(400).json({ error: 'File failed security scan' }); } res.json({ fileId: uuidv4(), downloadUrl: `/files/${finalName}` }); });*Pass*: All validation branches exercised in unit tests yield appropriate HTTP status codes.
Security Test Matrix
Test Input Expected Server Response Extension spoof (.exe renamed to .jpg) malicious.exe(contentMZ…)400, “Unsupported file type” Path traversal ../../../tmp/evil.sh400, “Invalid filename” Oversized file 6 MB PDF 400, “File exceeds 5 MB” Empty file 0 byte TXT 400, “File must contain data” Valid PDF with embedded JS PDF containing /JS400 (if JS detection) or sanitized & stored outside web root Virus‑infected file EICAR test file 400, “File failed security scan” Unauthenticated POST No auth header 401 or 403 Rate limit exceed 15 uploads in 30 s 429, “Too many requests” *Pass*: Each row’s response matches the expected status and message.
Performance and Load Testing for File Uploads
Performance testing ensures the upload feature remains responsive under expected traffic and does not become a bottleneck for the overall system. It also validates that resource usage (CPU, memory, disk I/O, network) stays within acceptable limits.
Performance Goals (example)
- 95th percentile latency for a 2 MB upload ≤ 800 ms on a typical mid‑tier server.
- Throughput ≥ 50 concurrent uploads per second without error spikes.
- CPU utilization ≤ 70 % during sustained load.
- Disk write latency ≤ 5 ms average.
- Network saturation ≤ 80 % of uplink capacity.
Manual Performance Checks
- Single‑user baseline: Time a 5 MB upload with a stopwatch; compare against baseline.
- Browser DevTools Network: Record the
stages(stalling, request, response) to identify where time is spent. - Mobile emulation: Throttle connection to 3G (1.6 Mbps down, 768 kbps up) and verify the upload still completes within an acceptable window (e.g., ≤ 5 s for 2 MB).
- Observe server metrics: Use
top,htop, or cloud monitoring to see spikes during the test.
Automated Load Test (k6 Script)
import http from 'k6/http'; import { sleep, check } from 'k6'; import { SharedArray } from 'k6/data'; const files = new SharedArray('test files', function () { return [ { name: 'small.pdf', size: 500 * 1024 }, // 0.5 MB { name: 'medium.pdf', size: 2 * 1024 * 1024}, // 2 MB { name: 'large.pdf', size: 10 * 1024 * 1024} // 10 MB ]; }); export const options = { stages: [ { duration: '2m', target: 20 }, // ramp‑up { duration: '5m', target: 20 }, // steady { duration: '2m', target: 0 } // ramp‑down ], thresholds: { 'http_req_duration': ['p(95)<800'], // 95% under 800 ms 'http_req_failed': ['rate<0.01'] // <1% errors } }; export default function () { const file = files[Math.floor(Math.random() * files.length)]; const form = http.formData(); form.append('file', http.file(file.name, open(`./fixtures/${file.name}`), 'application/octet-stream')); const res = http.post('https://example.com/api/upload', form, { headers: { 'Content-Type': 'multipart/form-data' } }); check(res, { 'status is 200': (r) => r.status === 200, 'json has fileId': (r) => r.json().fileId !== undefined }); sleep(1); }*Pass*: The test completes with 95th‑percentile latency under 800 ms and error rate below 1 %.
Performance Test Table
Load Level Concurrent Users Avg. Latency (ms) 95th‑pct Latency (ms) Error Rate Light 5 210 350 0 % Moderate 20 420 720 0.2 % Heavy 50 680 950 1.5 % Spike 100 (burst) 1200 1800 4 % *Pass*: For the target SLA (e.g., 95th‑pct ≤ 800 ms at ≤ 30 users), the light and moderate rows must satisfy the condition; any deviation triggers performance tuning.
Tips for Improving Upload Performance
- Stream the file directly to object storage (e.g., S3) using presigned POST URLs to avoid buffering on the app server.
- Enable HTTP/2 or QUIC to multiplex uploads with other traffic.
- Offload virus scanning to an asynchronous worker queue; return a provisional ID and poll for scan completion.
- Use chunked uploads (e.g., Tus protocol) for large files to allow pause/resume and reduce timeout risk.
Release Readiness and Regression Checklist
Before tagging a release, the team must confirm that the upload feature satisfies functional, non‑functional, and compliance requirements. This section consolidates the prior checklists into a release‑gate matrix and outlines how to automate regression verification.
Release Gate Checklist (Yes/No)
Item Description Pass? Happy‑path upload works for all supported file types Verified via manual + automated test Error messages are clear, localized, and non‑technical Reviewed for each validation rule Accessibility audit (axe) reports zero violations Run on staging build Security scan (SAST + DAST) finds no high/medium findings OWASP ZAP + internal scanner Performance under expected load meets SLA k6/JMeter results within thresholds Storage location and permissions are correct Files stored outside web root, chmod 640Download link returns the exact byte‑for‑byte file Hash comparison (SHA‑256) Rate limiting and abuse mitigation active Tested with burst script Rollback plan documented (e.g., revert to previous container image) Reviewed in release notes Monitoring alerts configured (upload latency, error rate, storage utilization) Alertmanager rules verified *Pass*: Every item must be marked Yes before promotion to production.
Regression Test Suite (Playwright + API)
const { test, expect } = require('@playwright/test'); const { execSync } = require('child_process'); test.describe('File upload regression suite', () => { test.beforeAll(() => { // Ensure test data is present execSync('npm run prepare-fixtures'); }); test('happy path PDF', async ({ page }) => { /* … */ }); test('rejects .exe', async ({ page }) => { /* … */ }); test('keyboard navigation', async ({ page }) => { /* … */ }); test('error message language', async ({ page }) => { /* … */ }); test('download integrity', async ({ page }) => { /* … */ }); });*Pass*: All tests succeed on the release candidate branch; any failure blocks the merge.
Using SUSA for Autonomous Exploration (Optional)
SUSA can be pointed at the upload URL to automatically exercise many of the checklist items in a single pass:
pip install susatest-agent susatest run --url https://example.com/upload \ --personas curious impatient novice power-user \ --output ./susatest-report.jsonThe agent will:
- Attempt uploads with various file types and sizes (happy path & edge cases).
- Trigger validation errors by submitting malformed filenames or oversized payloads.
- Navigate via keyboard and screen‑reader simulated interactions to surface accessibility gaps.
- Record network responses, enabling later verification of status codes and payload structure.
- Generate regression scripts (Appium for Android, Playwright for web) that capture the discovered flows.
While SUSA provides broad coverage, it does not replace targeted security scans or performance load tests; those should still be run explicitly.
*Pass*: If the SUSA report shows zero critical findings (crashes, ANRs, dead ends) and the generated scripts pass in CI, the upload feature is considered exploration‑ready.
Closing Takeaways
A robust file upload feature demands more than a simple “select and send” check. By treating the upload surface as a combination of input validation, user interaction, security boundary, performance bottleneck, and accessibility touchpoint, you can construct a test matrix that catches defects before they reach users. The checklist presented here—spanning happy path, error handling, edge cases, accessibility, security, performance, and release readiness—offers concrete pass criteria, real‑world examples, and automation snippets that you can drop into your CI pipelines today.
When you integrate autonomous exploration tools like SUSA, you gain an extra layer of confidence: the agent will walk through many of these scenarios without manual scripting, surfacing regressions early and producing ready‑to‑run test scripts for future cycles. Combine that with disciplined manual checks for nuanced accessibility and security concerns, and you’ll have a repeatable, trustworthy process that keeps your file upload workflow reliable, safe, and usable across the evolving landscape of 2026 browsers, devices, and threat models.
Test Your App Autonomously
Upload your APK or URL. SUSA explores like 10 real users — finds bugs, accessibility violations, and security issues. No scripts.
Try SUSA Free