How to Test Image Upload: A Complete Guide
How to Test Image Upload: A Complete Guide
How to Test Image Upload: A Complete Guide
Testing image upload is a critical part of any application that accepts user‑generated media. Failures in this flow can lead to broken features, security vulnerabilities, accessibility barriers, and poor user experience. This guide walks you through a complete, platform‑agnostic approach—from why the feature matters to a detailed test matrix, manual and automated techniques, accessibility and security checks, production‑only edge cases, tooling recommendations, and a concise checklist you can keep on hand.
How to Test Image Upload: A Complete Guide – Overview
Image upload touches many layers of a system: the client‑side UI, the API endpoint, storage services, background processing (thumbnails, virus scanning), and downstream consumption (display, sharing). A defect anywhere in this chain can manifest as a silent failure (no error shown), a misleading success toast, or a crash that only appears under specific conditions (large files, unusual MIME types, concurrent uploads).
Why Image Upload Matters
- User trust – Users expect photos to appear instantly; failures erode confidence.
- Business impact – E‑commerce, social networks, and SaaS platforms often tie revenue to media upload (product images, profile pictures, attachments).
- Compliance – Regulations such as GDPR or CCPA may require proper handling of personal data embedded in images (EXIF GPS, facial recognition).
- Performance – Large uploads can saturate bandwidth, cause timeouts, or exhaust server resources if not guarded.
Common Failure Modes
| Failure Category | Typical Symptom | Root Cause Example |
|---|---|---|
| Client validation | Upload button stays disabled after selecting a file | JavaScript incorrectly reads FileList length |
| API contract | 415 Unsupported Media Type returned | Server expects image/jpeg but receives image/jpg |
| Storage quota | 507 Insufficient Storage after 10 MB upload | Bucket policy limits per‑user size |
| Virus scan | 500 Internal Server Error after upload | Scanner crashes on corrupted JPEG |
| Thumbnail generation | Image displays as broken icon | ImageMagick fails on CMYK color space |
| Accessibility | Screen reader announces “upload failed” without details | Missing aria-live region for error messages |
| Security | Arbitrary code execution via uploaded SVG with script | Server serves uploaded SVG without sanitizing script tags |
Understanding these categories helps you build a test matrix that covers happy paths, error paths, and edge cases that only surface under load or with specific file characteristics.
How to Test Image Upload: A Complete Guide – Test Matrix Foundations
A solid test matrix starts with dimensions you can combine: file attributes, transfer conditions, user contexts, and system states. Below is a comprehensive matrix you can adapt to web, mobile, or desktop clients.
File Attribute Dimensions
| Dimension | Values to Test | Rationale |
|---|---|---|
| MIME type | image/jpeg, image/png, image/webp, image/gif, image/svg+xml, application/octet-stream (renamed .jpg) | Verify server accepts allowed types and rejects others |
| File size | 0 B (empty), 1 B, 100 KB, 5 MB, 10 MB, 50 MB (if limit >50 MB), 101 MB (over limit) | Test boundary conditions and rejection messages |
| Dimensions | 1×1 pixel, 100×100, 1920×1080, 7680×4320 (8K), 30000×30000 (absurd) | Ensure decoder handles extremes without OOM |
| Color profile | sRGB, Adobe RGB, CMYK, grayscale, ICC‑embedded | Some libraries choke on non‑sRGB profiles |
| Metadata | No EXIF, EXIF with GPS, EXIF with orientation tag, XMP, IPTC | Check that orientation is respected and no data leakage |
| Corruption | Truncated JPEG, zero‑filled PNG, malformed SVG, polyglot image/JavaScript | Validate server‑side virus scanning and sandboxing |
| Animated | GIF, APNG, WebP animation | Confirm animation frames are preserved or properly flattened |
| Multi‑page | TIFF with multiple pages, PDF (if allowed) | Ensure only first page is processed or rejection occurs |
| Filename | ASCII, Unicode (emoji), very long (>255 chars), containing path traversal (../), null bytes | Test filename sanitization and storage safety |
Transfer Condition Dimensions
| Condition | Values to Test | Rationale |
|---|---|---|
| Network latency | 0 ms (localhost), 50 ms, 200 ms, 500 ms (simulated with tc or throttling proxy) | Verify timeout handling and retry logic |
| Bandwidth | Unlimited, 50 KB/s, 5 MB/s, 500 KB/s (fluctuating) | Test progressive upload and abort on slow links |
| Connection loss | Drop after 10 % uploaded, after 90 % uploaded, mid‑chunk | Ensure resumable upload or clean error state |
| Concurrent uploads | 1, 5, 20 simultaneous files from same user | Check server thread pools, rate limits, and lock‑free storage |
| Chunked vs. whole | Disable chunking (single PUT) vs. enable 5 MB chunks | Validate backend reassembly logic |
| HTTP method | POST multipart/form‑data, PUT raw bytes, PATCH (if supported) | Confirm each endpoint behaves correctly |
| Headers | Missing Content-Type, incorrect Content-Length, custom X-Upload-Key | Test strict header validation |
User Context Dimensions
| Context | Values to Test | Rationale |
|---|---|---|
| Authenticated vs. anonymous | Valid token, expired token, no token | Verify authorization enforcement |
| Role‑based limits | Free tier (2 MB), premium (20 MB), admin (unlimited) | Validate quota per role |
| Persona | Curious (explores UI), impatient (rapid clicks), novice (needs hints), power user (keyboard shortcuts), elderly (larger touch targets), accessibility (screen reader, high contrast) | Ensure UI works across interaction styles |
| Device | Mobile portrait, mobile landscape, tablet, desktop, high‑DPI screen | Check responsive layout and touch targets |
| Assistive tech | TalkBack, VoiceOver, NVDA, magnification, switch control | Validate ARIA labels and focus order |
System State Dimensions
| State | Values to Test | Rationale |
|---|---|---|
| Storage health | Empty, near‑quota, full, read‑only volume | Verify graceful degradation |
| Backend load | Idle, moderate CPU, high CPU (stress test), GC pauses | Ensure upload does not exacerbate overload |
| Service dependencies | Virus scanner down, thumbnail worker queue stalled, CDN edge unreachable | Test circuit‑breaker and fallback messages |
| Time‑of‑day | Peak traffic window, off‑peak, scheduled maintenance | Verify rate‑limit windows and back‑off |
| Feature flags | Upload disabled, new storage backend enabled, experimental image processing on | Confirm flag‑gated behavior |
#### Using the Matrix
Pick one value from each dimension to form a test case. For a manageable baseline, start with the happy‑path combination (valid JPEG, 500 KB, 800×600, sRGB, no metadata, good network, authenticated user, idle system). Then systematically vary one dimension at a time while holding others constant to isolate failures. For edge‑case hunting, combine extreme values (e.g., 101 MB file + latency 500 ms + concurrent uploads 20 + storage near‑quota).
How to Test Image Upload: A Complete Guide – Manual Testing Techniques
Even with automation, manual exploratory testing uncovers issues that scripts miss—especially UI glitches, misleading messages, and accessibility problems.
Setting Up a Manual Test Session
- Prepare a file library – Create a folder with representatives from each file‑attribute bucket (size, type, corruption). Name them descriptively (
size_10mb.jpg,corrupt_truncated.png). - Tooling – Use a proxy like Burp Suite, OWASP ZAP, or mitmproxy to view and modify requests in real time. For mobile, configure the device to point to the proxy via Wi‑Fi.
- Session charter – Define a time‑boxed goal (e.g., “test error handling for oversized files across three personas”).
Core Manual Test Procedures
| Step | Action | Expected Observation |
|---|---|---|
| 1 | Navigate to the upload UI (drag‑drop area, click‑to‑select, or paste‑from‑clipboard). | UI shows clear affordance; drag‑drop highlights on hover. |
| 2 | Select a valid small file via file picker. | File name appears, preview thumbnail loads, upload button enables. |
| 3 | Initiate upload. | Progress bar or spinner appears; network request shows multipart/form-data with correct boundaries. |
| 4 | Monitor response. | Server returns 200/201 with JSON containing asset ID and URLs; UI shows success toast. |
| 5 | Verify asset display. | Image renders correctly in gallery, orientation respected, dimensions match original. |
| 6 | Repeat with invalid file type (e.g., .txt renamed to .jpg). | UI shows inline validation error (“Only JPEG, PNG, WebP allowed”) *before* request is sent. |
| 7 | Attempt oversized file (beyond limit). | Either client‑side block (button disabled) or server returns 413/422 with helpful message (“File exceeds 10 MB limit”). |
| 8 | Simulate network loss mid‑upload (using proxy to drop connection after 50 % transferred). | Upload aborts; UI shows retry option or clear failure message; no partial asset left in storage. |
| 9 | Test with accessibility tools enabled (screen reader, high contrast). | All interactive elements have accessible names; live region announces upload status and errors; focus traps are absent. |
| 10 | Try keyboard‑only workflow (Tab to file input, Enter to open dialog, Arrow keys to select file, Space to trigger upload). | All actions reachable; no mouse‑only dependence. |
| 11 | Perform rapid‑click stress (impatient persona): click upload button 10 times quickly. | Only one request sent; UI disables button after first click to prevent duplicates. |
| 12 | Check for hidden fields: inspect network payload for unexpected parameters (e.g., user_id injected from JS). | No extraneous data that could lead to IDOR. |
| 13 | Log out and repeat steps 1‑5 with an anonymous session (if allowed). | Either access denied (403) or upload proceeds under guest policy, per spec. |
| 14 | After upload, attempt to access the asset via a direct URL with tampered filename (path traversal). | Server returns 404 or 403; no directory listing exposure. |
| 15 | Clean up: delete uploaded asset via UI or API; confirm removal from storage and CDN cache (purge if needed). | Asset no longer accessible; storage usage reflects deletion. |
Exploratory Tips
- Use file‑name tricks: try filenames with Unicode emojis, leading/trailing spaces, double extensions (
image.jpg.exe). - Leverage the proxy to manually adjust
Content-Lengthto be smaller than actual payload and observe whether the server truncates or rejects. - Force retries by throttling bandwidth to a trickle and watching whether the client respects
Retry-Afterheaders. - Check for caching headers on the uploaded asset response (
Cache-Control,ETag). Missing headers can cause stale images to be served after replacement.
How to Test Image Upload: A Complete Guide – Automated Testing Strategies
Automation provides repeatability and scale for regression testing, performance baselines, and CI/CD gating. Below are patterns for unit, contract, UI, and load tests, with concrete code snippets.
Unit / Contract Tests (API Layer)
If your upload endpoint is isolated behind a thin controller, write contract tests that assert status codes, response schema, and error messages. Using Pact or OpenAPI validation ensures the contract stays stable.
# test_upload_contract.py
import requests
import jsonschema
from pathlib import Path
UPLOAD_URL = "https://api.example.com/v1/images"
SCHEMA = {
"type": "object",
"properties": {
"id": {"type": "string"},
"url": {"type": "string", "format": "uri"},
"width": {"type": "integer"},
"height": {"type": "integer"}
},
"required": ["id", "url", "width", "height"]
}
def test_valid_jpeg():
files = {"file": ("test.jpg", Path("samples/valid.jpg").read_bytes(), "image/jpeg")}
resp = requests.post(UPLOAD_URL, files=files, headers={"Authorization": "Bearer valid-token"})
assert resp.status_code == 201
data = resp.json()
jsonschema.validate(data, SCHEMA)
assert data["width"] > 0 and data["height"] > 0
def test_oversized_png():
files = {"file": ("big.png", b"x" * (11 * 1024 * 1024), "image/png")} # 11 MB
resp = requests.post(UPLOAD_URL, files=files, headers={"Authorization": "Bearer valid-token"})
assert resp.status_code == 413
assert "exceeds" in resp.json()["error"].lower()
Run these in every PR to catch contract drifts early.
UI Automation (Web)
For web apps, Playwright offers reliable selectors, network interception, and file upload handling.
// upload.spec.js
const { test, expect } = require('@playwright/test');
test.describe('Image upload flow', () => {
test.use({ viewport: { width: 1280, height: 720 } });
test('successful upload shows preview', async ({ page }) => {
await page.goto('https://app.example.com/upload');
const fileChooserPromise = page.waitForEvent('filechooser');
await page.click('button:has-text("Choose photo")');
const fileChooser = await fileChooserPromise;
await fileChooser.setFile('samples/valid.png');
// Intercept request to assert multipart
await page.route('**/v1/images', route => {
const request = route.request();
expect(request.method()).toBe('POST');
const headers = request.headers();
expect(headers['content-type']).toMatch(/multipart\/form-data/);
route.continue();
});
await page.click('button:has-text("Upload")');
await expect(page.locator('.upload-success')).toBeVisible({ timeout: 15000 });
const preview = page.locator('img.preview');
await expect(preview).toHaveAttribute('src', /\/uploads\//);
});
test('oversized file shows inline error', async ({ page }) => {
await page.goto('https://app.example.com/upload');
await page.setInputFiles('input[type="file"]', 'samples/11mb.jpg');
await expect(page.locator('.error-text')).toHaveText(/File exceeds.*10 MB/i);
await expect(page.locator('button:has-text("Upload")')).toBeDisabled();
});
});
Key points:
- Use
waitForEvent('filechooser')to avoid flaky timing. - Intercept the request to validate
Content-Typeand presence of a proper boundary. - Assert UI states (success toast, error messages, disabled button) rather than relying solely on HTTP status.
Mobile Automation (Android)
Appium with the UiAutomator2 driver can drive native or hybrid upload flows.
// UploadTest.java
import io.appium.java_client.android.AndroidDriver;
import org.junit.jupiter.api.*;
import org.openqa.selenium.*;
import java.net.URL;
import java.time.Duration;
public class UploadTest {
private AndroidDriver driver;
@BeforeEach
void setUp() throws Exception {
driver = new AndroidDriver(new URL("http://localhost:4723/wd/hub"),
getCapabilities());
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));
}
@AfterEach
void tearDown() {
if (driver != null) driver.quit();
}
private DesiredCapabilities getCapabilities() {
DesiredCapabilities caps = new DesiredCapabilities();
caps.setCapability("platformName", "Android");
caps.setCapability("deviceName", "Pixel_4_API_33");
caps.setCapability("appPackage", "com.example.app");
caps.setCapability("appActivity", ".MainActivity");
caps.setCapability("automationName", "UiAutomator2");
return caps;
}
@Test
void uploadValidImage() {
driver.findElement(By.id("btn_choose_image")).click();
// Use ADB to push file to device then pick via gallery
Runtime.getRuntime().exec("adb push samples/valid.jpg /sdcard/Pictures/");
Thread.sleep(1000); // allow media scanner
driver.findElement(By.accessibilityId("Gallery")).click();
driver.findElement(By.xpath("//android.widget.CheckedText[@text='valid.jpg']")).click();
driver.findElement(By.id("btn_upload")).click();
WebElement successToast = driver.findElement(By.xpath("//android.widget.Toast[contains(@text,'Upload successful')]"));
Assertions.assertNotNull(successToast);
}
@Test
void uploadOversizedImage() {
driver.findElement(By.id("btn_choose_image")).click();
Runtime.getRuntime().exec("adb push samples/11mb.jpg /sdcard/Pictures/");
Thread.sleep(1000);
driver.findElement(By.accessibilityId("Gallery")).click();
driver.findElement(By.xpath("//android.widget.CheckedText[@text='11mb.jpg']")).click();
WebElement error = driver.findElement(By.id("txt_upload_error"));
Assertions.assertEquals("File exceeds 10 MB limit", error.getText());
Assertions.assertTrue(driver.findElement(By.id("btn_upload")).isAttributeEnabled("enabled") == false);
}
}
- Push test files to the device via
adb push. - Use accessibility IDs for robust locators.
- Validate toast messages and button state.
Load / Stress Testing
To uncover issues that appear only under concurrency or large payloads, employ a tool like k6 or Locust.
// k6 script: upload_load.js
import http from 'k6/http';
import { sleep, check } from 'k6';
import { SharedArray } from 'k6/data';
const files = new SharedArray('test images', () => {
return [
{ name: 'small.jpg', size: 150 * 1024, type: 'image/jpeg' },
{ name: 'medium.jpg', size: 3 * 1024 * 1024, type: 'image/jpeg' },
{ name: 'large.jpg', size: 12 * 1024 * 1024, type: 'image/jpeg' }
];
});
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)<2000'], // 95% of requests under 2 s
http_req_failed: ['rate<0.01'] // <1% errors
}
};
export default function () {
const file = files[Math.floor(Math.random() * files.length)];
const payload = http.file(file.name, open(`./samples/${file.name}`, 'b'), file.type);
const params = {
headers: {
'Content-Type': 'multipart/form-data',
'Authorization': `Bearer ${__ENV.API_TOKEN}`
}
};
const res = http.post('https://api.example.com/v1/images', { file: payload }, params);
check(res, {
'status is 201': (r) => r.status === 201,
'json has id': (r) => r.json('id') !== '',
});
sleep(1);
}
Run with k6 run upload_load.js. Adjust the file array to include corrupted or oversized items to verify error‑rate thresholds.
Performance Benchmarks
Capture timings for each stage:
- Client‑side preparation (file reading, hashing).
- Network transfer (time to first byte, total upload time).
- Server processing (validation, storage write, thumbnail generation).
- CDN propagation (time until asset is publicly reachable).
Use browser dev tools Network tab or curl -w "@format.txt" to output custom timings.
How to Test Image Upload: A Complete Guide – Accessibility and UX Considerations
Image upload is not just a functional feature; it shapes how users perceive the product’s inclusiveness.
WCAG Checkpoints Relevant to Upload
| WCAG 2.1 | Requirement | How to Verify |
|---|---|---|
| 1.3.1 Info and Relationships | Information conveyed through layout must also be available programmatically. | Ensure drag‑drop zone has role="region" and aria-label="Drop image here"; file input is associated with a visible label (). |
| 1.4.3 Contrast (Minimum) | Text and UI components must have contrast ratio ≥ 4.5:1. | Use a contrast analyzer on upload button, error text, and placeholder. |
| 2.1.1 Keyboard | All functionality operable via keyboard. | Tab through the upload flow; ensure file picker can be opened with Enter and files selected with arrow keys. |
| 2.4.3 Focus Order | Focus moves logically and predictably. | After selecting a file, focus should move to the upload button or progress bar, not jump to unrelated sections. |
| 2.4.7 Focus Visible | Keyboard focus must be visible. | Verify a clear outline appears on the upload button when focused. |
| 3.2.1 On Focus | Changing focus does not initiate a change of context. | Opening the file dialog should not submit the form automatically. |
| 3.3.1 Error Identification | Errors must be identified and described in text. | Server validation errors should appear in a visible |
| 3.3.3 Error Suggestion | If possible, suggestions for fixing the error should be provided. | For unsupported file type, message should list allowed types. |
| 4.1.2 Name, Role, Value | Custom controls must have accessible name, role, and state. | If using a custom drag‑drop div, assign role="button" and aria-pressed state changes during drag. |
Manual Accessibility Tests
- Screen reader – Turn on VoiceOver (iOS/macOS) or TalkBack (Android). Navigate to the upload area; listen for announcements like “Choose photo button, collapsed” and after file selection “5 MB JPEG selected”.
- High contrast mode – Enable OS‑level high contrast; ensure the upload button and error text remain distinguishable.
- Reduced motion – If you animate a progress bar, respect the
prefers-reduced-motionmedia query; animation should be disabled or replaced with a static indicator. - Touch target size – On mobile, the upload button should be at least 44 × 44 dp; test with a finger or stylus.
- Voice control – Use dictation commands like “Click choose photo” or “Tap upload” to confirm that voice‑activated interfaces can trigger the flow.
Automated Accessibility Checks
Integrate axe-core into your test suite:
// axe.spec.js
const { test, expect } = require('@playwright/test');
import { injectAxe, checkA11y } from 'playwright-axe';
test.beforeEach(async ({ page }) => {
await injectAxe(page);
});
test('upload page passes WCAG AA', async ({ page }) => {
await page.goto('/upload');
await checkA11y(page, {
rules: [{ id: 'color-contrast', enabled: true }],
detailedReport: true,
detailedReportOptions: { includeNode: true, includeHtml: false }
});
});
Run this in every CI build to catch regressions early.
How to Test Image Upload: A Complete Guide – Security Testing for Image Upload
Image upload is a common attack vector. Malicious actors may attempt to upload scripts, executables, or polyglot files that bypass validation and lead to remote code execution (RCE), stored XSS, or data exfiltration.
Threat Modeling
| Threat | Technique | Mitigation |
|---|---|---|
| File type bypass | Rename .exe to .jpg; double extension image.jpg.php | Validate both extension *and* actual MIME via file signature (magic bytes). |
| Embedded scripts | SVG with tags; EXIF comment containing JavaScript | Sanitize SVG (strip scripts) or reject SVG unless explicitly allowed; strip or ignore EXIF user comments. |
| Polyglot files | JPEG that also contains valid JavaScript (e.g., GIFAR) | Use a strict image processor that re‑encodes the image (e.g., libjpeg‑turbo) discarding non‑image segments. |
| Path traversal | Filename ../../etc/passwd | Sanitize filename; store with UUID or hash; never trust user‑provided name for filesystem paths. |
| Overflow / DoS | Extremely large dimensions (30 000 × 30 000) causing memory exhaustion in decoder | Limit pixel count (width × height) and enforce maximum dimensions before decoding. |
| Upload‑folder execution | Storing uploads in a web‑accessible directory with execute permissions | Store uploads outside web root; serve via a secure CDN or signed URLs; set nosniff header. |
| Race condition | Time‑of‑check‑time‑of‑use (TOCTOU) between validation and move | Perform validation *after* moving file to a quarantine sandbox; use atomic move operations. |
| Metadata leakage | GPS coordinates, device IDs in EXIF | Strip or redact sensitive EXIF fields before storage or display. |
Security Test Cases
| Test | Input | Expected Outcome |
|---|---|---|
| Magic‑byte verification | File with .jpg extension but PNG header | Rejected with 415 Unsupported Media Type |
| Double extension | malicious.jpg.php | Rejected (extension not in allowlist) |
| SVG with script | | Either rejected or script stripped; resulting file must not execute when rendered in browser |
| EXIF script comment | JPEG with EXIF UserComment containing alert(1) | Comment removed; no script appears in DOM when image is displayed |
| Polyglot GIFAR | GIF that also executes as JavaScript when loaded via | Rejected or re‑encoded; resulting file must not be valid JavaScript |
| Filename path traversal | ../../../etc/passwd | Stored as something like a1b2c3d4.pdf; no directory traversal |
| Large dimensions | 30 000 × 30 000 pixel PNG (1 KB file) | Rejected with 400 Bad Request (pixel limit exceeded) |
| Executable content | ELF binary renamed to .png | Rejected; virus scanner flags as malicious |
| Upload folder execution | Upload .html with JS, then attempt to request https://cdn.example.com/uploads/xyz.html | Request returns 403 or serves file with Content‑Disposition: attachment preventing execution |
| Rate limit bypass | Send 100 requests in 1 second from same IP | Server responds with 429 Too Many Requests after threshold |
| Token leakage | Upload endpoint logs file name in plain text | Ensure logs redact or hash filenames; no PII in logs |
Automated Security Checks
- OWASP ZAP active scan – Configure a scan targeting the upload endpoint with the
File Uploadscanner enabled. - Custom script using
ffmpeg/identify– After upload, runidentify -format "%[EXIF:*]" file.jpgto ensure no dangerous tags remain. - Static analysis – Use tools like Bandit (Python) or SpotBugs (Java) to detect dangerous patterns such as
Runtime.execwith user input.
#### Example: Post‑upload sanitization script (Python)
import subprocess, os, json
from PIL import Image
def sanitize_image(path):
# Re‑encode with Pillow to strip all non‑image data
with Image.open(path) as im:
# Force conversion to RGB (drops alpha, CMYK, etc.)
rgb_im = im.convert('RGB')
rgb_im.save(path, format='JPEG', optimize=True)
# Optionally strip EXIF entirely
subprocess.run(['exiftool', '-all=', path], check=False)
# Usage after receiving upload
sanitize_image('/tmp/uploads/abc123.jpg')
Runtime Protection
- Web Application Firewall (WAF) – Deploy rules that block requests with known malicious signatures in multipart bodies (e.g.,
tags). - Content‑Disposition header – Serve uploaded images with
Content-Disposition: attachment; filename*=UTF-8''%{uuid}.jpgto discourage browsers from treating them as HTML. - Subresource Integrity (SRI) – If you ever serve a JSON manifest containing image URLs, include SRI hashes to detect tampering.
How to Test Image Upload: A Complete Guide – Production‑Only Edge Cases
Some defects only manifest when the system runs at scale, with real‑world traffic patterns, or when integrated with third‑party services.
1. CDN Cache Invalidation Race
When an image is overwritten (e.g., user replaces profile picture), the CDN may still serve the stale version until the TTL expires or a purge propagates.
Test:
- Upload image
A(
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