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

March 31, 2026 · 17 min read · Testing Checklists

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

Manual Test Steps

  1. Open the page containing the upload widget.
  2. Click the “Choose File” button and select a valid file (e.g., a 500 KB PDF).
  3. Observe the UI update: file name displayed, progress indicator starts.
  4. Wait for the upload to finish and verify the success message.
  5. Use the browser’s network tab to confirm the request payload includes Content‑Disposition: form-data; name="file"; filename="document.pdf".
  6. Check the server storage location for the file and verify its size matches the source.
  7. 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 TypeSizeExpected UIPass Criteria
PDF100 KBName shown, progress barHTTP 200, file stored
JPEG2 MBThumbnail previewHTTP 200, correct MIME
ZIP10 MBNo preview, name shownHTTP 200, extracted? (if applicable)
TXT500 BName shownHTTP 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

Pass Criteria for Negative Tests

Manual Test Steps (Size Limit Example)

  1. Set the server‑side max size to 2 MB.
  2. Choose a 2.5 MB PDF file.
  3. Click upload.
  4. 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.”
  5. Confirm that no POST /api/upload request 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

SymptomLikely CauseFix
Success toast appears for a blocked .exeClient‑side validation missingAdd extension check before FormData creation
Server returns 500 when file is 0 bytesNo guard against empty fileReturn 400 with “File must contain data”
Error message disappears after 2 secondsAuto‑dismiss toast too aggressiveKeep error visible until user action
Duplicate file allowed overwriting silentlyNo conflict resolutionImplement “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

Concurrency and Race Conditions

Browser‑Specific Quirks

Pass Criteria for Edge Cases

Manual Test Steps (Unicode Filename)

  1. Create a file named 📊_报告_2026.pdf (contains emoji and Chinese characters).
  2. Upload via the widget.
  3. After success, locate the file in storage; confirm the name is exactly 📊_报告_2026.pdf (or URL‑encoded equivalent).
  4. 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 CaseTest DataExpected Result
Filename with emoji😀_test.txtStored name unchanged, downloadable
Filename 260 charsa…a.txt (260)Either truncated to 255 with notice or rejected (422)
File with LF in nameline\n.txtRejected or name sanitized (LF removed)
Simultaneous uploads (2×)Two 5 MB PDFs started within 200 msTwo distinct IDs, both succeed
Network drop‑offKill 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

Manual Accessibility Checks

  1. Screen Reader: Using NVDA or VoiceOver, navigate to the upload area. Verify that the announcement includes the button label, allowed formats, and size limit.
  2. 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).
  3. High Contrast Mode: Switch OS to high contrast; confirm that the drag‑and‑drop zone border and text remain distinguishable.
  4. Reduced Motion: If the upload includes animations (e.g., spinner), verify that they respect the prefers-reduced-motion media query.
  5. 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

IssueWCAG CriterionRemediation
Upload button uses only an icon without aria-label1.3.1, 4.1.2Add aria-label="Attach file" or visually hidden text
Drag‑and‑drop zone is a
lacking role
4.1.2Add role="button" and tabindex="0"
Error message appears as a tooltip not announced3.3.2, 4.1.2Use aria-live="assertive" on the message container
Focus outline removed via CSS outline:none2.4.7Provide a custom visible focus style
File size limit conveyed only via placeholder text3.3.2Associate helper text with aria-describedby on 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

Pass Criteria for Security Tests

Manual Security Test Steps (Path Traversal)

  1. Attempt to upload a file named ../../etc/passwd.
  2. Observe the server response: it should return HTTP 400 with a message like “Invalid filename.”
  3. Verify that no file appears in /etc/ or any parent directory of the intended upload store.
  4. 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

TestInputExpected Server Response
Extension spoof (.exe renamed to .jpg)malicious.exe (content MZ…)400, “Unsupported file type”
Path traversal../../../tmp/evil.sh400, “Invalid filename”
Oversized file6 MB PDF400, “File exceeds 5 MB”
Empty file0 byte TXT400, “File must contain data”
Valid PDF with embedded JSPDF containing /JS400 (if JS detection) or sanitized & stored outside web root
Virus‑infected fileEICAR test file400, “File failed security scan”
Unauthenticated POSTNo auth header401 or 403
Rate limit exceed15 uploads in 30 s429, “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)

Manual Performance Checks

  1. Single‑user baseline: Time a 5 MB upload with a stopwatch; compare against baseline.
  2. Browser DevTools Network: Record the stages (stalling, request, response) to identify where time is spent.
  3. 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).
  4. 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 LevelConcurrent UsersAvg. Latency (ms)95th‑pct Latency (ms)Error Rate
Light52103500 %
Moderate204207200.2 %
Heavy506809501.5 %
Spike100 (burst)120018004 %

*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

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)

ItemDescriptionPass?
Happy‑path upload works for all supported file typesVerified via manual + automated test
Error messages are clear, localized, and non‑technicalReviewed for each validation rule
Accessibility audit (axe) reports zero violationsRun on staging build
Security scan (SAST + DAST) finds no high/medium findingsOWASP ZAP + internal scanner
Performance under expected load meets SLAk6/JMeter results within thresholds
Storage location and permissions are correctFiles stored outside web root, chmod 640
Download link returns the exact byte‑for‑byte fileHash comparison (SHA‑256)
Rate limiting and abuse mitigation activeTested 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.json

The agent will:

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