VALIDATION LABFILE UPLOAD

Date & Files

File Upload

Upload an image, verify size + type validation, and reject files that exceed the budget.

Intermediate

Scenario

Uploads are the security frontier — a single wrong MIME-check lets an .exe slip into the asset CDN. QA must verify both client AND server-side validation, never just the visible error.

Live widget · interact freely

Manual test checklist

  • 1Upload a PNG under the size cap — preview thumbnail renders
  • 2Upload a renamed `.exe` (or any unsupported MIME) — error appears
  • 3Upload a >1MB file — size error appears
  • 4Upload nothing then submit — graceful fallback, no crash
  • 5Inspect the network request — is the file `multipart/form-data`?
  • 6Drag-and-drop onto the input — same flow as click-to-pick

Expected result

A 50KB PNG renders a preview thumbnail; a 5MB upload triggers the size error.

Automation challenge

Use `setInputFiles({ name, mimeType, buffer })` to construct a synthetic 5 MB buffer in memory — no fixture file needed. Assert the size error fires. Then upload a real fixture PNG and assert the preview renders.

Stable selectors

  • File input[data-testid="upload-input"]
  • Preview thumbnail[data-testid="upload-preview"]
  • Size error[data-testid="upload-error"]

Locator strategy

Three levels from simple IDs to scoped Playwright locators. IDs and names are easy to learn but are not always the best long-term choice when labels change or components repeat.

Use simple IDs and names to understand how locating elements works.

1
2
3
await page.goto('https://lab.hakdogan.com/practice/file-upload');

await page.locator('#upload-input').setInputFiles('fixture.png');

Avoid as primary strategies

XPath (unless there is no alternative), long CSS chains, Tailwind-style utility class selectors, generated or unstable IDs, and volatile framework internals break when layout, styling, or DOM structure shifts.

1
2
await page.locator('.w-full.rounded-xl.border.bg-blue-500').fill('demo@example.com');
await page.locator('//div[2]/form/div[1]/input').fill('demo@example.com');

Reference Playwright spec

file-upload.spec.ts
ts
1
2
3
4
5
6
7
8
9
10
11
12
13
import { test, expect } from '@playwright/test';

test('file upload rejects oversize file', async ({ page }) => {
  await page.goto('https://lab.hakdogan.com/practice/file-upload');

  await page.getByTestId('upload-input').setInputFiles({
    name: 'huge.png',
    mimeType: 'image/png',
    buffer: Buffer.alloc(2 * 1024 * 1024),
  });

  await expect(page.getByTestId('upload-error')).toContainText(/exceeds/i);
});

Headed Test Playback

Simulated headed-browser flow — no real browser is launched.

Automation-style playback (Playwright-shaped logs). No real browser; no commands run on your machine.

Mixed: fill() for stable fields, type() / press() where keyboard semantics matter.

Idlefile-upload.spec.ts
Failure demos
https://lab.hakdogan.com/practice/file-upload
playwright · headed · chromium0.00s
# awaiting Run Test · terminal scrolls automatically
Steps
  1. Open upload widget
  2. Select avatar.png
  3. Validate file type
  4. Validate file size
  5. Show preview
  6. Assert upload success
StatusIdle
BrowserChromium
FrameworkPlaywright + TypeScript
Elapsed (live)--
Specfile-upload.spec.ts