Image Upload Testing

Image Upload Test Cases: Verify the Rendered Result

Verify image uploads beyond HTTP success by testing decoding, EXIF orientation, crop output, generated variants, metadata privacy, cache behavior, accessibility and final browser rendering.

WrightTest TeamApproximately 18 min read
Image upload testing lifecycle from file selection through decoding, orientation, transformation, variants, storage, delivery and final rendering.
Image-upload tests should verify the final rendered artifact, not only file selection or a successful upload response.

The file picker is rarely where the most expensive image-upload defects appear.

A request may succeed while the published image is rotated, cropped around the wrong subject, flattened onto a black background, served from an old cache, or missing one of the responsive variants used on mobile.

That is why an image upload should not pass at HTTP 200, or even when the original object reaches storage. The useful result is the image that the user finally sees.

File selected
→ uploaded
→ decoded
→ validated
→ orientation normalised
→ transformed
→ variants generated
→ stored
→ delivered
→ rendered correctly

This guide covers the image-specific parts of that lifecycle: real image decoding, pixel dimensions, EXIF orientation, crop coordinates, transparency, responsive variants, visual quality, metadata privacy, delivery, caching, accessibility, and browser rendering.

General upload behaviour—network interruption, cancellation, retry, duplicate requests, batch processing, and orphan cleanup—belongs to the parent guide, File Upload Test Cases, at /file-upload-test-cases/.

Define What “Image Ready” Means

Different products promise different image results.

An avatar workflow may generate one square thumbnail. A marketplace listing may preserve the original and create several responsive derivatives. A CMS may apply a focal point differently for hero, card, and mobile layouts.

Before writing test cases, define the final artifact.

Contract areaQuestions to answer
Accepted formatsAre JPEG, PNG, WebP, AVIF, GIF, SVG, HEIC, or other formats supported?
DetectionDoes the server inspect the real image content or trust extension and MIME type?
File sizeWhat are the minimum and maximum byte limits?
Pixel dimensionsWhat are the minimum and maximum width, height, and total pixel count?
Aspect ratioIs any ratio accepted, or must the image be square, portrait, or landscape?
OrientationShould EXIF orientation be applied, preserved, or stripped?
TransformationIs the image cropped, resized, rotated, compressed, or re-encoded?
VariantsWhich thumbnails and responsive sizes must be generated?
TransparencyIs the alpha channel preserved or flattened onto a background?
AnimationAre multiple frames preserved, flattened, or rejected?
MetadataWhich EXIF, GPS, device, author, and colour-profile fields are retained?
StorageIs the original preserved together with derivatives?
DeliveryIs the image public, private, signed, proxied, or delivered through a CDN?
AccessibilityIs alternative text required, optional, or generated elsewhere?
ModerationMust the image pass scanning or review before publication?

A practical completion rule looks like this:

The image is ready when the source has been decoded, every mandatory variant has been generated, the correct artifact is attached to the intended record, and the delivered result renders with the expected orientation, crop, dimensions, and access rules.

This definition matters because image formats do not share one behaviour. JPEG has no alpha channel, PNG supports transparency, and formats such as GIF, WebP, AVIF, and APNG may contain animation or multiple frames. SVG is vector-based and requires a different security policy from ordinary raster images.

Model the Image Processing Lifecycle

The exact state names will vary, but transfer and visual readiness should remain separate.

Empty
  ↓
Selected
  ├── Client rejected
  ↓
Uploading
  ├── Interrupted
  ├── Cancelled
  ↓
Server accepted
  ├── Server rejected
  ↓
Decode pending
  ├── Decode failed
  ↓
Source validated
  ├── Dimension rejected
  ├── Format rejected
  ↓
Orientation normalised
  ↓
Transforming
  ├── Crop failed
  ├── Resize failed
  ├── Re-encode failed
  ↓
Generating variants
  ├── Mandatory variant failed
  ↓
Ready
  ↓
Published, replaced, moderated, expired, or deleted

A 201 Created response may mean that the upload record exists. A 202 Accepted response may mean that processing was queued. Neither proves that the browser can decode the final image or that all required derivatives exist.

The main automated journey should therefore wait for a terminal image state, not a transport response.

Core Image Upload Test Cases

Source Image Validation

Source validation has four separate dimensions:

  1. Is the file allowed?
  2. Can the processing library decode it completely?
  3. Do its dimensions meet the product contract?
  4. Is its shape suitable for the intended use?

Extension, MIME type, signature, and decodable content may disagree. The server should not trust a browser-provided value as its only validation signal. OWASP recommends allowlisting permitted types, validating content, limiting size, controlling filenames and storage, and applying several safeguards rather than relying on one check.

IDTest caseExpected result
IU-001Select a supported imageThe component shows the correct filename and selected state
IU-002Upload each documented image formatEvery supported format reaches the expected terminal state
IU-003Upload a non-image file renamed with an allowed image extensionServer-side validation rejects the content mismatch
IU-004Upload a valid image with an incorrect extensionThe system follows its documented content-detection rule
IU-005Send a valid image with the wrong declared MIME typeThe server validates the content instead of trusting the request header
IU-006Send non-image content with an allowed image MIME typeThe server rejects it before publication
IU-007Upload a truncated JPEGFull decoding fails safely, and the image never becomes ready
IU-008Upload a corrupted PNGProcessing returns a controlled failure rather than remaining pending
IU-009Upload a zero-byte fileThe file is rejected
IU-010Upload an image without a filename extensionThe documented detection rule is applied
IU-011Upload a valid image with an uppercase extensionExtension case does not change the documented result
IU-012Upload a progressive JPEGThe final persisted image decodes and renders correctly
IU-013Upload a grayscale imageThe image remains valid and retains the expected appearance
IU-014Upload a CMYK JPEGThe image is converted, accepted, or rejected according to the contract
IU-015Upload a supported WebP or AVIF imageAll mandatory outputs are generated successfully
IU-016Upload an unsupported HEIC imageThe user receives a format-specific, actionable error
IU-017Upload an image below, at, and above the minimum widthEach boundary follows the documented inclusive or exclusive rule
IU-018Upload an image below, at, and above the minimum heightEach boundary follows the documented rule
IU-019Upload an image below, at, and above the maximum width or heightOversized images are rejected or resized according to the contract
IU-020Upload a highly compressed image with an excessive total pixel countThe system prevents uncontrolled decoding or transformation

Byte size and pixel count solve different problems. A small compressed file can still expand into a large in-memory bitmap.

Use a compact boundary matrix rather than duplicating one test for every individual measurement:

ConstraintBelow boundaryExact boundaryAbove boundary
WidthReject or cropAcceptReject or resize
HeightReject or cropAcceptReject or resize
Total pixel countAcceptAcceptReject before expensive processing
Byte sizeAcceptAcceptReject before upload or processing
Aspect-ratio toleranceReject or open editorAcceptReject or open editor

For the rendered artifact, naturalWidth and naturalHeight expose the image’s intrinsic, density-corrected dimensions in CSS pixels. They are useful browser assertions, although source pixel dimensions may still need verification through processing metadata.

Orientation, Crop, and Editor Output

Phone cameras often store pixels in one orientation and describe the intended display orientation through EXIF metadata. That creates a classic mismatch: the browser preview looks upright while the server-produced derivative appears sideways.

Crop editors add another coordinate system. The displayed image may be scaled by CSS, browser zoom, device pixel ratio, or a responsive container before crop coordinates reach the processing service.

Test the saved pixels, not only the editor overlay.

IDTest caseExpected result
IU-021Upload an image with normal orientationThe result is not rotated
IU-022Upload an image requiring a 90-degree rotationThe persisted image appears upright
IU-023Upload an image requiring a 180-degree rotationThe final orientation is correct
IU-024Upload an image with mirrored orientationMirroring and rotation follow the product contract
IU-025Compare local preview orientation with the stored resultBoth show the same intended orientation
IU-026Strip EXIF after applying orientationThe processed image remains upright without depending on the removed metadata
IU-027Manually rotate an image after automatic orientation correctionRotation is applied once rather than twice
IU-028Replace one oriented image with anotherPreview, metadata, and generated variants update consistently
IU-029Open a landscape image in a square crop editorThe initial crop is valid and visible
IU-030Move the crop to each source edgeThe crop stays within valid image bounds
IU-031Change zoom from minimum to maximumThe selected output region remains valid
IU-032Rotate and crop in the same edit sessionThe saved result reflects the final operation order
IU-033Save a crop while the editor is displayed responsivelyStored crop coordinates map to the intended source region
IU-034Reopen an existing cropThe editor reconstructs the saved position accurately
IU-035Cancel or discard crop changesThe previous persisted image remains unchanged
IU-036Apply the same crop twiceReprocessing is stable and does not accumulate transformations

One automated case does not need to publish every EXIF orientation value as a separate table row. Keep representative groups in the article—normal, rotated, mirrored, and rotated plus mirrored—while covering all supported values in the data-driven fixture set.

The highest-risk crop cases usually place the subject near an edge. A centred stock photo is a poor fixture for detecting coordinate and focal-point defects.

Generated Variants and Visual Fidelity

A single source upload may create several artifacts:

Original
├── Avatar 64×64
├── Avatar 256×256
├── Card 640×360
├── Content 1280×720
└── Full 1920×1080

Each derivative has its own state, dimensions, format, crop rule, and delivery URL.

In practice, “the original works” is not enough. A broken card thumbnail may affect every listing page even though the full-resolution image remains valid.

IDTest caseExpected result
IU-037Generate every mandatory derivativeAll required variants reach Ready
IU-038Verify the width and height of each variantDimensions match the transformation contract
IU-039Resize while preserving aspect ratioThe output is not stretched
IU-040Produce a fixed-size variant from a different source ratioThe documented crop or letterbox rule is applied
IU-041Request a variant larger than the sourceUpscaling follows the documented rule
IU-042Downscale a very large sourceProcessing completes within controlled resource limits
IU-043Generate variants from an EXIF-oriented sourceEvery output uses the correct orientation
IU-044Fail one mandatory derivativeThe parent image does not become fully ready
IU-045Retry one failed derivativeSuccessful variants are not duplicated or re-compressed unnecessarily
IU-046Replace the source imageAll dependent variants are regenerated or versioned correctly
IU-047Preserve PNG or WebP transparencyAlpha remains intact where the output format supports it
IU-048Convert an image with alpha to JPEGThe configured background is applied instead of an accidental black fill
IU-049Crop through a semi-transparent edgeThe result does not introduce dark or coloured halos
IU-050Upload an image with an embedded colour profileThe profile is preserved, converted, or removed according to the contract
IU-051Re-encode a detailed JPEGCompression stays within the accepted visual-quality range
IU-052Resize an image containing fine text or line artImportant content remains legible at the supported size
IU-053Reprocess the same imageOutput remains stable within the defined tolerance
IU-054Render variants at different viewport widthsThe browser receives and displays a valid candidate for each layout

Pixel-perfect comparison is tempting, but it is often the wrong oracle for a pipeline that intentionally re-encodes images. A valid output may differ at the byte and pixel level because of compression, colour conversion, metadata removal, or encoder versions.

Prefer assertions against the actual contract:

  • output format;
  • dimensions;
  • aspect ratio;
  • crop region;
  • orientation;
  • alpha behaviour;
  • required variants;
  • file-size range;
  • expected subject visibility;
  • browser decoding;
  • business attachment.

Responsive images add another detail: srcset defines candidate resources, while currentSrc reports the URL the browser selected. The selected URL alone does not prove successful decoding, but it lets a test identify which derivative was used at a given viewport.

Formats That Need a Separate Policy

Animated images and SVG should not inherit an accidental policy from ordinary JPEG and PNG uploads.

An animated file may contain hundreds of frames. SVG may reference external resources or contain active content. Products that do not need these capabilities are usually easier to test and secure when they reject them explicitly.

IDTest caseExpected result
IU-055Upload a supported animated imageAnimation is preserved and fully decodable
IU-056Upload animation where only still images are allowedThe image is rejected or flattened according to the contract
IU-057Flatten an animated imageThe documented representative frame is used
IU-058Generate a thumbnail from an animated sourceThe selected frame and output dimensions are correct
IU-059Upload an animation with excessive frames or durationProcessing remains within documented limits
IU-060Replace an animated image with a still imagePublished and cached variants update correctly
IU-061Upload a basic SVG when SVG is supportedThe image is sanitised, rasterised, or stored according to policy
IU-062Upload SVG when vector uploads are not supportedThe server rejects it
IU-063Upload SVG containing external referencesReferences are removed, blocked, or rejected
IU-064Upload SVG containing active contentThe file is sanitised or rejected
IU-065Generate a raster derivative from SVGDimensions, transparency, and background are correct
IU-066Deliver the resulting SVG or raster imageResponse content type and access rules match the contract

Deep SVG bypasses, parser exploitation, decompression bombs, and malicious polyglot files belong to the security-focused guide at /file-upload-security-test-cases/.

The functional suite should still prove the product’s declared policy: preserve, flatten, sanitise, rasterise, or reject.

Metadata and Privacy

Image metadata is easy to miss because it does not usually affect the visible preview.

A camera image may contain GPS coordinates, capture time, device model, author information, editing history, orientation, copyright fields, and colour-profile data. Removing metadata from the thumbnail does not prove that the downloadable original is clean.

IDTest caseExpected result
IU-067Upload an image containing standard EXIF metadataOnly permitted fields remain
IU-068Upload an image containing GPS coordinatesLocation data follows the privacy policy
IU-069Upload an image containing camera and device informationDevice metadata is retained or removed as documented
IU-070Upload author or copyright metadataThe platform follows its preservation policy
IU-071Generate derivatives from an image containing sensitive metadataPublic derivatives do not retain prohibited fields
IU-072Download the processed imageIts metadata matches the published contract
IU-073Preserve the original but strip derivativesEach artifact follows its separate policy
IU-074Replace an image containing sensitive metadataOld artifacts follow the retention and deletion rules
IU-075Display metadata values in the interfaceValues belong to the correct image and are safely encoded
IU-076Upload malformed metadata with otherwise valid image pixelsMetadata parsing fails safely without leaving processing stuck

Privacy should be verified against every artifact that users can retrieve:

Original
→ full-size processed image
→ responsive variants
→ thumbnails
→ exported or downloaded copy

A clean thumbnail cannot compensate for an original file that still exposes location data.

Storage, Delivery, and Cache Behaviour

Image replacement defects often live at the cache layer rather than in upload processing.

The database may reference the new object while an old CDN response remains valid. Adding a random cache-busting query to the test can hide the defect instead of detecting it.

IDTest caseExpected result
IU-077Retrieve the ready imageThe response returns an expected image content type
IU-078Retrieve every mandatory variantEach URL returns the intended artifact
IU-079Request the image before processing completesA controlled pending result or placeholder is returned
IU-080Reload after uploadThe page uses a persisted URL rather than a local blob: or data: preview
IU-081Replace the imageThe application and delivery layer return the new version
IU-082Request the previous URL after replacementOld content follows the documented versioning or invalidation rule
IU-083Delete the imageApplication and delivery endpoints stop serving it according to retention policy
IU-084Request a deleted image from a new browser sessionCached client state does not restore access
IU-085Change only the crop or focal pointCache keys or versioned URLs update consistently
IU-086Retrieve the image through different delivery regionsThe new version propagates within the documented window
IU-087Simulate an image-service transformation failureThe UI shows a controlled fallback rather than a permanent broken image
IU-088Request a missing derivativeThe service returns a controlled response or documented fallback
IU-089Inspect cache headersThey match the replacement and versioning strategy
IU-090Request a private image without authenticationAccess is denied
IU-091Request one user’s private image as another userAccess is denied even when the URL is known
IU-092Let a signed image URL expireThe expired URL stops working while a newly issued URL succeeds

Check the browser-selected derivative through currentSrc, then call decode() to verify that the selected image can actually be decoded. currentSrc identifies the chosen candidate; it does not by itself prove successful loading.

Published Image Accessibility

Image accessibility depends on purpose, not file format.

A meaningful image needs a text alternative that conveys its information or function. A decorative image should normally use an empty alternative. Images containing important text require that text to be available in an equivalent form.

IDTest caseExpected result
IU-093Publish a meaningful image without required alternative textPublication is blocked or a clear warning appears
IU-094Enter alternative text and publishThe rendered image exposes the expected alt value
IU-095Edit alternative text after publicationThe updated value appears on the published image
IU-096Mark an image as decorativeThe rendered image uses the documented empty-alt behaviour
IU-097Upload an image containing meaningful textEquivalent text is available through alt text or nearby content
IU-098Use the crop and edit workflow with a keyboardRequired editor controls remain operable
IU-099Display a processing or validation errorThe error is associated with the image-upload control
IU-100Render a missing or broken imageThe text alternative or fallback still communicates the intended meaning

A filename is rarely good alternative text. IMG_8421.jpg and summer-banner-final-v3.png describe storage history, not the image’s purpose on the page.

For complex diagrams, a short alt value may be insufficient. A longer description can be associated with visible page content where needed.

Evidence That the Rendered Image Is Correct

A strong test connects the selected source to the artifact users finally see.

LayerEvidence to collect
Browser selectionSource filename, file size, and selected state
Upload APIUpload identity and accepted or rejected result
DecoderFull source content can be decoded
Metadata serviceDetected format, dimensions, orientation, frames, and metadata
TransformerCrop, resize, rotation, background, and re-encoding result
Variant generatorEvery mandatory derivative reaches a terminal state
StorageOriginal and derivatives exist once with correct ownership
DeliveryCorrect content type, cache policy, access rule, and version
Browser renderingSelected resource decodes and displays with the expected ratio
Business recordCorrect image is attached to the intended user, product, post, or document
AccessibilityAlternative text and editor behaviour follow the content policy
CleanupReplaced, failed, expired, and deleted artifacts follow retention rules
Image upload evidence layers for source selection, upload API, decoder, metadata, transformer, variant generator, storage, delivery, browser rendering, business record, accessibility and cleanup
Rendered image evidence connects generated variants, delivery behavior and browser display back to the original upload contract.

An assertion such as image is visible proves only a small part of this chain.

It does not identify:

  • whether the image came from a local preview or server storage;
  • which responsive variant the browser selected;
  • whether the crop matches the saved coordinates;
  • whether the original still exposes sensitive metadata;
  • whether an old cache supplied the bytes;
  • whether private delivery rules were enforced;
  • whether every required derivative succeeded.

Use different evidence for different risks. Screenshots are valuable for crop, orientation, transparency, overlays, and layout. APIs and metadata are stronger for dimensions, variant completeness, access, storage identity, and processing state.

Image Upload Failures That Look Like Success

The Preview Never Left the Browser

The selected image appears immediately because the page created a local object URL. The upload later fails, but the preview remains visible.

Proof to collect: reload the page and confirm that the rendered image uses a persisted application or delivery URL rather than blob: or data:.

The Original Succeeded but the Card Image Failed

The full-resolution image exists, while a mandatory card or mobile derivative failed during processing.

Proof to collect: wait for every required variant, not only the parent image record.

The Browser Corrected Orientation but the Server Did Not

The local preview honours EXIF orientation. The server strips metadata without rotating the pixels, leaving the persisted image sideways.

Proof to collect: reload and inspect the processed artifact.

The Crop Overlay and Saved Pixels Disagree

CSS scaling or browser zoom changes how editor coordinates map back to the source image.

Proof to collect: verify the generated crop itself. A screenshot of the editor before saving is not enough.

Transparency Was Flattened onto the Wrong Background

A transparent logo looks correct on white but gains dark edges or a black fill after JPEG conversion.

Proof to collect: render the actual derivative against every supported page background.

The New Image Is Stored but the Old One Is Served

Replacement updates the database, but a CDN or browser cache continues returning stale bytes.

Proof to collect: verify the current versioned URL, the old URL’s documented behaviour, and cache headers.

The Public Thumbnail Is Clean but the Original Leaks GPS

Metadata removal runs only during thumbnail generation.

Proof to collect: inspect every publicly downloadable artifact, not just the on-page preview.

A Re-encoded Image Fails a Byte Comparison

The test expects the downloaded bytes to equal the original upload even though the product intentionally compresses or converts the image.

Proof to collect: compare the promised output properties. Byte equality is appropriate only when the original must be preserved unchanged.

Automate the Rendered Result with Playwright

Playwright’s locator-based setInputFiles() can assign one file, multiple files, a directory where supported, clear an input, or provide an in-memory file payload with a name, MIME type, and buffer.

The browser interaction is straightforward. The important part begins after the upload response.

import path from 'node:path';
import { test, expect } from '@playwright/test';

test('uploaded image reaches ready and renders after reload', async ({
  page,
}) => {
  const api = page.context().request;

  await page.goto('/profile');

  await page
    .getByLabel('Profile image')
    .setInputFiles(
      path.join(
        __dirname,
        'fixtures',
        'images',
        'landscape-1200x800.jpg',
      ),
    );

  const [uploadResponse] = await Promise.all([
    page.waitForResponse(
      response =>
        response.url().includes('/api/images') &&
        response.request().method() === 'POST',
    ),
    page.getByRole('button', { name: 'Upload image' }).click(),
  ]);

  expect(uploadResponse.ok()).toBeTruthy();

  const { id } = await uploadResponse.json();

  await expect
    .poll(
      async () => {
        const response = await api.get(`/api/images/${id}`);

        if (!response.ok()) {
          return `http-${response.status()}`;
        }

        const image = await response.json();
        return image.status;
      },
      {
        message: 'image should reach the ready state',
        timeout: 30_000,
      },
    )
    .toBe('ready');

  await page.reload();

  const image = page.getByRole('img', { name: 'Profile image' });

  await expect(image).toBeVisible();

  const result = await image.evaluate(async element => {
    if (!(element instanceof HTMLImageElement)) {
      throw new Error('Expected an HTMLImageElement');
    }

    await element.decode();

    const rect = element.getBoundingClientRect();

    return {
      src: element.currentSrc,
      alt: element.alt,
      naturalWidth: element.naturalWidth,
      naturalHeight: element.naturalHeight,
      renderedWidth: rect.width,
      renderedHeight: rect.height,
    };
  });

  expect(result.src).not.toMatch(/^(blob:|data:)/);
  expect(result.naturalWidth).toBe(1200);
  expect(result.naturalHeight).toBe(800);
  expect(result.renderedWidth / result.renderedHeight).toBeCloseTo(
    1200 / 800,
    2,
  );
});

HTMLImageElement.decode() returns a promise that resolves after the browser has decoded the image and it is safe to render. This makes it a stronger signal than checking that an <img> element merely exists.

The endpoint names and output dimensions in the example are application-specific. If the product intentionally crops or resizes the source, assert the expected derivative rather than the original dimensions.

Verify Orientation Through the Final Artifact

A good orientation assertion describes the business result rather than a particular image library.

import path from 'node:path';
import { test, expect } from '@playwright/test';

test('normalises a portrait image stored with EXIF rotation', async ({
  page,
}) => {
  await page.goto('/profile');

  await page
    .getByLabel('Profile image')
    .setInputFiles(
      path.join(
        __dirname,
        'fixtures',
        'images',
        'exif-orientation-6.jpg',
      ),
    );

  await page.getByRole('button', { name: 'Upload image' }).click();

  await expect(page.getByRole('status')).toContainText(/ready/i);

  await page.reload();

  const image = page.getByRole('img', { name: 'Profile image' });

  const dimensions = await image.evaluate(async element => {
    if (!(element instanceof HTMLImageElement)) {
      throw new Error('Expected an HTMLImageElement');
    }

    await element.decode();

    return {
      width: element.naturalWidth,
      height: element.naturalHeight,
    };
  });

  expect(dimensions.height).toBeGreaterThan(dimensions.width);
});

This test proves that the delivered artifact is portrait-oriented. A processing API can add a stricter assertion against exact dimensions or normalised metadata.

Verify Responsive Candidate Selection

When the page uses srcset, test the resource the browser actually selected.

import { test, expect } from '@playwright/test';

test('uses a valid responsive image on a narrow viewport', async ({
  page,
}) => {
  await page.setViewportSize({
    width: 390,
    height: 844,
  });

  await page.goto('/products/example');

  const image = page.getByRole('img', {
    name: 'Product image',
  });

  const result = await image.evaluate(async element => {
    if (!(element instanceof HTMLImageElement)) {
      throw new Error('Expected an HTMLImageElement');
    }

    await element.decode();

    return {
      currentSrc: element.currentSrc,
      naturalWidth: element.naturalWidth,
      naturalHeight: element.naturalHeight,
    };
  });

  expect(result.currentSrc).toContain('/mobile/');
  expect(result.naturalWidth).toBeGreaterThan(0);
  expect(result.naturalHeight).toBeGreaterThan(0);
});

The exact URL convention is product-specific. Some systems encode width in a path, others use query parameters, immutable asset names, or an image transformation service.

Avoid asserting implementation details that the product does not promise. The stable contract may simply be: a valid candidate was selected, decoded successfully, and is large enough for the rendered slot.

Do Not Turn Every Image Test into a Screenshot Test

Use screenshots where visual judgment is part of the contract:

  • crop boundaries;
  • orientation;
  • transparency;
  • overlays;
  • editor layout;
  • visible compression defects;
  • focal-point placement.

Use structured assertions for:

  • detected format;
  • exact dimensions;
  • metadata removal;
  • processing state;
  • variant completeness;
  • ownership;
  • content type;
  • cache version;
  • signed URL expiration;
  • storage identity.

A small visual suite plus broad metadata and state coverage is usually more stable than hundreds of pixel comparisons.

Build a Controlled Image Fixture Set

Random files from a tester’s laptop create weak, irreproducible coverage.

A useful fixture library records why each file exists and what the test should expect from it.

tests/
└── fixtures/
    └── images/
        ├── formats/
        │   ├── valid-jpeg.jpg
        │   ├── valid-png.png
        │   ├── valid-webp.webp
        │   ├── valid-avif.avif
        │   └── valid-animated.gif
        ├── dimensions/
        │   ├── 1x1.png
        │   ├── square-1000x1000.jpg
        │   ├── landscape-1200x800.jpg
        │   ├── portrait-800x1200.jpg
        │   └── panorama-5000x500.jpg
        ├── orientation/
        │   ├── exif-orientation-1.jpg
        │   ├── exif-orientation-3.jpg
        │   ├── exif-orientation-6.jpg
        │   └── exif-orientation-8.jpg
        ├── transparency/
        │   ├── alpha-full.png
        │   ├── alpha-partial.png
        │   └── transparent-only.png
        ├── metadata/
        │   ├── gps-location.jpg
        │   ├── camera-metadata.jpg
        │   └── icc-profile.jpg
        ├── invalid/
        │   ├── corrupted.jpg
        │   ├── truncated.png
        │   ├── text-renamed-as-image.jpg
        │   └── unsupported.heic
        └── crop/
            ├── subject-centre.jpg
            ├── subject-left-edge.jpg
            └── face-near-top.jpg

Keep expected properties in a manifest rather than encoding every assumption into the filename.

{
  "landscape-1200x800.jpg": {
    "format": "jpeg",
    "width": 1200,
    "height": 800,
    "orientation": 1,
    "animated": false,
    "hasAlpha": false
  },
  "exif-orientation-6.jpg": {
    "format": "jpeg",
    "encodedWidth": 1200,
    "encodedHeight": 800,
    "expectedDisplayWidth": 800,
    "expectedDisplayHeight": 1200,
    "orientation": 6,
    "animated": false,
    "hasAlpha": false
  }
}

The manifest can support browser tests, API tests, processing-service tests, and manual investigation without duplicating the fixture contract.

Useful fixture rules:

  • keep the files deterministic;
  • version them with the tests;
  • avoid production images;
  • use small files where size is irrelevant;
  • generate boundary buffers when committing large binaries is unnecessary;
  • record licensing and source where applicable;
  • include visibly asymmetric subjects for crop and orientation cases.

A solid orientation fixture should make failure obvious. A symmetrical landscape or a plain colour square will not.

The Release-Gating Image Journey

Do not begin by automating all 100 cases independently.

Start with one representative image and prove the complete path:

  1. the browser selects the expected source;
  2. the server accepts it once;
  3. the decoder reads the full content;
  4. orientation is normalised;
  5. the required crop or resize is applied;
  6. every mandatory derivative reaches Ready;
  7. the correct image is attached to the intended record;
  8. the page uses a persisted URL after reload;
  9. the browser decodes the delivered artifact;
  10. the displayed ratio and orientation are correct;
  11. another user cannot retrieve a private image;
  12. replacement delivers the new version;
  13. deletion removes access according to the retention policy.

Once that path is reliable, vary the data rather than duplicating the entire journey:

  • format;
  • dimensions;
  • aspect ratio;
  • orientation;
  • transparency;
  • animation;
  • crop coordinates;
  • colour profile;
  • metadata policy;
  • user role;
  • transformation rules;
  • expected variants.

With WrightTest, the visible browser flow can remain stable while the fixture, account, transformation policy, and terminal assertions change. The value is not a longer collection of scripts. It is one reusable image journey that connects the user’s selection to the artifact the application ultimately publishes.

Sources

  • MDN — Image file type and format guide: browser image formats, transparency, animation, compression, and format capabilities.
  • **MDN — HTMLImageElement.decode():** browser decoding as an observable promise.
  • **MDN — naturalWidth:** intrinsic density-corrected rendered dimensions.
  • **MDN — currentSrc and srcset:** responsive candidate selection.
  • Playwright — Locator API: uploading paths, multiple files, directories, clearing inputs, and in-memory file payloads.
  • OWASP — File Upload and Input Validation Cheat Sheets: layered server-side validation, size, content, storage, and safe delivery.
  • W3C WAI — Images Tutorial and H37/H67: informative, decorative, functional, and text-containing image alternatives.