PRACTICAL GUIDE / image alt text testing

An alt attribute can pass while the image still fails

Learn to classify decorative, informative, functional, and complex images, then test each rendered alternative against its purpose and surrounding task.

By The Testing AcademyUpdated August 4, 202619 min read
All field guides
In this guide6 sections
  1. Why checking for an alt attribute gives false confidence
  2. How purpose changes the correct alternative
  3. Three pages that need three different oracles
  4. How to prove which failure you actually found
  5. How to roll the fix through an existing content system
  6. When an automated alt check is the wrong tool

What you will learn

  • Why checking for an alt attribute gives false confidence
  • How purpose changes the correct alternative
  • Three pages that need three different oracles
  • How to prove which failure you actually found

The product gallery is full of alt attributes, yet a screen reader announces three items as “product image” and skips the color that distinguishes them. A presence check reports that every image is covered. A customer choosing the blue model still cannot tell it from the black one.

Why checking for an alt attribute gives false confidence

An alt attribute has three materially different states. It can be absent, present with an empty value, or present with text. Those states do not map to fail, fail, and pass. An intentional empty value is correct for many decorative images. Non-empty text is required for many informative images, but the words can still be useless, duplicated, stale, or aimed at the wrong purpose.

The decision begins with what would be lost if the image were unavailable, not with what the file contains. A diagonal flourish beside a heading may add no information. A photo in an online shop may distinguish a product variant. A magnifying-glass icon inside an otherwise unnamed button represents an action. A chart may carry dozens of values and a trend that cannot fit in one short phrase. All four can be PNG files with an img element, yet they need different alternatives.

That is why a single rule such as every image must have non-empty alt text creates new defects while appearing strict. It makes decorative images noisy. It encourages content authors to paste filenames into fields. It can give a functional icon a description such as magnifying glass when the user needs Search. It can compress a complex chart into a vague sentence and remove the data required to understand it.

WCAG 2.2 Success Criterion 1.1.1, Non-text Content, is Level A. Its core requirement is a text alternative that serves the equivalent purpose, with specific cases for controls, time-based media, tests, sensory experiences, CAPTCHA, and decoration. The phrase equivalent purpose is the useful oracle. Merely counting attributes is an implementation check beneath that outcome.

Context can change the answer for the same asset. A portrait in an employee directory may need the person's name because it identifies the record. The same portrait next to a heading that already names the person may be redundant and can be treated as decorative if it adds nothing else. A warning icon beside visible text saying Payment failed may need an empty alternative, while the same icon used alone in a status column needs a meaningful text equivalent.

Separate the content decision from the markup check. Product or content owners should state the image's purpose and the facts or function that must survive without pixels. Accessibility review confirms that decision. Automation then verifies that the rendered page implements it. Asking a test script to infer the purpose from a filename or run image recognition reverses that ownership and produces confident nonsense.

The most useful inventory therefore records more than source and alt. Include a stable content identifier, page context, category, surrounding text, enclosing link or button, approved meaning, owner, and review date. That data lets a reviewer see why one empty value is deliberate and another is a regression.

How purpose changes the correct alternative

The W3C WAI Images Tutorial groups common cases into informative, decorative, functional, images of text, complex images, groups, and image maps. These are reasoning aids, not labels that a browser assigns for the team. A tester still has to classify the image as it is used on the page.

Informative images communicate a concept or fact. Their alternative should convey the essential information in that context. A product photo might need model, color, and view when those details affect selection. It usually does not need a list of every visible pixel. A headshot used only as a visual complement to an already named profile may not add information at all. The boundary comes from the task.

Decorative images add visual treatment without adding content. The decorative image guidance uses a null alternative, written as alt="", so assistive technology can ignore the image. Leaving the attribute out is not equivalent markup. A missing attribute can lead user agents to expose unhelpful information such as the file location, while an empty value is an intentional author decision.

Functional images participate in a link or control. Their text alternative describes the action or destination rather than their appearance. A printer icon used as a button needs Print this invoice, not Small printer. A company mark used as the only content of a home link needs a name that communicates the home destination. If the enclosing button already has a reliable accessible name, the nested icon can usually have alt="" so it does not add duplicate words.

Images of text should preserve the words when the image is necessary, while WCAG also has separate criteria about using real text where possible. Logos are a familiar exception to replacing all stylized text. For testing, compare the meaningful words, not capitalization embedded in the artwork unless capitalization itself conveys required information.

Complex images carry more than a short alternative can reasonably express. The WAI complex images tutorial describes a two-part approach: a short description identifies the image, and a longer description presents the essential information. A chart can be followed by a visible data table, a structured description, or a clearly located long description. The best implementation depends on the page, but the user must be able to reach equivalent facts.

An icon is not automatically decorative or functional. A green tick next to a visible Passed label is redundant. A green tick alone in a results grid conveys status and needs a text equivalent. The same asset can switch category when the adjacent text changes. That is why component tests should render both contexts instead of assigning one permanent rule to check.svg.

Accessible names add another layer. For an image link, the image's alternative can contribute the link name. For an icon button with aria-label on the button, that label determines the button's accessible name before name-from-content is considered. The empty child alternative instead marks the icon as redundant with the named action. Test the enclosing control by role and name, then inspect the child alt only when its treatment is part of the chosen implementation.

Do not require phrases such as image of or picture of by default. Assistive technology generally identifies an image as an image, and repeated type words add noise. There are contexts where the medium matters, such as distinguishing a photograph from an illustration in an art archive. State that need explicitly rather than applying a universal wording rule.

Three pages that need three different oracles

The first worked example is a product chooser for a two-person tent. Three cards share the same visible product name and price. The photos show blue, black, and orange variants, but the original alternatives all say Trail tent product image. The page passes missing-alt scans, and each image can be found by its alternative. The alternatives still remove the only distinction a non-visual customer needs.

The content contract for this page says color and viewing angle affect the purchase decision. The approved alternatives therefore include those facts. Exact text is reasonable here because the alternatives are controlled catalog data, not free editorial copy. The test iterates stable product image identifiers and verifies the rendered values.

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

const productImages = [
  { id: 'trail-2-blue-front', alt: 'Blue Trail 2 tent, front view' },
  { id: 'trail-2-black-front', alt: 'Black Trail 2 tent, front view' },
  { id: 'trail-2-orange-side', alt: 'Orange Trail 2 tent, side view' }
];

test('variant photos preserve the purchase distinctions', async ({ page }) => {
  await page.goto('/products/trail-2');

  for (const image of productImages) {
    await expect(page.locator('[data-image-id="' + image.id + '"]'))
      .toHaveAttribute('alt', image.alt);
  }
});

This oracle can fail when catalog data loses a color, when two variants receive the same alternative, when localization returns the wrong value, or when the UI renders an old asset record. It is not proving that any non-empty string is good. It compares output with an independently reviewed requirement.

The cost is editorial coupling. A harmless change from front view to viewed from the front fails the test. Some teams accept that cost for regulated or high-value content. Others store required concepts, such as color and model, and allow wording variation. Do not reduce the assertion to alt is not empty unless emptiness is the only risk you intend to cover.

The second example contains two functional images with opposite markup choices. The company logo is the only child of a link to the home page, so its alternative supplies the useful link name. A trash-can icon sits inside a button that already has aria-label="Remove Alpine mug"; the icon receives an empty alternative. Under the Accessible Name and Description Computation 1.2 algorithm, aria-label determines this button's accessible name before name-from-content processing, so the child's alt text is not concatenated into the button name. Empty alt remains correct because the icon is redundant with the explicitly named Remove action, not because its text would repeat in this name computation.

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

test('image controls expose destination and action names', async ({ page }) => {
  await page.goto('/cart');

  const home = page.getByRole('link', {
    name: 'The Testing Academy home',
    exact: true
  });
  await expect(home).toHaveAttribute('href', '/');
  await expect(home.locator('img')).toHaveAttribute(
    'alt',
    'The Testing Academy home'
  );

  const remove = page.getByRole('button', {
    name: 'Remove Alpine mug',
    exact: true
  });
  await expect(remove).toBeVisible();
  await expect(remove.locator('img')).toHaveAttribute('alt', '');
});

The role locators are doing more than finding convenient nodes. They verify that a user-facing destination and action are available as control names. The child assertions then protect the implementation decision. If visible button text is later added, the team can remove aria-label and reassess the icon. Tests should follow the resulting name, not preserve an obsolete labeling technique.

A deceptive near-miss occurs when the test locates the trash image by getByAltText and fails after alt is intentionally emptied. That is not evidence that the control became inaccessible. The meaningful object is the button, so its role and name are the primary oracle. A DOM-level empty-alt check supports the decorative treatment of the nested icon.

The third example is a quarterly revenue chart. Its original alt says Bar chart. That identifies a type but none of the information. A replacement paragraph says Revenue increased, which still omits region values and hides that one region declined. The product requirement says users must be able to compare quarterly totals by region and identify the direction of change.

The implementation uses a short alternative to identify the chart and a visible table with the same quarterly values. The controlled fixture below is illustrative: its amounts are test inputs, not measurements from a real report. The test checks the alternative and every rendered table value. It does not attempt to prove visual bar heights or color contrast; those are separate risks with different evidence.

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

test('revenue chart has a short identity and a complete data equivalent', async ({ page }) => {
  await page.goto('/reports/revenue');

  const chart = page.locator('[data-chart-id="revenue-by-region"]');
  await expect(chart.locator('img')).toHaveAttribute(
    'alt',
    'Quarterly revenue by region. Data table follows.'
  );

  const table = chart.getByRole('table', {
    name: 'Quarterly revenue by region'
  });
  await expect(table).toBeVisible();
  await expect(table.getByRole('columnheader')).toHaveText([
    'Region',
    'Q1',
    'Q2',
    'Q3',
    'Q4'
  ]);
  const rows = table.locator('tbody tr');
  await expect(rows).toHaveCount(2);
  await expect(rows.nth(0).locator('th, td')).toHaveText(
    ['North', 'INR 120,000', 'INR 130,000', 'INR 140,000', 'INR 150,000'],
  );
  await expect(rows.nth(1).locator('th, td')).toHaveText(
    ['South', 'INR 110,000', 'INR 115,000', 'INR 105,000', 'INR 100,000']
  );
});

In the real suite, expected amounts should come from the report requirement or a controlled seed, not from the same API response the page renders. Deriving both the UI and the expected table from one response creates an oracle that will agree with a wrong backend calculation.

The table costs page space and content maintenance, although it also helps sighted users who need exact figures. A linked long-description page reduces clutter but adds navigation and can drift from the chart. A hidden description may be harder to discover and review. Choose one model, assign an owner to both representations, and test the association and facts that model promises.

How to prove which failure you actually found

Start with a raw inventory. The DOM method getAttribute returns null when the requested attribute is absent and otherwise returns its value. It therefore distinguishes a missing alt from an intentional empty value. Capture the attribute directly.

This Playwright test prints a compact inventory and fails only on missing attributes. It is a diagnostic layer, not a quality verdict. A content reviewer uses the page, context, enclosing control, and raw value to classify the remaining rows.

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

test('capture the rendered image inventory', async ({ page }) => {
  await page.goto('/catalog');

  const inventory = await page.locator('img').evaluateAll(
    (images: HTMLImageElement[]) =>
      images.map((image, index) => ({
        index,
        src: image.currentSrc || image.getAttribute('src'),
        alt: image.getAttribute('alt'),
        parent: image.parentElement?.tagName ?? null,
        parentLabel: image.parentElement?.getAttribute('aria-label') ?? null
      }))
  );

  console.table(inventory);
  expect(inventory.filter((image) => image.alt === null)).toEqual([]);
});

The explicit HTMLImageElement[] annotation is not decoration. Playwright types evaluateAll callbacks as SVGElement | HTMLElement, not as the element the locator happens to select, so an unannotated image.currentSrc fails tsc --noEmit under strict mode with TS2339: the property does not exist on HTMLElement. The test still runs, because Playwright transpiles specs with esbuild and esbuild strips types without checking them, which is precisely why the mistake survives a green suite and lands on whoever added a type-check gate later. Annotate the parameter, or cast inside the callback, and the same code compiles and runs.

When that assertion fails, inspect the rendered node rather than the source template alone. A content management system may omit the field, a frontend mapping may turn null into no attribute, or client hydration may replace correct server markup with an incomplete component. The trace DOM snapshot before and after hydration tells those paths apart.

When a role locator cannot find an image with alt="", that can be expected. Null alternatives make images presentational in common accessibility mappings. Confirm that the img node exists with a CSS locator and that its attribute is exactly empty. Do not set includeHidden or add a fake name just to satisfy a locator designed for semantic elements.

Broken loading is a separate failure. An image can return a network error while retaining excellent alternative text. The alternative reduces the accessibility impact but does not make the broken product image acceptable. Check naturalWidth or the relevant network response when the defect is asset delivery. Check alt and accessible names when the defect is equivalent meaning. One ticket can contain both, but the oracles should remain distinct.

Localization failures leave recognizable evidence. The English page may render a reviewed alternative, while another locale exposes a translation key such as catalog.trail2.blue.alt or falls back to Product image. Capture locale, content identifier, rendered value, and the translation record. A screenshot alone cannot show what a screen reader receives, and an attribute dump alone may not reveal that the visible product variant is different.

Responsive image selection can create a quieter mismatch. The picture element may choose a different source for a narrow viewport while the img element keeps one alternative. That is correct when every source is an art-directed version of the same information. It is wrong when the mobile source changes the subject, crop, status, or product variant enough that the approved words no longer describe it. Reproduce the target viewport, record currentSrc beside the raw alt value, and compare both with the content contract. Do not write a test that expects one fixed currentSrc across browsers, because source selection legitimately depends on viewport, pixel density, supported formats, and the declared source candidates. The stable oracle is that whichever visual variant the browser selects still serves the meaning represented by the alternative. If art direction changes that meaning, the component may need contextual content instead of one shared img fallback.

Lazy loading produces a related timing clue. A placeholder can carry alt="", then be replaced by an informative product image after intersection or data loading. If the final node never receives its approved alternative, a test run at the top of the page can miss the defect. Scroll the card into view, wait for the product state rather than a fixed delay, and capture both the placeholder and final DOM snapshots. This distinguishes an intentional loading skeleton from an informative image that remains hidden from non-visual users.

Another near-miss is duplicate nearby text. An image can have a precise alternative that repeats an adjacent caption word for word. Repetition may be acceptable in some structures, but often it creates needless announcements. Review the entire component in reading order. An automated exact-alt assertion cannot decide whether the neighboring copy now makes that value redundant.

CSS background images need a different inspection path. They have no img alt attribute. If the background is decoration, that may be exactly right. If it carries a status, label, or product information with no textual equivalent, adding an alt rule to img elements will never find it. Compare visual assets with the content inventory and inspect pseudo-elements, inline styles, and component requirements when counts do not reconcile.

Filename-based output is a strong clue, not a universal assertion. Some user agents may expose a source when alt is missing, but tests should not depend on one exact spoken fallback across every browser and screen reader. The stable defect is that the author-supplied alternative is absent. Record the assistive-technology announcement only for the supported combination being manually tested.

How to roll the fix through an existing content system

Begin with the highest-risk contexts: image-only controls, linked images, product choices, instructional diagrams, charts, status icons, and content required to finish a transaction. Decorative flourishes are worth classifying, but they should not delay a fix for an unnamed Delete control.

Add purpose to the content model. A single optional alt field forces authors to infer too much. Useful models store an image category, the required alternative or function, a long-description reference where needed, locale status, and ownership. Do not expose all of that complexity to every author if the component can supply a safe default. An icon button component should require an accessible name and make its icon decorative by construction.

Migrate in reviewed batches. First produce an inventory that distinguishes missing, empty, and non-empty values. Next have content and accessibility reviewers classify the page context rather than only the media library asset. Then change templates or records and run focused browser checks. Finally sample the result with supported assistive technology, especially where control names or long descriptions are involved.

Media-library defaults are dangerous during migration. The same uploaded logo might serve as informative content on a brand-history page, the only content of a home link, and decoration in a footer. Storing one universal alt string on the asset encourages incorrect reuse. Allow a contextual override, or store the asset's factual identity separately from the alternative chosen by each component.

CI can enforce stable structure without pretending to approve prose. Run missing-attribute checks broadly. Run exact alternatives for controlled catalog or legal content. Test controls by role and accessible name. Verify that known complex images expose their approved description or table. Route new or changed editorial alternatives to human review rather than snapshotting every sentence.

A small dedicated project can retain diagnostic evidence without slowing every browser matrix:

TypeScript
import { defineConfig } from '@playwright/test';

export default defineConfig({
  testDir: './tests/image-accessibility',
  workers: 2,
  retries: 0,
  reporter: [['line'], ['html', { open: 'never' }]],
  use: {
    baseURL: process.env.BASE_URL ?? 'http://127.0.0.1:3000',
    trace: 'retain-on-failure',
    screenshot: 'only-on-failure'
  }
});

The rollout has real costs. Content classification requires judgment. Localized alternatives add translation work. Exact assertions create maintenance when approved wording changes. Long descriptions can drift from their graphics. Storing review metadata complicates the CMS. Those costs are smaller when ownership is explicit and checks match risk, but they do not disappear.

Do not measure success by the percentage of non-empty attributes. Track unresolved high-risk images, unnamed functional controls, missing complex equivalents, stale localized content, and defects found by users or manual reviews. A decorative page can correctly have many empty values, so a non-empty percentage can move in the wrong direction after a good cleanup.

When an automated alt check is the wrong tool

Do not ask automation to write or approve meaning from pixels. Computer vision can help an author discover possible objects, but it does not know which fact matters in a specific checkout, lesson, medical result, or chart. Treat generated wording as a draft requiring ownership, not as an accessibility oracle.

Avoid exact-string assertions for frequently edited prose unless wording itself is controlled. A test that fails on punctuation but misses an omitted business fact spends maintenance in the wrong place. For those pages, assert presence and linkage mechanically, then use a review workflow with stated content questions.

Never reject alt="" without classifying the image. Empty alternatives are a deliberate part of accessible HTML for decoration and for icons whose enclosing control already has a name. Replacing every empty value with a filename makes the experience worse.

Do not use the nested image as the main oracle for a link or button. Test the interactive element's role, name, destination, and behavior. Inspect the child only to verify how the component builds that name or suppresses redundant content.

Skip a long description when the same information is already available clearly in adjacent text and the image adds no additional facts. Duplicating a full table solely to satisfy a template can create two sources that drift. Document where the equivalent lives and test that users can reach it.

Finally, do not confuse alternative text with the entire accessibility review. A chart may have an excellent data table and still rely on indistinguishable colors for sighted users. A product image may have precise wording and fail to zoom. An icon button may be named and too small to operate. Keep the oracle tied to the loss you are investigating, then open separate defects for separate user barriers.

// FIELD DISPATCH

Get the QA Field Notes

Weekly QA battles, AI testing guides, and interview drills. Free on Substack.

The Testing Academy editorial desk

Practical QA guidance built around test evidence, production tradeoffs, and interview-ready explanations.

Published July 26, 2026 / Reviewed August 4, 2026

PRIMARY REFERENCES

Verify the details at the source

QABattle guides are practical explanations. Product behavior, standards, and APIs can change, so use these primary references for the canonical details.

  1. 01
    Official w3.org reference

    w3.org

    Primary documentation selected and verified for the claims in this guide.

  2. 02
    Official w3.org reference

    w3.org

    Primary documentation selected and verified for the claims in this guide.

  3. 03
    Official w3.org reference

    w3.org

    Primary documentation selected and verified for the claims in this guide.

  4. 04
    Official w3.org reference

    w3.org

    Primary documentation selected and verified for the claims in this guide.

FAQ / QUICK ANSWERS

Questions testers ask

Is an empty alt attribute always an accessibility bug?

No. An empty alt value is the correct treatment for an image that adds no information or function in its context. The defect is using that treatment for an image whose meaning would then disappear.

How can automation tell whether alt text is actually useful?

Automation can enforce an approved content contract for known images and catch missing, empty, stale, or generic values. Human review still decides whether the words convey the image's purpose in the page and task.

Should a linked logo be described as a logo?

Describe the destination or function of the link, not merely the visual type. A home link needs a name that tells the user where activating it goes.

What should a test capture when a chart is inaccessible?

Record the image source, short alternative, nearby description or data table, visible caption, and the exact facts unavailable without the graphic. That evidence distinguishes an absent equivalent from a disagreement about wording.

Can a browser locator find images with empty alt text?

Role-based lookup may exclude an image that has null alternative text because it is treated as presentational. Inspect the DOM attribute separately when the test is verifying an intentional empty value.