Skip to content
AgileLogics
6 min read

Testing and Validating HTML

Prerequisites: Accessibility basics

Browsers never tell you your HTML is wrong. Tools do — and they're easy to run automatically.

The Nu Html Checker on the command line

The checker behind validator.w3.org/nu is a Java program, vnu.jar, available from the validator project's GitHub releases (and as the npm package vnu-jar):

java -jar vnu.jar index.html about.html
java -jar vnu.jar --errors-only --skip-non-html dist/
java -jar vnu.jar --also-check-css --format json index.html

It exits with a non-zero status when it finds errors, so it can fail a CI build. Given the file below, it reports the messages that follow it (message text only; the real output also gives the file name, line, and column):

<!-- Wrong: three mistakes -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="utf-8">
    <title>Test</title>
  </head>
  <body>
    <main>
      <h1>Test</h1>
      <img src="a.png">
      <a href="/"><button>Home</button></a>
      <label for="missing">Name</label>
    </main>
  </body>
</html>
error: An 'img' element must have an 'alt' attribute, except under certain conditions. For details, consult guidance on providing text alternatives for images.
error: The element 'button' must not appear as a descendant of the 'a' element.
error: The value of the 'for' attribute of the 'label' element must be the ID of a non-hidden form control.

The third message is about the label whose for points to an id that doesn't exist.

Linters

  • html-validate (npm) — runs in Node.js with configurable rules, including many accessibility and best-practice checks, and supports template files.
  • HTMLHint — a lighter linter with editor integrations.
  • Framework linters: eslint-plugin-jsx-a11y (React), eslint-plugin-vuejs-accessibility.

Automated accessibility tests

axe-core (by Deque) finds problems like missing names, bad contrast, and invalid ARIA. Run it as a browser extension, with Lighthouse, or in end-to-end tests. With Playwright:

npm install --save-dev @playwright/test @axe-core/playwright
import { test, expect } from "@playwright/test";
import AxeBuilder from "@axe-core/playwright";

test("home page has no detectable accessibility violations", async ({ page }) => {
  await page.goto("http://localhost:8000/");
  const results = await new AxeBuilder({ page }).analyze();
  expect(results.violations).toEqual([]);
});

Automated tools find perhaps a third of accessibility problems. Keyboard testing and screen-reader testing are still necessary.

Test the behavior, not just the markup

  • Tab through every page; operate every widget with the keyboard.
  • Check the heading outline and landmarks (browser extensions list them).
  • Test forms: validation messages, error recovery, autofill.
  • Test with images and CSS turned off — the content should still make sense in order.
  • Zoom to 200% and 400%.
  • Query elements by role and name in tests (page.getByRole("button", { name: "Save" })): if a test can't find a button by its accessible name, neither can a screen-reader user.

Related developer tools