# 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" }]`