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.
@@ -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 — Fix: skip near-identical colors (ratio 1.05) to avoid false positives
1278
+ // Contrast — Walk up DOM tree to find effective opaque background
1143
1279
  const textColor = style.color;
1144
- const bgColor = style.backgroundColor;
1145
- if (textColor && bgColor) {
1280
+ if (textColor) {
1146
1281
  const fg = parseRgb(textColor);
1147
- const bg = parseRgb(bgColor);
1148
- if (fg && bg) {
1149
- const ratio = contrastRatio(fg, bg);
1150
- if (ratio > 1.05 && ratio < minContrast) {
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: Math.round(ratio * 100) / 100,
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
- const contrastPenalty = auditData.contrastViolations.length * 5;
1183
- const touchPenalty = auditData.touchTargetViolations.length * 3;
1184
- const spacingPenalty = auditData.spacingViolations.length * 1;
1185
- const totalPenalty = contrastPenalty + touchPenalty + spacingPenalty;
1186
- const score = Math.max(0, Math.round(100 - Math.min(100, totalPenalty)));
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 (auditData.contrastViolations.length > 0)
1193
- parts.push(`${auditData.contrastViolations.length} contrast violation(s)`);
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