webpeel 0.17.20 → 0.17.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/browser-fetch.d.ts +51 -0
- package/dist/core/browser-fetch.d.ts.map +1 -1
- package/dist/core/browser-fetch.js +259 -19
- package/dist/core/browser-fetch.js.map +1 -1
- package/dist/core/design-analysis.d.ts +71 -0
- package/dist/core/design-analysis.d.ts.map +1 -0
- package/dist/core/design-analysis.js +430 -0
- package/dist/core/design-analysis.js.map +1 -0
- package/dist/core/fetcher.d.ts +2 -1
- package/dist/core/fetcher.d.ts.map +1 -1
- package/dist/core/fetcher.js +1 -1
- package/dist/core/fetcher.js.map +1 -1
- package/dist/core/pipeline.d.ts +2 -0
- package/dist/core/pipeline.d.ts.map +1 -1
- package/dist/core/pipeline.js +35 -4
- package/dist/core/pipeline.js.map +1 -1
- package/dist/core/screenshot.d.ts +41 -1
- package/dist/core/screenshot.d.ts.map +1 -1
- package/dist/core/screenshot.js +35 -2
- package/dist/core/screenshot.js.map +1 -1
- package/dist/server/openapi.yaml +114 -0
- package/dist/server/routes/screenshot.d.ts.map +1 -1
- package/dist/server/routes/screenshot.js +191 -2
- package/dist/server/routes/screenshot.js.map +1 -1
- package/dist/types.d.ts +4 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/package.json +4 -1
|
@@ -85,10 +85,34 @@ export declare function browserScreenshot(url: string, options?: {
|
|
|
85
85
|
timeout?: number;
|
|
86
86
|
}>;
|
|
87
87
|
scrollThrough?: boolean;
|
|
88
|
+
selector?: string;
|
|
88
89
|
}): Promise<{
|
|
89
90
|
buffer: Buffer;
|
|
90
91
|
finalUrl: string;
|
|
91
92
|
}>;
|
|
93
|
+
/**
|
|
94
|
+
* Capture screenshots of two URLs and compute a pixel-level visual diff.
|
|
95
|
+
*/
|
|
96
|
+
export declare function browserDiff(url1: string, url2: string, options?: {
|
|
97
|
+
width?: number;
|
|
98
|
+
height?: number;
|
|
99
|
+
fullPage?: boolean;
|
|
100
|
+
threshold?: number;
|
|
101
|
+
format?: 'png' | 'jpeg';
|
|
102
|
+
quality?: number;
|
|
103
|
+
stealth?: boolean;
|
|
104
|
+
waitMs?: number;
|
|
105
|
+
timeoutMs?: number;
|
|
106
|
+
}): Promise<{
|
|
107
|
+
diffBuffer: Buffer;
|
|
108
|
+
diffPixels: number;
|
|
109
|
+
totalPixels: number;
|
|
110
|
+
diffPercent: number;
|
|
111
|
+
dimensions: {
|
|
112
|
+
width: number;
|
|
113
|
+
height: number;
|
|
114
|
+
};
|
|
115
|
+
}>;
|
|
92
116
|
/**
|
|
93
117
|
* Retry a fetch operation with exponential backoff
|
|
94
118
|
*/
|
|
@@ -210,6 +234,7 @@ export declare function browserViewports(url: string, options: {
|
|
|
210
234
|
}>;
|
|
211
235
|
export interface DesignAuditResult {
|
|
212
236
|
score: number;
|
|
237
|
+
colorScheme: 'light' | 'dark' | 'unknown';
|
|
213
238
|
spacingViolations: {
|
|
214
239
|
element: string;
|
|
215
240
|
property: string;
|
|
@@ -228,6 +253,7 @@ export interface DesignAuditResult {
|
|
|
228
253
|
bgColor: string;
|
|
229
254
|
ratio: number;
|
|
230
255
|
required: number;
|
|
256
|
+
bgResolved?: boolean;
|
|
231
257
|
}[];
|
|
232
258
|
typography: {
|
|
233
259
|
fontSizes: string[];
|
|
@@ -235,6 +261,12 @@ export interface DesignAuditResult {
|
|
|
235
261
|
letterSpacings: string[];
|
|
236
262
|
};
|
|
237
263
|
spacingScale: number[];
|
|
264
|
+
accessibilityViolations: {
|
|
265
|
+
type: 'missing-alt' | 'missing-label' | 'missing-aria' | 'heading-skip' | 'empty-link' | 'empty-button';
|
|
266
|
+
element: string;
|
|
267
|
+
details: string;
|
|
268
|
+
}[];
|
|
269
|
+
headingStructure: string[];
|
|
238
270
|
summary: string;
|
|
239
271
|
}
|
|
240
272
|
/**
|
|
@@ -260,4 +292,23 @@ export declare function browserDesignAudit(url: string, options?: {
|
|
|
260
292
|
audit: DesignAuditResult;
|
|
261
293
|
finalUrl: string;
|
|
262
294
|
}>;
|
|
295
|
+
/**
|
|
296
|
+
* Extract structured visual design intelligence from a URL using a browser.
|
|
297
|
+
* Returns a DesignAnalysis object with effects, palette, layout, type scale,
|
|
298
|
+
* and quality signals.
|
|
299
|
+
*/
|
|
300
|
+
export declare function browserDesignAnalysis(url: string, options?: {
|
|
301
|
+
selector?: string;
|
|
302
|
+
width?: number;
|
|
303
|
+
height?: number;
|
|
304
|
+
waitMs?: number;
|
|
305
|
+
timeoutMs?: number;
|
|
306
|
+
userAgent?: string;
|
|
307
|
+
headers?: Record<string, string>;
|
|
308
|
+
cookies?: string[];
|
|
309
|
+
stealth?: boolean;
|
|
310
|
+
}): Promise<{
|
|
311
|
+
analysis: import('./design-analysis.js').DesignAnalysis;
|
|
312
|
+
finalUrl: string;
|
|
313
|
+
}>;
|
|
263
314
|
//# sourceMappingURL=browser-fetch.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"browser-fetch.d.ts","sourceRoot":"","sources":["../../src/core/browser-fetch.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"browser-fetch.d.ts","sourceRoot":"","sources":["../../src/core/browser-fetch.ts"],"names":[],"mappings":"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"}
|
|
@@ -472,7 +472,7 @@ export async function browserFetch(url, options = {}) {
|
|
|
472
472
|
export async function browserScreenshot(url, options = {}) {
|
|
473
473
|
// SECURITY: Validate URL to prevent SSRF
|
|
474
474
|
validateUrl(url);
|
|
475
|
-
const { fullPage = false, width, height, format = 'png', quality, waitMs = 0, timeoutMs = 30000, userAgent, headers, cookies, stealth = false, actions, scrollThrough = false, } = options;
|
|
475
|
+
const { fullPage = false, width, height, format = 'png', quality, waitMs = 0, timeoutMs = 30000, userAgent, headers, cookies, stealth = false, actions, scrollThrough = false, selector, } = options;
|
|
476
476
|
const validatedUserAgent = userAgent ? validateUserAgent(userAgent) : getRandomUserAgent();
|
|
477
477
|
// Basic validation
|
|
478
478
|
if (waitMs < 0 || waitMs > 60000) {
|
|
@@ -612,6 +612,18 @@ export async function browserScreenshot(url, options = {}) {
|
|
|
612
612
|
if (waitMs > 0) {
|
|
613
613
|
await page.waitForTimeout(waitMs);
|
|
614
614
|
}
|
|
615
|
+
// Element-level screenshot (clip to a specific CSS selector)
|
|
616
|
+
if (selector) {
|
|
617
|
+
const count = await page.locator(selector).count();
|
|
618
|
+
if (count === 0)
|
|
619
|
+
throw new WebPeelError(`Element not found: ${selector}`);
|
|
620
|
+
const element = await page.locator(selector).first();
|
|
621
|
+
const buf = await element.screenshot({
|
|
622
|
+
type: format,
|
|
623
|
+
...(format === 'jpeg' && typeof quality === 'number' ? { quality } : {}),
|
|
624
|
+
});
|
|
625
|
+
return { finalUrl: page.url(), screenshotBuffer: buf };
|
|
626
|
+
}
|
|
615
627
|
// Scroll through the page to trigger IntersectionObservers, lazy loading, animations
|
|
616
628
|
if (scrollThrough) {
|
|
617
629
|
const scrollHeight = await page.evaluate(() => document.body.scrollHeight);
|
|
@@ -681,6 +693,60 @@ export async function browserScreenshot(url, options = {}) {
|
|
|
681
693
|
activePagesCount--;
|
|
682
694
|
}
|
|
683
695
|
}
|
|
696
|
+
// ── browserDiff ───────────────────────────────────────────────────────────────
|
|
697
|
+
/**
|
|
698
|
+
* Capture screenshots of two URLs and compute a pixel-level visual diff.
|
|
699
|
+
*/
|
|
700
|
+
export async function browserDiff(url1, url2, options = {}) {
|
|
701
|
+
const { width = 1280, height = 720, fullPage = false, threshold = 0.1, stealth = false, waitMs = 0, timeoutMs = 30000, } = options;
|
|
702
|
+
// Take both screenshots as PNG (required for pixelmatch)
|
|
703
|
+
const [res1, res2] = await Promise.all([
|
|
704
|
+
browserScreenshot(url1, { width, height, fullPage, format: 'png', stealth, waitMs, timeoutMs }),
|
|
705
|
+
browserScreenshot(url2, { width, height, fullPage, format: 'png', stealth, waitMs, timeoutMs }),
|
|
706
|
+
]);
|
|
707
|
+
// Dynamically import pngjs and pixelmatch (ESM-compatible)
|
|
708
|
+
const { PNG } = await import('pngjs');
|
|
709
|
+
const pixelmatch = (await import('pixelmatch')).default;
|
|
710
|
+
const img1 = PNG.sync.read(res1.buffer);
|
|
711
|
+
const img2 = PNG.sync.read(res2.buffer);
|
|
712
|
+
// Use the larger of the two dimensions
|
|
713
|
+
const outWidth = Math.max(img1.width, img2.width);
|
|
714
|
+
const outHeight = Math.max(img1.height, img2.height);
|
|
715
|
+
// Pad images to the same size if needed
|
|
716
|
+
function padImage(img, targetW, targetH) {
|
|
717
|
+
if (img.width === targetW && img.height === targetH) {
|
|
718
|
+
return img.data;
|
|
719
|
+
}
|
|
720
|
+
const padded = Buffer.alloc(targetW * targetH * 4, 0);
|
|
721
|
+
for (let y = 0; y < img.height && y < targetH; y++) {
|
|
722
|
+
for (let x = 0; x < img.width && x < targetW; x++) {
|
|
723
|
+
const srcIdx = (y * img.width + x) * 4;
|
|
724
|
+
const dstIdx = (y * targetW + x) * 4;
|
|
725
|
+
padded[dstIdx] = img.data[srcIdx];
|
|
726
|
+
padded[dstIdx + 1] = img.data[srcIdx + 1];
|
|
727
|
+
padded[dstIdx + 2] = img.data[srcIdx + 2];
|
|
728
|
+
padded[dstIdx + 3] = img.data[srcIdx + 3];
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
return padded;
|
|
732
|
+
}
|
|
733
|
+
const data1 = padImage(img1, outWidth, outHeight);
|
|
734
|
+
const data2 = padImage(img2, outWidth, outHeight);
|
|
735
|
+
const diffData = Buffer.alloc(outWidth * outHeight * 4);
|
|
736
|
+
const diffPixels = pixelmatch(data1, data2, diffData, outWidth, outHeight, { threshold });
|
|
737
|
+
const totalPixels = outWidth * outHeight;
|
|
738
|
+
const diffPercent = totalPixels > 0 ? (diffPixels / totalPixels) * 100 : 0;
|
|
739
|
+
const diffPng = new PNG({ width: outWidth, height: outHeight });
|
|
740
|
+
diffPng.data = diffData;
|
|
741
|
+
const diffBuffer = PNG.sync.write(diffPng);
|
|
742
|
+
return {
|
|
743
|
+
diffBuffer,
|
|
744
|
+
diffPixels,
|
|
745
|
+
totalPixels,
|
|
746
|
+
diffPercent,
|
|
747
|
+
dimensions: { width: outWidth, height: outHeight },
|
|
748
|
+
};
|
|
749
|
+
}
|
|
684
750
|
// ── retryFetch ────────────────────────────────────────────────────────────────
|
|
685
751
|
/**
|
|
686
752
|
* Retry a fetch operation with exponential backoff
|
|
@@ -1057,6 +1123,76 @@ export async function browserDesignAudit(url, options = {}) {
|
|
|
1057
1123
|
return null;
|
|
1058
1124
|
return [parseInt(m[1]), parseInt(m[2]), parseInt(m[3])];
|
|
1059
1125
|
}
|
|
1126
|
+
function parseRgba(color) {
|
|
1127
|
+
const m = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/);
|
|
1128
|
+
if (!m)
|
|
1129
|
+
return null;
|
|
1130
|
+
return [parseInt(m[1]), parseInt(m[2]), parseInt(m[3]), m[4] !== undefined ? parseFloat(m[4]) : 1];
|
|
1131
|
+
}
|
|
1132
|
+
function getEffectiveBackground(el) {
|
|
1133
|
+
let current = el;
|
|
1134
|
+
while (current && current !== document.documentElement) {
|
|
1135
|
+
const style = window.getComputedStyle(current);
|
|
1136
|
+
const bg = style.backgroundColor;
|
|
1137
|
+
const parsed = parseRgba(bg);
|
|
1138
|
+
if (parsed && parsed[3] > 0.5) {
|
|
1139
|
+
return [parsed[0], parsed[1], parsed[2]];
|
|
1140
|
+
}
|
|
1141
|
+
current = current.parentElement;
|
|
1142
|
+
}
|
|
1143
|
+
// Check html element
|
|
1144
|
+
const htmlStyle = window.getComputedStyle(document.documentElement);
|
|
1145
|
+
const htmlBg = parseRgba(htmlStyle.backgroundColor);
|
|
1146
|
+
if (htmlBg && htmlBg[3] > 0.5) {
|
|
1147
|
+
return [htmlBg[0], htmlBg[1], htmlBg[2]];
|
|
1148
|
+
}
|
|
1149
|
+
// Check body element
|
|
1150
|
+
const bodyStyle = window.getComputedStyle(document.body);
|
|
1151
|
+
const bodyBg = parseRgba(bodyStyle.backgroundColor);
|
|
1152
|
+
if (bodyBg && bodyBg[3] > 0.5) {
|
|
1153
|
+
return [bodyBg[0], bodyBg[1], bodyBg[2]];
|
|
1154
|
+
}
|
|
1155
|
+
// Check color-scheme CSS property or meta tag
|
|
1156
|
+
const colorScheme = htmlStyle.colorScheme ||
|
|
1157
|
+
document.querySelector('meta[name="color-scheme"]')?.getAttribute('content') || '';
|
|
1158
|
+
if (colorScheme.includes('dark')) {
|
|
1159
|
+
return [0, 0, 0]; // Dark scheme default
|
|
1160
|
+
}
|
|
1161
|
+
// Ultimate fallback: white (standard web default)
|
|
1162
|
+
return [255, 255, 255];
|
|
1163
|
+
}
|
|
1164
|
+
function hasBackdropFilter(el) {
|
|
1165
|
+
let current = el;
|
|
1166
|
+
while (current) {
|
|
1167
|
+
const style = window.getComputedStyle(current);
|
|
1168
|
+
const bf = style.backdropFilter;
|
|
1169
|
+
if (bf && bf !== 'none' && bf !== '')
|
|
1170
|
+
return true;
|
|
1171
|
+
current = current.parentElement;
|
|
1172
|
+
}
|
|
1173
|
+
return false;
|
|
1174
|
+
}
|
|
1175
|
+
function detectPageColorScheme() {
|
|
1176
|
+
const htmlStyle = window.getComputedStyle(document.documentElement);
|
|
1177
|
+
const htmlBg = parseRgba(htmlStyle.backgroundColor);
|
|
1178
|
+
if (htmlBg && htmlBg[3] > 0.5) {
|
|
1179
|
+
const lum = luminance(htmlBg[0], htmlBg[1], htmlBg[2]);
|
|
1180
|
+
return lum < 0.18 ? 'dark' : 'light';
|
|
1181
|
+
}
|
|
1182
|
+
const bodyStyle = window.getComputedStyle(document.body);
|
|
1183
|
+
const bodyBg = parseRgba(bodyStyle.backgroundColor);
|
|
1184
|
+
if (bodyBg && bodyBg[3] > 0.5) {
|
|
1185
|
+
const lum = luminance(bodyBg[0], bodyBg[1], bodyBg[2]);
|
|
1186
|
+
return lum < 0.18 ? 'dark' : 'light';
|
|
1187
|
+
}
|
|
1188
|
+
const colorScheme = htmlStyle.colorScheme ||
|
|
1189
|
+
document.querySelector('meta[name="color-scheme"]')?.getAttribute('content') || '';
|
|
1190
|
+
if (colorScheme.includes('dark'))
|
|
1191
|
+
return 'dark';
|
|
1192
|
+
if (colorScheme.includes('light'))
|
|
1193
|
+
return 'light';
|
|
1194
|
+
return 'unknown';
|
|
1195
|
+
}
|
|
1060
1196
|
function luminance(r, g, b) {
|
|
1061
1197
|
const [rs, gs, bs] = [r, g, b].map(c => {
|
|
1062
1198
|
const s = c / 255;
|
|
@@ -1139,32 +1275,103 @@ export async function browserDesignAudit(url, options = {}) {
|
|
|
1139
1275
|
touchTargetViolations.push({ element: label, width: Math.round(w), height: Math.round(h), minRequired: minTouchTarget });
|
|
1140
1276
|
}
|
|
1141
1277
|
}
|
|
1142
|
-
// Contrast —
|
|
1278
|
+
// Contrast — Walk up DOM tree to find effective opaque background
|
|
1143
1279
|
const textColor = style.color;
|
|
1144
|
-
|
|
1145
|
-
if (textColor && bgColor) {
|
|
1280
|
+
if (textColor) {
|
|
1146
1281
|
const fg = parseRgb(textColor);
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
// Only flag elements with visible text content
|
|
1282
|
+
if (fg) {
|
|
1283
|
+
if (hasBackdropFilter(el)) {
|
|
1284
|
+
// Background can't be determined from CSS alone — mark as unresolvable
|
|
1285
|
+
// and exclude from scoring (bgResolved: false)
|
|
1152
1286
|
const text = el.textContent?.trim() || '';
|
|
1153
1287
|
if (text.length > 0 && text.length < 200) {
|
|
1154
1288
|
contrastViolations.push({
|
|
1155
1289
|
element: label,
|
|
1156
1290
|
textColor,
|
|
1157
|
-
bgColor,
|
|
1158
|
-
ratio:
|
|
1291
|
+
bgColor: 'unknown (backdrop-filter)',
|
|
1292
|
+
ratio: 0,
|
|
1159
1293
|
required: minContrast,
|
|
1294
|
+
bgResolved: false,
|
|
1160
1295
|
});
|
|
1161
1296
|
}
|
|
1162
1297
|
}
|
|
1298
|
+
else {
|
|
1299
|
+
const effectiveBg = getEffectiveBackground(el);
|
|
1300
|
+
// bgResolved: true — background was successfully determined via DOM traversal
|
|
1301
|
+
const ratio = contrastRatio(fg, effectiveBg);
|
|
1302
|
+
if (ratio > 1.05 && ratio < minContrast) {
|
|
1303
|
+
// Only flag elements with visible text content
|
|
1304
|
+
const text = el.textContent?.trim() || '';
|
|
1305
|
+
if (text.length > 0 && text.length < 200) {
|
|
1306
|
+
contrastViolations.push({
|
|
1307
|
+
element: label,
|
|
1308
|
+
textColor,
|
|
1309
|
+
bgColor: `rgb(${effectiveBg.join(',')})`,
|
|
1310
|
+
ratio: Math.round(ratio * 100) / 100,
|
|
1311
|
+
required: minContrast,
|
|
1312
|
+
bgResolved: true,
|
|
1313
|
+
});
|
|
1314
|
+
}
|
|
1315
|
+
}
|
|
1316
|
+
}
|
|
1163
1317
|
}
|
|
1164
1318
|
}
|
|
1165
1319
|
}
|
|
1166
1320
|
const spacingScale = Array.from(spacingValuesSet).sort((a, b) => a - b).map(v => Math.round(v));
|
|
1321
|
+
// ── WCAG Accessibility Audit ──────────────────────────────────────
|
|
1322
|
+
const a11yViolations = [];
|
|
1323
|
+
const headingStructure = [];
|
|
1324
|
+
// 1. Images without alt text
|
|
1325
|
+
const images = root.querySelectorAll('img');
|
|
1326
|
+
for (const img of Array.from(images)) {
|
|
1327
|
+
if (!img.getAttribute('alt') && !img.getAttribute('aria-label') && !img.getAttribute('role')?.includes('presentation')) {
|
|
1328
|
+
a11yViolations.push({ type: 'missing-alt', element: elementLabel(img), details: `src: ${(img.getAttribute('src') || '').slice(0, 80)}` });
|
|
1329
|
+
}
|
|
1330
|
+
}
|
|
1331
|
+
// 2. Form inputs without labels
|
|
1332
|
+
const inputs = root.querySelectorAll('input, select, textarea');
|
|
1333
|
+
for (const input of Array.from(inputs)) {
|
|
1334
|
+
const id = input.getAttribute('id');
|
|
1335
|
+
const hasLabel = id && document.querySelector(`label[for="${id}"]`);
|
|
1336
|
+
const hasAria = input.getAttribute('aria-label') || input.getAttribute('aria-labelledby');
|
|
1337
|
+
const hasTitle = input.getAttribute('title');
|
|
1338
|
+
if (!hasLabel && !hasAria && !hasTitle && input.getAttribute('type') !== 'hidden') {
|
|
1339
|
+
a11yViolations.push({ type: 'missing-label', element: elementLabel(input), details: `type: ${input.getAttribute('type') || 'text'}` });
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
// 3. Heading hierarchy
|
|
1343
|
+
const headings = root.querySelectorAll('h1, h2, h3, h4, h5, h6');
|
|
1344
|
+
let prevLevel = 0;
|
|
1345
|
+
for (const h of Array.from(headings)) {
|
|
1346
|
+
const level = parseInt(h.tagName[1]);
|
|
1347
|
+
headingStructure.push(h.tagName.toLowerCase());
|
|
1348
|
+
if (prevLevel > 0 && level > prevLevel + 1) {
|
|
1349
|
+
a11yViolations.push({ type: 'heading-skip', element: elementLabel(h), details: `Jumped from h${prevLevel} to h${level}` });
|
|
1350
|
+
}
|
|
1351
|
+
prevLevel = level;
|
|
1352
|
+
}
|
|
1353
|
+
// 4. Empty links
|
|
1354
|
+
const links = root.querySelectorAll('a');
|
|
1355
|
+
for (const link of Array.from(links)) {
|
|
1356
|
+
const text = (link.textContent || '').trim();
|
|
1357
|
+
const aria = link.getAttribute('aria-label');
|
|
1358
|
+
const title = link.getAttribute('title');
|
|
1359
|
+
const hasImg = link.querySelector('img[alt]');
|
|
1360
|
+
if (!text && !aria && !title && !hasImg) {
|
|
1361
|
+
a11yViolations.push({ type: 'empty-link', element: elementLabel(link), details: `href: ${(link.getAttribute('href') || '').slice(0, 60)}` });
|
|
1362
|
+
}
|
|
1363
|
+
}
|
|
1364
|
+
// 5. Empty buttons
|
|
1365
|
+
const buttons = root.querySelectorAll('button');
|
|
1366
|
+
for (const btn of Array.from(buttons)) {
|
|
1367
|
+
const text = (btn.textContent || '').trim();
|
|
1368
|
+
const aria = btn.getAttribute('aria-label');
|
|
1369
|
+
if (!text && !aria) {
|
|
1370
|
+
a11yViolations.push({ type: 'empty-button', element: elementLabel(btn), details: '' });
|
|
1371
|
+
}
|
|
1372
|
+
}
|
|
1167
1373
|
return {
|
|
1374
|
+
colorScheme: detectPageColorScheme(),
|
|
1168
1375
|
spacingViolations: spacingViolations.slice(0, 50),
|
|
1169
1376
|
touchTargetViolations: touchTargetViolations.slice(0, 50),
|
|
1170
1377
|
contrastViolations: contrastViolations.slice(0, 50),
|
|
@@ -1174,27 +1381,60 @@ export async function browserDesignAudit(url, options = {}) {
|
|
|
1174
1381
|
letterSpacings: Array.from(letterSpacingsSet).slice(0, 20),
|
|
1175
1382
|
},
|
|
1176
1383
|
spacingScale: [...new Set(spacingScale)].slice(0, 30),
|
|
1384
|
+
accessibilityViolations: a11yViolations.slice(0, 50),
|
|
1385
|
+
headingStructure,
|
|
1177
1386
|
};
|
|
1178
1387
|
}, { sel: selector, spacingGrid, minTouchTarget, minContrast });
|
|
1179
1388
|
});
|
|
1180
1389
|
// Weighted scoring: contrast failures are most serious (accessibility),
|
|
1181
|
-
// touch target issues affect usability, spacing is cosmetic.
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
const
|
|
1185
|
-
const
|
|
1186
|
-
const
|
|
1390
|
+
// touch target issues affect usability, spacing is cosmetic, a11y is significant.
|
|
1391
|
+
// Only count contrast violations where we could resolve the background (bgResolved: true).
|
|
1392
|
+
// Violations with unresolvable backgrounds (backdrop-filter etc.) are excluded from scoring.
|
|
1393
|
+
const resolvedContrastViolations = auditData.contrastViolations.filter(v => v.bgResolved !== false);
|
|
1394
|
+
const unresolvedContrastViolations = auditData.contrastViolations.filter(v => v.bgResolved === false);
|
|
1395
|
+
const contrastPenalty = Math.min(40, resolvedContrastViolations.length * 5); // cap at 40pts
|
|
1396
|
+
const touchPenalty = Math.min(30, auditData.touchTargetViolations.length * 3); // cap at 30pts
|
|
1397
|
+
const spacingPenalty = Math.min(20, auditData.spacingViolations.length * 1);
|
|
1398
|
+
const a11yPenalty = Math.min(30, auditData.accessibilityViolations.length * 4);
|
|
1399
|
+
// Bonus for zero violations in a category (up to 5 pts total)
|
|
1400
|
+
let bonus = 0;
|
|
1401
|
+
if (resolvedContrastViolations.length === 0)
|
|
1402
|
+
bonus += 2;
|
|
1403
|
+
if (auditData.touchTargetViolations.length === 0)
|
|
1404
|
+
bonus += 1;
|
|
1405
|
+
if (auditData.accessibilityViolations.length === 0)
|
|
1406
|
+
bonus += 2;
|
|
1407
|
+
const totalPenalty = contrastPenalty + touchPenalty + spacingPenalty + a11yPenalty;
|
|
1408
|
+
const score = Math.min(100, Math.max(0, Math.round(100 - totalPenalty + bonus)));
|
|
1187
1409
|
const parts = [];
|
|
1188
1410
|
if (auditData.spacingViolations.length > 0)
|
|
1189
1411
|
parts.push(`${auditData.spacingViolations.length} spacing violation(s)`);
|
|
1190
1412
|
if (auditData.touchTargetViolations.length > 0)
|
|
1191
1413
|
parts.push(`${auditData.touchTargetViolations.length} touch target violation(s)`);
|
|
1192
|
-
if (
|
|
1193
|
-
parts.push(`${
|
|
1414
|
+
if (resolvedContrastViolations.length > 0)
|
|
1415
|
+
parts.push(`${resolvedContrastViolations.length} contrast violation(s)`);
|
|
1416
|
+
if (unresolvedContrastViolations.length > 0)
|
|
1417
|
+
parts.push(`${unresolvedContrastViolations.length} unresolvable contrast check(s)`);
|
|
1418
|
+
if (auditData.accessibilityViolations.length > 0)
|
|
1419
|
+
parts.push(`${auditData.accessibilityViolations.length} accessibility violation(s)`);
|
|
1194
1420
|
const summary = parts.length === 0
|
|
1195
1421
|
? 'No design violations found.'
|
|
1196
1422
|
: `Found: ${parts.join(', ')}.`;
|
|
1197
1423
|
const audit = { score, summary, ...auditData };
|
|
1198
1424
|
return { audit, finalUrl };
|
|
1199
1425
|
}
|
|
1426
|
+
// ── browserDesignAnalysis ──────────────────────────────────────────────────────
|
|
1427
|
+
/**
|
|
1428
|
+
* Extract structured visual design intelligence from a URL using a browser.
|
|
1429
|
+
* Returns a DesignAnalysis object with effects, palette, layout, type scale,
|
|
1430
|
+
* and quality signals.
|
|
1431
|
+
*/
|
|
1432
|
+
export async function browserDesignAnalysis(url, options = {}) {
|
|
1433
|
+
const { width = 1440, height = 900, waitMs = 0, timeoutMs = 60000, userAgent, headers, cookies, stealth = false, } = options;
|
|
1434
|
+
const { extractDesignAnalysis } = await import('./design-analysis.js');
|
|
1435
|
+
const { result: analysis, finalUrl } = await withBrowserPage(url, { width, height, userAgent, headers, cookies, stealth, waitMs, timeoutMs }, async (page) => {
|
|
1436
|
+
return extractDesignAnalysis(page);
|
|
1437
|
+
});
|
|
1438
|
+
return { analysis, finalUrl };
|
|
1439
|
+
}
|
|
1200
1440
|
//# sourceMappingURL=browser-fetch.js.map
|