Skip to content

Commit cee6b53

Browse files
committed
chore: use default viewport
1 parent 9c0e30f commit cee6b53

2 files changed

Lines changed: 61 additions & 19 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,7 +164,7 @@ The comment is regenerated on every workflow run, so reviewers always see fresh
164164
| `routes` | `["/"]` | Array of paths to diff (JSON array string or YAML list string with `|`) |
165165
| `threshold` | `0.001` | Max acceptable diff ratio (0..1) |
166166
| `pixel-threshold` | `0.1` | Per-pixel sensitivity (0..1) |
167-
| `mql` | *(empty)* | JSON object with Microlink API options; default viewport is `{ "width": 1280, "height": 800, "deviceScaleFactor": 2 }` |
167+
| `mql` | `{ "force": true }` | JSON object with Microlink API options (`viewport`, `styles`, headers, etc.) |
168168
| `microlink-api-key` | *(empty)* | Optional paid-tier key |
169169
| `presigned-ttl-seconds` | `86400` | Pre-signed URL TTL (overrides `s3-config.presignedTtlSeconds`) |
170170
| `provider-timeout` | `600` | Max seconds to wait for `head: vercel` discovery |

src/index.js

Lines changed: 60 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,16 @@
11
import { mkdir, readFile, writeFile } from 'node:fs/promises'
22
import path from 'node:path'
33

4+
import { PNG } from 'pngjs'
5+
46
import { composite } from './composite.js'
57
import { diff as runDiff } from './diff.js'
68
import { screenshot } from './microlink.js'
79

810
const DEFAULT_THRESHOLD = 0.001
911
const DEFAULT_WARNING_THRESHOLD = 0.02
1012
const DEFAULT_PIXEL_THRESHOLD = 0.1
11-
const DEFAULT_VIEWPORT = { width: 1280, height: 800, deviceScaleFactor: 2 }
13+
const DEFAULT_SCREENSHOT_ATTEMPTS = 3
1214
const DEFAULT_ROUTES = ['/']
1315

1416
const noop = () => {}
@@ -62,6 +64,53 @@ const slugifyRoute = route => {
6264
)
6365
}
6466

67+
const imageSize = buffer => {
68+
const { width, height } = PNG.sync.read(buffer)
69+
return { width, height }
70+
}
71+
72+
const sameSize = (base, head) =>
73+
base.width === head.width && base.height === head.height
74+
75+
const capturePair = async ({ baseUrl, headUrl, route, log, attempts, opts }) => {
76+
let lastBaseSize
77+
let lastHeadSize
78+
79+
for (let attempt = 1; attempt <= attempts; attempt++) {
80+
const fetchStart = Date.now()
81+
const [baseBuffer, headBuffer] = await Promise.all([
82+
screenshot(baseUrl, {
83+
...opts,
84+
log: msg => log(`[${route}] ${msg}`)
85+
}),
86+
screenshot(headUrl, {
87+
...opts,
88+
log: msg => log(`[${route}] ${msg}`)
89+
})
90+
])
91+
92+
lastBaseSize = imageSize(baseBuffer)
93+
lastHeadSize = imageSize(headBuffer)
94+
log(
95+
`[${route}] both screenshots ready in ${
96+
Date.now() - fetchStart
97+
}ms · base ${lastBaseSize.width}x${lastBaseSize.height} · head ${
98+
lastHeadSize.width
99+
}x${lastHeadSize.height}`
100+
)
101+
102+
if (sameSize(lastBaseSize, lastHeadSize)) return { baseBuffer, headBuffer }
103+
if (attempt < attempts)
104+
log(
105+
`[${route}] screenshot dimensions mismatch, retrying (${attempt}/${attempts})`
106+
)
107+
}
108+
109+
throw new Error(
110+
`Screenshot dimensions mismatch for ${route}: base ${lastBaseSize.width}x${lastBaseSize.height}, head ${lastHeadSize.width}x${lastHeadSize.height}`
111+
)
112+
}
113+
65114
const runRoute = async ({
66115
route,
67116
base,
@@ -71,6 +120,7 @@ const runRoute = async ({
71120
pixelThreshold,
72121
outDir,
73122
log,
123+
screenshotAttempts = DEFAULT_SCREENSHOT_ATTEMPTS,
74124
...screenshotOpts
75125
}) => {
76126
const baseUrl = joinUrl(base, route)
@@ -79,18 +129,14 @@ const runRoute = async ({
79129
log(`[${route}] base: ${baseUrl}`)
80130
log(`[${route}] head: ${headUrl}`)
81131

82-
const fetchStart = Date.now()
83-
const [baseBuffer, headBuffer] = await Promise.all([
84-
screenshot(baseUrl, {
85-
...screenshotOpts,
86-
log: msg => log(`[${route}] ${msg}`)
87-
}),
88-
screenshot(headUrl, {
89-
...screenshotOpts,
90-
log: msg => log(`[${route}] ${msg}`)
91-
})
92-
])
93-
log(`[${route}] both screenshots ready in ${Date.now() - fetchStart}ms`)
132+
const { baseBuffer, headBuffer } = await capturePair({
133+
baseUrl,
134+
headUrl,
135+
route,
136+
log,
137+
attempts: screenshotAttempts,
138+
opts: screenshotOpts
139+
})
94140

95141
const diffStart = Date.now()
96142
const {
@@ -168,11 +214,7 @@ export const run = async ({
168214
if (!Array.isArray(routes) || routes.length === 0)
169215
throw new Error('routes must be a non-empty array')
170216

171-
const mql = {
172-
apiKey,
173-
...mqlOpts,
174-
viewport: { ...DEFAULT_VIEWPORT, ...mqlOpts.viewport }
175-
}
217+
const mql = { apiKey, force: true, ...mqlOpts }
176218
const resolvedThreshold = await resolveThreshold({ flag: threshold, cwd })
177219
const resolvedWarningThreshold = await resolveWarningThreshold({ flag: warningThreshold, cwd })
178220
log(

0 commit comments

Comments
 (0)