# 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 (H1–H6)
- 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 `` 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 ``
**When:** DOM is analysed
**Then:** `structuredData` contains `[{ "@type": "WebSite" }]`