128 lines
4.1 KiB
Markdown
128 lines
4.1 KiB
Markdown
# 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 `<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" }]`
|