Working image
Expected: 200 OK
VALIDATION LAB•IMAGE HANDLING LAB
Date & Files
Validate working, broken, slow, unauthorized, and corrupted image states with fallbacks, loading indicators, retry actions, and layout stability checks.
Scenario
Real products rely on remote avatars, CDN assets, private documents, and uploaded media. QA needs to prove broken images do not collapse layout, hide alt text, or trap users in infinite loading states.
Image Handling Lab
Validate loading, error handling, fallback copy, layout stability, and retry behavior across realistic image states.
Expected: 200 OK

Expected: 404 Not Found
Expected: Delayed 200 OK
Expected: 403 Forbidden
Expected: Corrupted binary
Live widget · interact freely
Successful images render, failing images stop loading and show useful fallback UI with truthful alt text, and slow images do not spin forever.
Automation challenge
Assert the working image loads, the broken image exposes `image-error-2` when the missing-fallback bug is disabled, and the slow image does not keep `image-loading-3` visible forever.
[data-testid="image-handling-lab"][data-testid="image-1"][data-testid="image-2"][data-testid="image-loading-3"][data-testid="image-error-4"][data-testid="image-error-5"][data-testid="image-lab-failed-count"][data-testid="image-retry-2"][data-testid="image-bug-controls"]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. Image labs expose predictable ids for each image, spinner, error panel, and retry button.
1234
await page.goto('https://lab.hakdogan.com/practice/broken-images');
await expect(page.locator('[data-testid="image-1"]')).toBeVisible();
await expect(page.locator('[data-testid="image-loading-3"]')).toBeVisible();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.
12
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');12345678910111213
import { test, expect } from '@playwright/test';
test('image handling lab shows fallbacks for failed images', async ({ page }) => {
await page.goto('https://lab.hakdogan.com/practice/broken-images');
await page.getByTestId('image-bug-missingFallback').uncheck();
await page.getByTestId('image-bug-infiniteSpinner').uncheck();
await expect(page.getByTestId('image-1')).toBeVisible();
await expect(page.getByTestId('image-error-2')).toBeVisible();
await expect(page.getByTestId('image-error-4')).toBeVisible();
await expect(page.getByTestId('image-loading-3')).toBeHidden({ timeout: 3000 });
});Simulated headed-browser flow — no real browser is launched.
Automation-style playback (Playwright-shaped logs). No real browser; no commands run on your machine.
broken-images.spec.ts