Skip to main content

How to Test File Upload Flows (CSV, Images, Docs)

Short answer

Upload flows fail on MIME validation, size limits, async virus scan, and drag-drop libraries that hide native file inputs. Use Playwright locator.setInputFiles() on input[type=file], fixture files in repo with exact byte sizes, expect.poll probes on scan_status, and ai.act only when drag-drop cannot reach the input—never skip negative type/size specs.

Part of Testing Guides by integrations.

Who this is for

HR applications, CMS, insurance, healthcare, and admin tools accepting résumés, images, PDFs, or CSV imports—with client and server validation plus optional malware scanning.

Why testing uploads matters

  • Security — executable masquerading as PDF; path traversal filenames
  • UX — valid HEIC from mobile rejected without clear error
  • Compliance — PII uploaded but scan pending blocks user with no feedback
  • Data integrity — partial upload marked success

Complexity map

ScenarioEdge caseWhy tests breakApproach
Size limit11MB on 10MB maxSilent failFixture exact size
MIME sniff.exe renamed .pdfMalware pathwrong.exe fixture
Scan pendingAsync ClamAVAssert too earlyPoll scan_status probe
Drag-drop UINo visible inputsetInputFiles failsExpose input or ai.act
Multiple filesOrder mattersWrong pairingsetInputFiles array
Progress UISlow networkTimeoutMock upload API delay
Image previewEXIF orientationWrong displayProbe stored metadata optional
Virus detectedQuarantineUser stuckAssert error + probe quarantine
S3 direct uploadPresigned URLUI bypassTest presign route separately
Chunked uploadResumePartial blobProbe final etag

setInputFiles (Playwright)

From Playwright file upload docs:

import path from 'path';

const fixtures = path.join(__dirname, '../fixtures/uploads');

await page.getByLabel('Upload document').setInputFiles(
path.join(fixtures, 'valid.pdf'),
);

await expect.poll(async () => {
const res = await request.get(`/api/test/probe-upload?runId=${runId}`);
return (await res.json()).status;
}, { timeout: 30_000 }).toBe('ready');

Multiple files:

await input.setInputFiles([
path.join(fixtures, 'page1.pdf'),
path.join(fixtures, 'page2.pdf'),
]);

Fixture files to keep in repo

fixtures/uploads/
valid.pdf # small valid doc
valid.png # image path
too-large.bin # exactly max+1 bytes
wrong.exe # rejected MIME (rename test)
empty.pdf # edge case

Generate too-large.bin in test setup if git size concern:

fs.writeFileSync(tmpPath, Buffer.alloc(10 * 1024 * 1024 + 1));

Size limit negative

await input.setInputFiles(path.join(fixtures, 'too-large.bin'));
await expect(page.getByText(/file too large|max 10/i)).toBeVisible();
await expect.poll(() => probeUploadCount(runId)).toBe(0);

MIME rejection

await input.setInputFiles(path.join(fixtures, 'wrong.exe'));
await expect(page.getByText(/file type|not allowed/i)).toBeVisible();
await expect.poll(() => probeUploadCount(runId)).toBe(0);

Virus scan pending

await input.setInputFiles(path.join(fixtures, 'valid.pdf'));
await expect(page.getByText(/scanning|processing/i)).toBeVisible();
await expect.poll(() => probeScanStatus(runId)).toBe('clean');
await expect(page.getByText(/upload complete/i)).toBeVisible();

Stub scanner in test env to transition pending → clean deterministically.

Drag-drop libraries

Many UI kits hide <input type="file">. Options:

  1. Unhide input in test builddata-testid="file-input"
  2. Dispatch drop event — library-specific, brittle
  3. ai.act('Drop valid.pdf onto upload zone') — last resort

Prefer (1) for CI stability.

Anti-patterns

Anti-patternWhy it failsBetter approach
Skip size negativeProd rejects silentlytoo-large fixture
Assert toast onlyFile not storedProbe upload row
Real EICAR in CIScanner infra variesStub infected flag
waitForTimeout after uploadScan still pendingpoll scan_status
Upload prod sample docsPII leakSynthetic fixtures

Example scenario

Situation: Candidate uploads PDF résumé; server scans then attaches to application.

Expected outcome: File stored with scan_status clean and linked to application id.

Why UI-only automation breaks: Progress bar completes but probe shows scan pending or wrong application.

  1. Arrange: Seed application runId; stub scanner fast-path.
  2. Act: setInputFiles valid.pdf on upload input.
  3. Assert: Probe upload linked to application; scan_status clean.

TestChimp workflow: Track file_type × upload_outcome when HEIC uploads spike untested.

Same Arrange/Act/Assert pattern as expired-coupon checkout.

Connect scenarios to your QA workflow

Capture business rules in markdown test plans and enforce them with seed routes and probe Assert. Link SmartTests with // @Scenario: for requirement traceability. Use /testchimp test on PRs; /testchimp explore on SmartTest paths for non-functional gaps (ExploreChimp).

External references

Frequently asked questions

How do I upload files in Playwright?

Use locator.setInputFiles(path) on input[type=file]. Register path to fixtures in repo. Poll probe until upload and scan complete—do not assert on toast alone.

Drag-and-drop upload without visible input?

Expose hidden input with data-testid in test builds, or use file chooser event if click opens picker. ai.act is last resort for drop zones.

How do I test file size limits?

Fixture file exactly max+1 bytes. Assert UI error and probe upload count zero.

How do I test virus scan pending state?

Stub scanner in test env. Assert processing UI then poll probe scan_status until clean or infected.

Multiple file upload order?

setInputFiles with array of paths. Probe stored files count and order if product requires it.

Should I use real malware samples?

No—stub scanner returning infected for a dedicated fixture name. EICAR behavior varies by infra.

HEIC uploads popular in prod but untested?

TrueCoverage file_type × upload_outcome highlights gap. Add heic fixture and conversion path spec via /testchimp evolve.

Apply these patterns in your repo

Run `/testchimp init` to connect TestChimp to your repo, then `/testchimp test` on PRs to turn these patterns into maintained SmartTests. Use `/testchimp evolve` when you want to expand coverage as your app grows.

Start free on TestChimp · Book a demo