Image Upload Testing Checklist (2026)

Image Upload Testing Checklist (2026) provides a concrete, step‑by‑step matrix for validating every aspect of an image upload feature—from the simplest happy‑path flow to the most obscure edge case, a

By · June 18, 2026 · 17 min read · Testing Checklists

Image Upload Testing Checklist (2026) provides a concrete, step‑by‑step matrix for validating every aspect of an image upload feature—from the simplest happy‑path flow to the most obscure edge case, accessibility concern, and security risk. Use this guide as a reference you can bookmark, adapt to your stack, and run manually or through automation. The sections below break the checklist into logical groups, give clear pass criteria, show real‑world examples, and include code snippets that you can drop into a test suite.

---

Image Upload Testing Checklist (2026): Happy Path Scenarios

Core flow validation

Test IDDescriptionInput / ActionExpected ResultPass Criteria
HP‑01Single image upload via file pickerSelect a JPEG ≤ 5 MB, click UploadImage appears in gallery, upload success toast, HTTP 200 response with URLUpload completes within 3 s on 3G, no errors in console
HP‑02Drag‑and‑drop uploadDrag a PNG ≤ 10 MB onto drop zone, releaseSame as HP‑01, visual feedback shows drop zone highlightNo JavaScript errors, drop zone returns to idle state
HP‑03Multiple image selectionChoose 3 images (JPEG, PNG, WebP) each ≤ 2 MBAll three upload concurrently, each shows individual progress bar, final gallery shows all threeAll requests return 200, total time ≤ 1.5× single upload time
HP‑04Upload from camera (mobile)Launch camera, capture photo, confirmPhoto uploaded, EXIF orientation preserved, thumbnail generatedImage displays correctly oriented, no corruption
HP‑05Upload with optional metadataFill caption field “Sunset beach”, select album “Vacation”, uploadBackend stores image URL, caption, album ID; gallery shows caption under thumbnailMetadata matches payload sent in multipart request
HP‑06Resume after network interruptionStart upload, disable Wi‑Fi at 50 % progress, re‑enable after 5 sUpload resumes from checkpoint, completes successfullyNo duplicate files, final size matches original

Implementation notes

Automated happy‑path snippets

cURL (manual verification)


curl -X POST https://api.example.com/v1/images \
  -H "Authorization: Bearer $TOKEN" \
  -F "file=@/tmp/sample.jpg;type=image/jpeg" \
  -F "caption=Sunset beach" \
  -F "album_id=42"

Appium (Android) – Java


@Test
public void testHappyPathUpload() {
    driver.findElement(By.id("upload_btn")).click();
    driver.findElement(By.id("file_picker")).sendKeys("/sdcard/Pictures/test.jpg");
    driver.findElement(By.id("caption")).sendKeys("Happy path test");
    driver.findElement(By.id("submit")).click();

    WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
    wait.until(ExpectedConditions.visibilityOfElementLocated(By.id("toast_success")));
    Assert.assertTrue(driver.findElement(By.id("toast_success")).getText()
            .contains("Upload successful"));
}

Playwright (Web) – TypeScript


test('happy path upload', async ({ page }) => {
  await page.goto('/upload');
  await page.setInputFiles('input[type="file"]', 'tests/fixtures/sample.png');
  await page.fill('textarea[placeholder="Caption"]', 'Playwright test');
  await page.click('button:has-text("Upload")');

  await expect(page.locator('.toast-success')).toContainText('Uploaded');
  const imgSrc = await page.getAttribute('img.gallery-item', 'src');
  expect(imgSrc).toMatch(/^https:\/\/cdn\.example\.com\/images\/.+\.(png|jpe?g)$/);
});

---

Image Upload Testing Checklist (2026): Error Handling and Validation

Client‑side validation

Test IDDescriptionInput / ActionExpected ResultPass Criteria
EH‑01Empty file pickerClick Upload without selecting a fileInline error “Please select an image”, form not submittedNo network request, focus stays on picker
EH‑02Wrong MIME typeSelect a .pdf fileError “Only image files are allowed”Request blocked, file not uploaded
EH‑03Exceed size limitChoose a 12 MB JPEG (limit 10 MB)Error “File too large – max 10 MB”No upload attempt
EH‑04Zero‑byte fileCreate empty file touch empty.jpg, select itError “File appears to be corrupted”Upload rejected
EH‑05Invalid characters in filenameSelect file named image<>.jpgEither sanitized name or error “Invalid filename”No 500 server error; filename stored safely
EH‑06Simultaneous exceed of concurrent uploadsTry to start 6 uploads when limit is 4First 4 start, remaining 2 queued or show “Too many concurrent uploads”System stays responsive, no crash

Server‑side validation

Test IDDescriptionInput / ActionExpected ResultPass Criteria
EH‑07Malformed multipart boundarySend request with missing boundaryHTTP 400 Bad Request, error “Invalid multipart format”No image stored
EH‑08Path traversal in filenameFilename ../../etc/passwdHTTP 400, error “Invalid filename”No file written outside upload directory
EH‑09Embedded scripts (XSS)Upload SVG with <script>alert(1)</script>File stored but served with Content‑Disposition: attachment or sanitized; script not executedResponse headers prevent execution
EH‑10Virus‑like contentUpload file containing EICAR test stringAV service (if integrated) blocks, returns HTTP 422 with “Malicious content detected”No file persisted
EH‑11Metadata injectionCaption field with SQL '; DROP TABLE images;--Stored as plain text, no SQL errorParameterized queries or ORM used
EH‑12Unsupported image formatUpload HEIC on backend that only accepts JPEG/PNG/WebPHTTP 415 Unsupported Media TypeClear error message, no 500

Implementation notes

Automated error‑handling snippet (Playwright)


test('rejects PDF upload', async ({ page }) => {
  await page.goto('/upload');
  await page.setInputFiles('input[type="file"]', 'tests/fixtures/dummy.pdf');
  await expect(page.locator('.error-message')).toHaveText(/Only image files are allowed/);
  await expect(page.request.once('request', r => r.url().includes('/v1/images'))).not.toBeCalled();
});

---

Image Upload Testing Checklist (2026): Edge and Boundary Cases

File‑size boundaries

Test IDSize (bytes)LimitExpected Result
EB‑010 (empty)>0Rejected (see EH‑04)
EB‑021 byte>0Accepted if valid image header (rare) – otherwise rejected
EB‑039 999 99910 MBAccepted
EB‑0410 000 00010 MBAccepted (exact boundary)
EB‑0510 000 00110 MBRejected
EB‑0650 MB100 MB (high‑limit endpoint)Accepted
EB‑07150 MB100 MBRejected

Dimension boundaries

Test IDWidth × Height (px)Max dimensionExpected Result
EB‑081 × 15000 pxAccepted (tiny but valid)
EB‑095000 × 50005000 pxAccepted (exact)
EB‑105001 × 50005000 pxRejected (width exceeds)
EB‑1110000 × 1005000 pxRejected (height exceeds)
EB‑120 × 0N/ARejected (invalid image)

Format‑specific quirks

Test IDFormatParticularityExpected Result
EB‑13JPEG with CMYK profileSome browsers cannot display CMYKImage uploaded, but preview may show colors shifted – ensure server converts to sRGB or warns
EB‑14PNG with alpha channelTransparencyPreserved after upload; verify via GET that alpha channel intact
EB‑15WebP losslessNewer formatAccepted if backend supports; otherwise returns 415
EB‑16Animated GIFMultiple framesAccepted; ensure only first frame used for thumbnail unless spec says otherwise
EB‑17HEIC (Apple)Requires licensingIf backend lacks HEIC support, return 415 with helpful message
EB‑18SVG with external resources<image href="http://evil.com/bad.png">Sanitize or block external references; otherwise may lead to SSRF

Implementation notes

Shell script to create boundary JPEGs


#!/usr/bin/env bash
# creates a JPEG of exact size $1 bytes (approximate)
SIZE=$1
OUT="test_${SIZE}b.jpg"
# start with a 100x100 color image
convert -size 100x100 xc:#$(printf "%06x" $((RANDOM%0xffffff))) tmp.png
# adjust quality until file size approximates target
QUAL=95
while true; do
  convert tmp.png -quality $QUAL $OUT
  ACTUAL=$(stat -c%s "$OUT")
  if (( ACTUAL <= SIZE && ACTUAL > SIZE-500 )); then break; fi
  if (( ACTUAL > SIZE )); then ((QUAL--)); else ((QUAL++)); fi
done
echo "Generated $OUT ($ACTUAL bytes)"

---

Image Upload Testing Checklist (2026): Accessibility

Keyboard navigation

Test IDActionExpected Result
A‑01Tab to file‑picker button, press EnterFile‑picker dialog opens
A‑02Tab through drop zone, press SpaceSame as click – file‑picker opens
A‑03After selecting file, tab to caption field, type, then tab to Upload button, press EnterUpload initiates
A‑04Escape key while dialog open closes dialog without selectionNo stray file attached

Screen‑reader announcements

Test IDScenarioExpected Announcement
A‑05File‑picker opens“Choose file, button”
A‑06File selected“File selected, image.jpg”
A‑07Upload in progress“Uploading, 30 percent completed” (if live region)
A‑08Upload success“Upload successful, image.jpg added to gallery”
A‑09Upload error“Error, file too large – maximum 10 MB”

Color contrast & focus visibility

ARIA & labeling

Automated accessibility check (axe‑core with Playwright)


import { injectAxe, checkA11y } from '@playwright/experimental-axe-helper';

test.describe('upload accessibility', () => {
  test.beforeEach(async ({ page }) => {
    await injectAxe(page);
    await page.goto('/upload');
  });

  test('passes axe core checks', async ({ page }) => {
    const accessibilitySnapshot = await checkA11y(page, {
      // exclude known false positives if any
      exclude: ['.tooltip'],
    });
    expect(accessibilitySnapshot.violations).toEqual([]);
  });
});

---

Image Upload Testing Checklist (2026): Security and Privacy

Threat model checklist

Test IDThreatTest caseExpected mitigation
S‑01Unauthorized uploadOmit auth token, attempt POSTHTTP 401 Unauthorized
S‑02File type spoofingRename .exe to .jpg, uploadServer rejects based on magic bytes, not extension
S‑03Path traversalFilename ../../../tmp/evil.jpgServer normalizes, returns 400
S‑04SSRF via image URL fetchProvide URL http://169.254.169.254/latest/meta-data/ in metadata fieldServer disallows fetching external URLs or restricts to allow‑list domains
S‑05Denial of service via huge fileUpload 5 GB fileConnection timed out or rejected early by size check before disk consumption
S‑06Metadata leakageUpload image with GPS coordinates, check if EXIF strippedEither EXIF removed or stored separately with user consent
S‑07CSRFSubmit upload form from another site without tokenRequest rejected due to missing/invalid CSRF token
S‑08ClickjackingEmbed upload page in iframeX-Frame-Options: DENY or CSP frame-ancestors 'none'
S‑09Rate limiting abuseSend 100 upload requests in 5 s from same IPHTTP 429 Too Many Requests after threshold
S‑10Stored XSS via SVGUpload SVG with <script>; later view in galleryScript not executed; CSP script-src 'self' blocks inline script

Privacy considerations

Automated security test (OWASP ZAP baseline script)


zap-baseline.py -t https://api.example.com/v1/images \
  -r zap_report.html \
  -k \
  -d \
  -c zap_policy.conf

Where zap_policy.conf disables passive scans that are noisy and enables active scans for injection, path traversal, and file upload tests.

---

Image Upload Testing Checklist (2026): Performance and Load Testing

Performance benchmarks

MetricTarget (95th percentile)Measurement method
Upload latency (file ≤ 5 MB)≤ 800 ms on 4G, ≤ 2 s on 3GJMeter or k6 with HTTP sampler
Throughput (concurrent users)≥ 50 uploads/sec sustained for 5 mink6 script with ramp‑up
CPU usage on upload worker≤ 60 % averagePrometheus node_exporter
Memory growth per upload≤ 5 MB (no leak)Heap snapshot before/after batch
Disk I/O spikes≤ 30 MB/s averageiostat during test

Sample k6 script


import http from 'k6/http';
import { check, sleep } from 'k6';
import { SharedArray } from 'k6/data';

const images = new SharedArray('test images', function () {
  // load base64 strings of small test images from a JSON file
  return JSON.parse(open('./test-images.json')).map(b64 => ({
    payload: b64,
  }));
});

export const options = {
  stages: [
    { duration: '2m', target: 20 }, // ramp‑up
    { duration: '5m', target: 20 }, // stay
    { duration: '2m', target: 0 },  // ramp‑down
  ],
};

export default function () {
  const img = images[Math.floor(Math.random() * images.length)];
  const form = {
    file: http.file(img.payload, 'upload.jpg', 'image/jpeg'),
    caption: `k6 test ${Date.now()}`,
  };
  const res = http.post('https://api.example.com/v1/images', form, {
    headers: { 'Authorization': `Bearer ${__ENV.TOKEN}` },
  });
  check(res, {
    'status is 200': (r) => r.status === 200,
    'upload latency < 800ms': (r) => r.timings.duration < 800,
  });
  sleep(1);
}

Run with:


k6 run --out json=result.json upload_test.js

Load‑testing with Locust (Python)


from locust import HttpUser, task, between

class ImageUploadUser(HttpUser):
    wait_time = between(1, 3)

    @task
    def upload_image(self):
        files = {'file': ('test.jpg', open('tests/fixtures/small.jpg', 'rb'), 'image/jpeg')}
        data = {'caption': 'Locust test'}
        with self.client.post("/v1/images", files=files, data=data, catch_response=True) as resp:
            if resp.status_code == 200:
                resp.success()
            else:
                resp.failure(f"Got {resp.status_code}")

---

Image Upload Testing Checklist (2026): Release Readiness and Regression

Pre‑release sign‑off checklist

ItemOwnerEvidence
All happy‑path tests pass on stagingQA LeadTestRail run ID #1245
No high‑severity security findings in ZAP baselineSecOpsZAP report < 5 low
Accessibility audit score ≥ 90 % (axe)UX EngineerAxe report
Performance benchmarks met under expected loadPerf Engineerk6 summary
Rollback plan documented (feature flag off)Release ManagerConfluence page
Monitoring alerts configured for upload error rate > 1 %SREAlertmanager rule
Documentation updated (API spec, user guide)Tech WriterPR #3321
Canary deployment validated (5 % traffic)DevOpsGrafana dashboard

Regression test suite

Example Pact consumer test (JavaScript)


const { Pact } = require('@pact-foundation/pact');
const path = require('path');

const provider = new Pact({
  consumer: 'image-uploader-ui',
  provider: 'image-service',
  port: 1234,
  log: path.resolve(process.cwd(), 'logs', 'pact.log'),
  dir: path.resolve(process.cwd(), 'pacts'),
});

describe('Image Upload Pact', () => {
  beforeAll(() => provider.setup());
  afterAll(() => provider.finalize());

  it('returns a URL for a valid JPEG', async () => {
    await provider.addInteraction({
      state: 'an image can be stored',
      uponReceiving: 'a valid JPEG upload',
      withRequest: {
        method: 'POST',
        path: '/v1/images',
        headers: { 'Content-Type': 'multipart/form-data; boundary=something' },
        body: // matcher for multipart omitted for brevity
      },
      willRespondWith: {
        status: 200,
        body: { url: liker('https://cdn.example.com/images/.*\\.jpg') },
        headers: { 'Content-Type': 'application/json' },
      },
    });

    const res = await chai.request(`http://localhost:${provider.port}`)
      .post('/v1/images')
      .set('Authorization', `Bearer ${process.env.TOKEN}`)
      .attach('file', Buffer.from('fake-jpeg-data'), 'test.jpg')
      .field('caption', 'pact test');

    expect(res).to.have.status(200);
    expect(res.body).to.have.property('url').that.matches(/^https:\/\/cdn\.example\.com\/images\/.*\.jpg$/);
  });
});

---

Image Upload Testing Checklist (2026): Autonomous Exploration with SUSA

SUSA (the autonomous QA platform) can exercise most of the items in this checklist without writing a single test script. When you point SUSA at your app’s upload screen (or provide the APK), it:

  1. Discovers the upload entry point – taps the button, opens the file picker, and attempts drag‑and‑drop on web.
  2. Generates varied personas – a *curious* user tries every button, an *impatient* user repeatedly taps upload while a file is still processing, a *novice* user leaves fields blank, an *adversarial* user attempts to inject scripts via filename or metadata, an *elderly* user interacts slowly to surface timing‑dependent bugs, and an *accessibility* persona navigates solely via keyboard and screen‑reader cues.
  3. Executes the matrix – each persona’s behavior profile drives a combination of happy‑path, error, edge, and security attempts. For example, the adversarial persona will try filenames with path‑traversal sequences, upload SVGs with script tags, and send oversized files to trigger DoS limits.
  4. Validates observables – SUSA checks for toasts, progress bars, network responses, console errors, and accessibility announcements. It records HTTP status, latency, and any crash or ANR.
  5. Creates regression scripts – after a run, SUSA exports Appium (Android) and Playwright (Web) scripts that reproduce the exact interactions it performed, giving you a ready‑to‑run test suite for CI.
  6. Learns across sessions – screens identified as dead ends (e.g., a modal that never dismisses) are remembered; subsequent runs skip fruitless paths and focus on new variations, improving coverage over time.

Running SUSA locally


# install the agent
pip install susatest-agent

# point at a running web app (ensure it's reachable)
susatest-agent test https://staging.example.com/upload \
  --output ./susatest-report.json \
  --format junit

# for a mobile APK
susatest-agent test ./app-release.apk \
  --device emulator-5554 \
  --output ./susatest-mobile.json

The resulting report contains a pass/fail matrix that maps directly to the checklist items above (e.g., HP-01: PASS , EH-07: FAIL – missing boundary validation ). You can then prioritise fixes based on severity and impact.

---

Image Upload Testing Checklist (2026): Manual vs Automated Approaches

ApproachStrengthsWeaknessesWhen to use
Manual exploratory testingHuman intuition catches UX quirks, subtle accessibility issues, and unexpected interaction patterns.Time‑consuming, hard to repeat, subject to tester bias.Early‑stage feature, usability studies, ad‑hoc bug hunts.
Scripted UI automation (Playwright/Appium)Repeatable, integrates into CI, fast regression, can load‑test with parallel workers.Requires maintenance when UI changes, may miss low‑frequency edge cases that aren’t scripted.Stable features, nightly regression, pre‑release sign‑off.
API‑level contract testsFast, isolates backend logic, easy to version with Pact.Does not validate UI‑specific behavior (toasts, drag‑and‑drop, keyboard).Backend‑only changes, contract verification with frontend teams.
Load & stress testing (k6, Locust)Reveals performance bottlenecks, concurrency limits, resource leaks.Needs realistic test data, may generate false alarms if not tuned to production‑like traffic.Pre‑release performance gate, capacity planning.
Autonomous exploration (SUSA)Broad coverage with minimal scripting, persona‑driven, self‑learning, produces executable scripts.Still depends on correct test environment setup; may generate noisy reports if not filtered.Continuous discovery, regression safety net, augmenting manual effort.

A balanced strategy layers these techniques: start with manual exploration to understand the flow, then automate the happy path and common errors, add contract tests for the API, run load tests nightly, and let SUSA run a weekly autonomous sweep to surface regressions that slip through the cracks.

---

Image Upload Testing Checklist (2026): Quick Reference Checklist

Copy this into your team’s wiki or markdown file and tick off items as you verify them.


[ ] HP-01  Single file upload via picker
[ ] HP-02  Drag‑and‑drop upload
[ ] HP-03  Multiple concurrent uploads
[ ] HP-04  Camera capture upload (mobile)
[ ] HP-05  Upload with caption/album metadata
[ ] HP-06  Resume after network interruption

[ ] EH-01  Empty picker – inline error
[ ] EH-02  Wrong MIME type – blocked
[ ] EH-03  File exceeds size limit – error
[ ] EH-04  Zero‑byte file – rejected
[ ] EH-05  Invalid filename characters – sanitized or error
[ ] EH-06  Too many concurrent uploads – queued or limited

[ ] EB-01 … EB-07  File size boundaries (0, 1, limit‑1, limit, limit+1, high‑limit, over‑high)
[ ] EB-08 … EB-12  Dimension boundaries (1×1, max‑max, over‑max, etc.)
[ ] EB-13 … EB-18  Format quirks (CMYK JPEG, PNG alpha, WebP, animated GIF, HEIC, risky SVG)

[ ] A-01 … A-04  Keyboard navigation (tab, enter, space, escape)
[ ] A-05 … A-09  Screen‑reader announcements (picker, file selected, progress, success, error)
[ ] Contrast ≥ 4.5:1, focus outline visible
[ ] ARIA labels, live region for progress, role/button for custom drop zone

[ ] S-01 … S-10  Security matrix (auth, spoofing, path traversal, SSRF, DoS, metadata, CSRF, clickjacking, rate limiting, stored XSS)

[ ] Perf targets met (latency, throughput, CPU, memory, I/O) – k6/Locust baseline
[ ] Release sign‑off checklist completed
[ ] Regression suite (unit, contract, UI smoke, performance) passes in CI
[ ] SUSA autonomous run yields ≥ 90 % coverage of matrix items, generates Appium/Playwright scripts

---

Takeaways

By following this exhaustive checklist, you’ll catch the classic “it works on my machine” slips, the nasty edge cases that only appear under load or with unusual file characters, and the accessibility or security gaps that could otherwise reach users. Happy testing!

Test Your App Autonomously

Upload your APK or URL. SUSA explores like 11 real users — finds bugs, accessibility violations, and security issues. No scripts. New to the category? Start with what autonomous product intelligence & QA means.

Try SUSA Free