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" }]`
|
||||
123
spec/lighthouse.md
Normal file
123
spec/lighthouse.md
Normal file
@@ -0,0 +1,123 @@
|
||||
# Lighthouse Spec (WE-L)
|
||||
|
||||
## Overview
|
||||
|
||||
Runs Lighthouse audits against a URL in two presets (desktop and mobile) and
|
||||
extracts structured findings. Returns a normalised result object used by the
|
||||
report assembler. Also saves the full Lighthouse JSON to the raw directory for
|
||||
reference.
|
||||
|
||||
## Responsibilities
|
||||
|
||||
- Launch Chrome via chrome-launcher using Playwright's bundled Chromium
|
||||
- Run a desktop Lighthouse audit
|
||||
- Run a mobile Lighthouse audit
|
||||
- Extract scores, Core Web Vitals, and categorised audit findings
|
||||
- Save raw results to the raw directory
|
||||
- Return structured results for report assembly
|
||||
|
||||
Delegates to: Lighthouse, chrome-launcher, Playwright (for executable path)
|
||||
|
||||
## Dependencies
|
||||
|
||||
Read [pipeline.md](pipeline.md) for output directory layout.
|
||||
|
||||
## Data Model
|
||||
|
||||
### LighthouseResult (returned per preset)
|
||||
|
||||
```js
|
||||
{
|
||||
preset: 'desktop' | 'mobile',
|
||||
scores: {
|
||||
performance: 87, // 0-100
|
||||
accessibility: 92,
|
||||
bestPractices: 100,
|
||||
seo: 90,
|
||||
},
|
||||
vitals: {
|
||||
lcp: { value: '1.2 s', status: 'good' }, // good | needs-improvement | poor
|
||||
tbt: { value: '120 ms', status: 'good' },
|
||||
cls: { value: '0.02', status: 'good' },
|
||||
ttfb: { value: '380 ms', status: 'needs-improvement' },
|
||||
fcp: { value: '0.8 s', status: 'good' },
|
||||
si: { value: '1.1 s', status: 'good' },
|
||||
},
|
||||
findings: {
|
||||
critical: [ { id, title, description, impact } ], // score === 0
|
||||
warnings: [ { id, title, description, impact } ], // 0 < score < 0.9
|
||||
},
|
||||
}
|
||||
```
|
||||
|
||||
### Vital status thresholds
|
||||
|
||||
| Vital | Good | Needs improvement | Poor |
|
||||
|---|---|---|---|
|
||||
| LCP | ≤ 2.5s | ≤ 4.0s | > 4.0s |
|
||||
| TBT | ≤ 200ms | ≤ 600ms | > 600ms |
|
||||
| CLS | ≤ 0.1 | ≤ 0.25 | > 0.25 |
|
||||
| TTFB | ≤ 800ms | ≤ 1800ms | > 1800s |
|
||||
| FCP | ≤ 1.8s | ≤ 3.0s | > 3.0s |
|
||||
| SI | ≤ 3.4s | ≤ 5.8s | > 5.8s |
|
||||
|
||||
## Requirements
|
||||
|
||||
**WE-L-1:** Use `chromium.executablePath()` from Playwright to locate the Chrome
|
||||
binary. Pass this path to `chrome-launcher` as `chromePath`.
|
||||
Why: The Docker base image includes Playwright's Chromium — we must use that
|
||||
executable rather than letting chrome-launcher search default system paths.
|
||||
|
||||
**WE-L-2:** Run two separate audits: one with `formFactor: 'desktop'` and one
|
||||
with `formFactor: 'mobile'`.
|
||||
|
||||
**WE-L-3:** Desktop screen emulation: `{ mobile: false, width: 1440, height: 900,
|
||||
deviceScaleFactor: 1, disabled: false }`.
|
||||
|
||||
**WE-L-4:** Mobile screen emulation: `{ mobile: true, width: 375, height: 812,
|
||||
deviceScaleFactor: 2, disabled: false }`.
|
||||
|
||||
**WE-L-5:** Run only the four standard categories: `performance`, `accessibility`,
|
||||
`best-practices`, `seo`. Omit `pwa`.
|
||||
|
||||
**WE-L-6:** Scores are extracted from `lhr.categories[id].score * 100`, rounded
|
||||
to the nearest integer.
|
||||
|
||||
**WE-L-7:** Core Web Vitals are extracted from these audit IDs:
|
||||
- LCP: `largest-contentful-paint`
|
||||
- TBT: `total-blocking-time` (TBT is the Lighthouse proxy for FID/INP)
|
||||
- CLS: `cumulative-layout-shift`
|
||||
- TTFB: `server-response-time`
|
||||
- FCP: `first-contentful-paint`
|
||||
- SI: `speed-index`
|
||||
|
||||
**WE-L-8:** A finding is **critical** if `audit.score === 0` and the audit is
|
||||
not `informative` mode. A finding is a **warning** if `0 < audit.score < 0.9`.
|
||||
Audits with score ≥ 0.9 or score `null` are omitted.
|
||||
|
||||
**WE-L-9:** Finding `description` is taken from `audit.description` (Markdown,
|
||||
may contain links). Finding `impact` is taken from `audit.details.type === 'opportunity'`
|
||||
savings estimate if present, otherwise omitted.
|
||||
|
||||
**WE-L-10:** Save the full `lhr` object as JSON to `raw/lighthouse-{preset}.json`.
|
||||
|
||||
**WE-L-11:** Kill the Chrome instance in a `finally` block even if the audit throws.
|
||||
Why: Orphaned Chrome processes inside Docker cause container bloat.
|
||||
|
||||
## Scenarios
|
||||
|
||||
### Scenario: Happy path
|
||||
**Given:** Reachable URL, valid Chrome path
|
||||
**When:** `runLighthouse(url, rawDir)` is called
|
||||
**Then:** Returns `[desktopResult, mobileResult]`; both `lighthouse-desktop.json`
|
||||
and `lighthouse-mobile.json` exist in `rawDir`
|
||||
|
||||
### Scenario: Unreachable URL
|
||||
**Given:** URL returns connection refused
|
||||
**When:** Lighthouse audit runs
|
||||
**Then:** Error is thrown; Chrome is killed before the error propagates
|
||||
|
||||
### Scenario: Score extraction
|
||||
**Given:** `lhr.categories.performance.score = 0.87`
|
||||
**When:** Score is extracted
|
||||
**Then:** `scores.performance === 87`
|
||||
93
spec/pipeline.md
Normal file
93
spec/pipeline.md
Normal file
@@ -0,0 +1,93 @@
|
||||
# Pipeline Spec (WE-P)
|
||||
|
||||
## Overview
|
||||
|
||||
Orchestrates the full evaluation pipeline for a single URL. Accepts a URL, runs
|
||||
screenshot capture, Lighthouse audits, and DOM analysis in sequence, writes all
|
||||
outputs to a configured directory, and exits with a clear success or failure code.
|
||||
|
||||
## Responsibilities
|
||||
|
||||
- Parse and validate input (URL + output directory)
|
||||
- Execute pipeline stages in order: screenshot → lighthouse → DOM → report
|
||||
- Write all output files to the configured output directory
|
||||
- Exit 0 on success, exit 1 on any stage failure (with error to stderr)
|
||||
|
||||
Delegates to: `screenshot.js`, `lighthouse.js`, `dom-analysis.js`, `report.js`
|
||||
|
||||
## Dependencies
|
||||
|
||||
None — this is the root spec. All other specs depend on this one for the I/O contract.
|
||||
|
||||
## Data Model
|
||||
|
||||
### Input
|
||||
|
||||
```
|
||||
CLI: node src/index.js <url>
|
||||
ENV: TARGET_URL=<url> (fallback if no CLI arg)
|
||||
ENV: OUTPUT_DIR=/output (default: /output)
|
||||
```
|
||||
|
||||
### Output directory layout
|
||||
|
||||
```
|
||||
$OUTPUT_DIR/
|
||||
report.md ← primary artifact
|
||||
screenshots/
|
||||
desktop.png
|
||||
mobile.png
|
||||
raw/
|
||||
lighthouse-desktop.json
|
||||
lighthouse-mobile.json
|
||||
dom.json
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
**WE-P-1:** The URL must be provided as the first CLI argument or via `TARGET_URL`.
|
||||
If neither is present, print usage to stderr and exit 1.
|
||||
|
||||
**WE-P-2:** The URL must start with `http://` or `https://`. If it does not, print
|
||||
an error and exit 1.
|
||||
Why: Lighthouse and Playwright both require a valid HTTP URL. Silent failures with
|
||||
invalid URLs are confusing.
|
||||
|
||||
**WE-P-3:** The output directory (default `/output`) and its subdirectories
|
||||
(`screenshots/`, `raw/`) must be created if they do not exist.
|
||||
Why: The Docker volume mount only creates the parent; subdirs must be explicit.
|
||||
|
||||
**WE-P-4:** Pipeline stages run sequentially in this order:
|
||||
1. Screenshots + DOM extraction (single Playwright browser session)
|
||||
2. Lighthouse desktop audit
|
||||
3. Lighthouse mobile audit
|
||||
4. Report assembly
|
||||
Why: Lighthouse requires its own Chrome instance. Running screenshots and DOM
|
||||
extraction first in a single Playwright session is more efficient than two sessions.
|
||||
|
||||
**WE-P-5:** Each pipeline stage logs a single progress line to stdout before starting
|
||||
(e.g., `"Capturing screenshots..."`).
|
||||
|
||||
**WE-P-6:** If any stage throws, the error message is printed to stderr and the
|
||||
process exits 1. Partial output files are acceptable — the caller should check
|
||||
exit code, not file presence.
|
||||
|
||||
**WE-P-7:** On success, print the path to `report.md` and exit 0.
|
||||
|
||||
## Scenarios
|
||||
|
||||
### Scenario: Happy path
|
||||
**Given:** Valid URL `https://example.com` and writable `/output`
|
||||
**When:** Pipeline completes all stages
|
||||
**Then:** Exit 0; `report.md`, `screenshots/desktop.png`, `screenshots/mobile.png`,
|
||||
`raw/lighthouse-desktop.json`, `raw/lighthouse-mobile.json`, `raw/dom.json` all exist
|
||||
|
||||
### Scenario: Missing URL
|
||||
**Given:** No CLI arg and `TARGET_URL` not set
|
||||
**When:** `index.js` starts
|
||||
**Then:** Prints usage to stderr, exits 1
|
||||
|
||||
### Scenario: Invalid URL scheme
|
||||
**Given:** URL `file:///etc/passwd`
|
||||
**When:** Validation runs
|
||||
**Then:** Prints error "URL must start with http:// or https://", exits 1
|
||||
130
spec/report.md
Normal file
130
spec/report.md
Normal file
@@ -0,0 +1,130 @@
|
||||
# Report Spec (WE-R)
|
||||
|
||||
## Overview
|
||||
|
||||
Assembles all collected data into a single Markdown file optimised for Claude
|
||||
to read. Findings are phrased in a pre-interpreted tone: each issue explains
|
||||
what it means and what to do about it, not just that a value is absent or wrong.
|
||||
|
||||
## Responsibilities
|
||||
|
||||
- Accept LighthouseResult[] and DomData as inputs
|
||||
- Produce a single Markdown string
|
||||
- Apply severity grading to Lighthouse scores
|
||||
- Categorise findings as Critical (must fix) / Warning (should fix) / Info
|
||||
- Write the report in the defined section order
|
||||
- Deduplicate findings that appear in both desktop and mobile audits
|
||||
|
||||
Delegates to: nothing (pure function, no I/O)
|
||||
|
||||
## Dependencies
|
||||
|
||||
Read [lighthouse.md](lighthouse.md) for `LighthouseResult` shape.
|
||||
Read [dom-analysis.md](dom-analysis.md) for `DomData` shape.
|
||||
|
||||
## Data Model
|
||||
|
||||
### Score grading
|
||||
|
||||
| Score | Grade | Colour label |
|
||||
|---|---|---|
|
||||
| 90–100 | A | Good |
|
||||
| 75–89 | B | Needs improvement |
|
||||
| 50–74 | C | Poor |
|
||||
| 0–49 | F | Critical |
|
||||
|
||||
### Severity rules
|
||||
|
||||
- **Critical (must fix):** Lighthouse finding with `score === 0` in any preset
|
||||
- **Warning (should fix):** Lighthouse finding with `0 < score < 0.9` in any preset; or DOM issues (missing description, heading skips, missing alt)
|
||||
- **Info:** Minor or informational items
|
||||
|
||||
## Requirements
|
||||
|
||||
**WE-R-1:** The report begins with a level-1 heading: `# Website Evaluation: {hostname}`.
|
||||
Hostname is extracted from the URL (no scheme, no path).
|
||||
|
||||
**WE-R-2:** A generation line follows: `_Generated: {ISO date} · Audited: {full URL}_`
|
||||
|
||||
**WE-R-3:** Section order (all sections always present, even if empty):
|
||||
1. Lighthouse Scores (desktop + mobile tables)
|
||||
2. Core Web Vitals (desktop + mobile tables)
|
||||
3. Critical Issues
|
||||
4. Warnings
|
||||
5. Info
|
||||
6. Page Structure
|
||||
7. Screenshots reference
|
||||
8. Raw Data reference
|
||||
|
||||
**WE-R-4:** Lighthouse score tables have columns: Category | Desktop | Mobile | Desktop Grade | Mobile Grade.
|
||||
Why: Side-by-side makes it easy to spot mobile regressions.
|
||||
|
||||
**WE-R-5:** Core Web Vitals tables have columns: Metric | Desktop | Mobile | Status.
|
||||
Status is the worst of the two presets for that metric (e.g., if desktop is "good"
|
||||
but mobile is "needs-improvement", show "needs-improvement").
|
||||
|
||||
**WE-R-6:** Each finding is rendered as a Markdown checkbox list item:
|
||||
```
|
||||
- [ ] **{title}** — {pre-interpreted explanation with fix guidance}
|
||||
```
|
||||
The title comes from `audit.title`. The explanation is built from `audit.description`
|
||||
(stripped of Markdown links) plus any savings estimate.
|
||||
|
||||
**WE-R-7:** If the same audit ID appears as a finding in both desktop and mobile
|
||||
results, it appears once in the report (not duplicated). The severity is the worst
|
||||
across both presets.
|
||||
|
||||
**WE-R-8:** DOM issues (missing meta description, heading skips, missing alt text,
|
||||
missing noopener) are included in the Warnings section with pre-interpreted phrasing.
|
||||
Exact phrasing:
|
||||
- Missing meta description: "Meta description missing — search engines will auto-generate one, often poorly. Add a 155-char summary."
|
||||
- Heading skip: "Heading hierarchy skips {skip} — this confuses screen readers and weakens document structure."
|
||||
- Missing alt: "{n} image(s) lack alt text — WCAG 1.1.1 violation (Level A); screen readers will skip them entirely."
|
||||
- Missing noopener: "{n} link(s) open in a new tab without `rel=\"noopener\"` — the opened page can access `window.opener`."
|
||||
|
||||
**WE-R-9:** Page Structure section lists:
|
||||
- Title (value + char count + assessment: "good" if 30–65 chars, "too short" / "too long" otherwise)
|
||||
- Meta description (value or "missing")
|
||||
- Canonical (value or "not set")
|
||||
- Heading counts per level (e.g., "H1×1, H2×4, H3×7")
|
||||
- Images (total, missing alt count)
|
||||
- Links (total, external, new-tab-no-opener count)
|
||||
- Structured data (types detected or "none")
|
||||
- Open Graph (present/absent)
|
||||
|
||||
**WE-R-10:** Screenshots section contains Markdown image references:
|
||||
```markdown
|
||||

|
||||

|
||||
```
|
||||
|
||||
**WE-R-11:** If all Critical, Warning, and Info sections are empty, replace each
|
||||
with `_None detected._`.
|
||||
|
||||
**WE-R-12:** The function signature is:
|
||||
```js
|
||||
assembleReport(url, lighthouseResults, domData) → string
|
||||
```
|
||||
It is a pure function — no file I/O, no side effects.
|
||||
|
||||
## Scenarios
|
||||
|
||||
### Scenario: Score table format
|
||||
**Given:** Desktop performance 87, mobile performance 62
|
||||
**When:** Report is assembled
|
||||
**Then:** Score table shows desktop 87 (B), mobile 62 (C) in the same row
|
||||
|
||||
### Scenario: Finding deduplication
|
||||
**Given:** `meta-description` audit fails (score 0) in both desktop and mobile results
|
||||
**When:** Report is assembled
|
||||
**Then:** The meta-description finding appears exactly once in Critical Issues
|
||||
|
||||
### Scenario: Empty sections
|
||||
**Given:** All Lighthouse audits pass (score ≥ 0.9), no DOM issues
|
||||
**When:** Report is assembled
|
||||
**Then:** Critical Issues, Warnings, and Info sections each contain `_None detected._`
|
||||
|
||||
### Scenario: Title assessment
|
||||
**Given:** Page title is 14 chars ("Example Domain")
|
||||
**When:** Page Structure section is built
|
||||
**Then:** Title line shows `"Example Domain" (14 chars — too short)`
|
||||
77
spec/screenshot.md
Normal file
77
spec/screenshot.md
Normal file
@@ -0,0 +1,77 @@
|
||||
# Screenshot Spec (WE-S)
|
||||
|
||||
## Overview
|
||||
|
||||
Captures full-page screenshots of a URL at desktop and mobile viewport sizes
|
||||
using Playwright (Chromium). Screenshots are saved as PNG files to the output
|
||||
screenshots directory.
|
||||
|
||||
## Responsibilities
|
||||
|
||||
- Launch a Playwright Chromium browser
|
||||
- Capture a full-page desktop screenshot (1440×900 viewport)
|
||||
- Capture a full-page mobile screenshot (375×812 viewport, mobile UA)
|
||||
- Save both as PNG to the screenshots directory
|
||||
- Return the browser instance for DOM analysis reuse
|
||||
|
||||
Delegates to: Playwright
|
||||
|
||||
## Dependencies
|
||||
|
||||
Read [pipeline.md](pipeline.md) for output directory layout.
|
||||
|
||||
## Data Model
|
||||
|
||||
### Viewport configs
|
||||
|
||||
```js
|
||||
const DESKTOP = { width: 1440, height: 900, deviceScaleFactor: 1 };
|
||||
const MOBILE = { width: 375, height: 812, deviceScaleFactor: 2, isMobile: true,
|
||||
hasTouch: true, userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0...' };
|
||||
```
|
||||
|
||||
### Output
|
||||
|
||||
```
|
||||
screenshots/desktop.png — full-page, lossless PNG
|
||||
screenshots/mobile.png — full-page, lossless PNG, 2× pixel density
|
||||
```
|
||||
|
||||
## Requirements
|
||||
|
||||
**WE-S-1:** Navigate to the URL and wait for `networkidle` before capturing.
|
||||
Why: Ensures lazy-loaded content and web fonts are rendered.
|
||||
|
||||
**WE-S-2:** Capture a full-page screenshot (not just the visible viewport).
|
||||
Why: Full-page screenshots let Claude see below-the-fold content.
|
||||
|
||||
**WE-S-3:** Desktop viewport: 1440×900, deviceScaleFactor 1, no mobile emulation.
|
||||
|
||||
**WE-S-4:** Mobile viewport: 375×812, deviceScaleFactor 2, mobile UA, touch enabled.
|
||||
The mobile UA string should identify as an iPhone running Safari to maximise
|
||||
site responsiveness compatibility.
|
||||
|
||||
**WE-S-5:** Save format: PNG (lossless). Do not use JPEG.
|
||||
Why: Lossless preserves fine text and UI details important for design critique.
|
||||
|
||||
**WE-S-6:** The function must accept an already-open Playwright browser instance
|
||||
(not launch its own) so the caller can reuse the session for DOM extraction.
|
||||
|
||||
**WE-S-7:** Both screenshots must be saved before returning.
|
||||
|
||||
## Scenarios
|
||||
|
||||
### Scenario: Happy path
|
||||
**Given:** Valid browser instance, reachable URL, writable screenshots dir
|
||||
**When:** `captureScreenshots(browser, url, screenshotsDir)` is called
|
||||
**Then:** `desktop.png` and `mobile.png` exist in `screenshotsDir`, both non-zero bytes
|
||||
|
||||
### Scenario: Navigation timeout
|
||||
**Given:** URL that times out (server unresponsive)
|
||||
**When:** Page navigation exceeds 30s
|
||||
**Then:** Error is thrown with the URL and "navigation timeout" in the message
|
||||
|
||||
### Scenario: Page with no content
|
||||
**Given:** URL that returns an empty 200 response
|
||||
**When:** Screenshots are captured
|
||||
**Then:** Screenshots are saved (may be blank); no error is thrown
|
||||
Reference in New Issue
Block a user