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

128 lines
4.1 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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](pipeline.md) for output file layout.
Read [screenshot.md](screenshot.md) — DOM extraction reuses the same Playwright page.
## Data Model
```js
{
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" }]`