Files
website-evaluator/spec/dom-analysis.md
2026-04-17 23:52:40 +12:00

4.1 KiB
Raw Permalink Blame History

DOM Analysis Spec (WE-D)

Overview

Extracts structured metadata from a loaded page using Playwright's page.evaluate(). Provides the page structure data used in the report's "Page Structure" section. Runs in the same browser session as the screenshots to avoid a second page load.

Responsibilities

  • Extract title, meta tags, canonical URL, Open Graph tags
  • Count and validate heading hierarchy (H1H6)
  • Audit images for missing alt text
  • Audit links for external targets missing rel="noopener"
  • Detect JSON-LD structured data
  • Return a plain object (serialisable to JSON)

Delegates to: Playwright page.evaluate()

Dependencies

Read pipeline.md for output file layout. Read screenshot.md — DOM extraction reuses the same Playwright page.

Data Model

{
  title: 'Example Domain',
  titleLength: 14,

  meta: {
    description: null,           // string or null
    descriptionLength: null,     // number or null
    robots: 'index, follow',     // string or null
    viewport: 'width=device-width, initial-scale=1',  // string or null
  },

  canonical: null,               // string URL or null

  openGraph: {
    title: null,
    description: null,
    image: null,
    type: null,
  },

  headings: {
    h1: ['Example Domain'],      // array of text content
    h2: [],
    h3: [],
    h4: [],
    h5: [],
    h6: [],
    skips: [],                   // e.g. ['H1→H3'] if H2 is absent between H1 and H3
  },

  images: {
    total: 12,
    missingAlt: 3,               // count with no alt attribute or alt=""
    missingAltSrcs: ['...'],     // up to 5 example src values
  },

  links: {
    total: 24,
    external: 8,
    externalNewTab: 4,           // target="_blank"
    externalNewTabNoOpener: 3,   // target="_blank" without rel containing noopener
  },

  structuredData: [],            // array of parsed JSON-LD objects (empty if none)
}

Requirements

WE-D-1: Extract metadata in a single page.evaluate() call to minimise round-trips. All DOM access happens inside the browser context.

WE-D-2: meta.description is null if the <meta name="description"> tag is absent or has an empty content attribute.

WE-D-3: Heading hierarchy skips are detected by comparing sequential heading levels. If the level jumps by more than 1 (e.g., H1 → H3), record the skip as "H1→H3" in headings.skips. Why: Heading skips confuse screen readers and weaken SEO.

WE-D-4: An image is counted as missingAlt if it lacks the alt attribute entirely OR if alt is an empty string "". Why: Both mean the image is inaccessible to screen readers.

WE-D-5: A link is counted as externalNewTabNoOpener if it has target="_blank" AND its rel attribute does not contain noopener. Why: Without noopener, the opened page can access window.opener — a security risk.

WE-D-6: Structured data is extracted from all <script type="application/ld+json"> tags. Parse each; if parsing fails, include the raw text string instead of the object.

WE-D-7: The missingAltSrcs array contains at most 5 example src values (truncated to 80 chars each) to keep the report concise.

WE-D-8: The result must be JSON-serialisable (no DOM nodes, functions, or circular refs).

WE-D-9: Run on the desktop viewport page (the same page used for the desktop screenshot). Do not navigate again.

Scenarios

Scenario: Well-structured page

Given: Page with one H1, sequential headings, all images have alt, no blank-target links When: analyzeDom(page) is called Then: headings.skips is empty, images.missingAlt === 0, links.externalNewTabNoOpener === 0

Scenario: Missing meta description

Given: Page with no <meta name="description"> tag When: DOM is analysed Then: meta.description === null, meta.descriptionLength === null

Scenario: Heading skip detected

Given: Page with H1 then H3 (no H2) When: DOM is analysed Then: headings.skips includes "H1→H3"

Scenario: JSON-LD present

Given: Page with <script type="application/ld+json">{"@type":"WebSite"}</script> When: DOM is analysed Then: structuredData contains [{ "@type": "WebSite" }]