M1: initial scaffold — pipeline, specs, source, Dockerfile
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
127
spec/dom-analysis.md
Normal file
127
spec/dom-analysis.md
Normal file
@@ -0,0 +1,127 @@
|
||||
# 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" }]`
|
||||
Reference in New Issue
Block a user