@logicsync/pixelprobe 0.1.0

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.
Files changed (75) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +377 -0
  3. package/dist/bin/pixelprobe.d.ts +3 -0
  4. package/dist/bin/pixelprobe.d.ts.map +1 -0
  5. package/dist/bin/pixelprobe.js +218 -0
  6. package/dist/bin/pixelprobe.js.map +1 -0
  7. package/dist/browser/BrowserManager.d.ts +20 -0
  8. package/dist/browser/BrowserManager.d.ts.map +1 -0
  9. package/dist/browser/BrowserManager.js +64 -0
  10. package/dist/browser/BrowserManager.js.map +1 -0
  11. package/dist/browser/ViewportManager.d.ts +15 -0
  12. package/dist/browser/ViewportManager.d.ts.map +1 -0
  13. package/dist/browser/ViewportManager.js +65 -0
  14. package/dist/browser/ViewportManager.js.map +1 -0
  15. package/dist/cli/formatter.d.ts +4 -0
  16. package/dist/cli/formatter.d.ts.map +1 -0
  17. package/dist/cli/formatter.js +107 -0
  18. package/dist/cli/formatter.js.map +1 -0
  19. package/dist/comparator/DiffAggregator.d.ts +24 -0
  20. package/dist/comparator/DiffAggregator.d.ts.map +1 -0
  21. package/dist/comparator/DiffAggregator.js +94 -0
  22. package/dist/comparator/DiffAggregator.js.map +1 -0
  23. package/dist/comparator/LayoutComparator.d.ts +8 -0
  24. package/dist/comparator/LayoutComparator.d.ts.map +1 -0
  25. package/dist/comparator/LayoutComparator.js +68 -0
  26. package/dist/comparator/LayoutComparator.js.map +1 -0
  27. package/dist/comparator/PixelComparator.d.ts +9 -0
  28. package/dist/comparator/PixelComparator.d.ts.map +1 -0
  29. package/dist/comparator/PixelComparator.js +72 -0
  30. package/dist/comparator/PixelComparator.js.map +1 -0
  31. package/dist/comparator/StyleComparator.d.ts +8 -0
  32. package/dist/comparator/StyleComparator.d.ts.map +1 -0
  33. package/dist/comparator/StyleComparator.js +119 -0
  34. package/dist/comparator/StyleComparator.js.map +1 -0
  35. package/dist/core.d.ts +23 -0
  36. package/dist/core.d.ts.map +1 -0
  37. package/dist/core.js +211 -0
  38. package/dist/core.js.map +1 -0
  39. package/dist/extractor/ScreenshotCapturer.d.ts +20 -0
  40. package/dist/extractor/ScreenshotCapturer.d.ts.map +1 -0
  41. package/dist/extractor/ScreenshotCapturer.js +56 -0
  42. package/dist/extractor/ScreenshotCapturer.js.map +1 -0
  43. package/dist/extractor/SectionExtractor.d.ts +26 -0
  44. package/dist/extractor/SectionExtractor.d.ts.map +1 -0
  45. package/dist/extractor/SectionExtractor.js +92 -0
  46. package/dist/extractor/SectionExtractor.js.map +1 -0
  47. package/dist/extractor/StyleExtractor.d.ts +23 -0
  48. package/dist/extractor/StyleExtractor.d.ts.map +1 -0
  49. package/dist/extractor/StyleExtractor.js +88 -0
  50. package/dist/extractor/StyleExtractor.js.map +1 -0
  51. package/dist/index.d.ts +8 -0
  52. package/dist/index.d.ts.map +1 -0
  53. package/dist/index.js +9 -0
  54. package/dist/index.js.map +1 -0
  55. package/dist/mcp/server.d.ts +3 -0
  56. package/dist/mcp/server.d.ts.map +1 -0
  57. package/dist/mcp/server.js +203 -0
  58. package/dist/mcp/server.js.map +1 -0
  59. package/dist/reporter/HTMLReporter.d.ts +8 -0
  60. package/dist/reporter/HTMLReporter.d.ts.map +1 -0
  61. package/dist/reporter/HTMLReporter.js +219 -0
  62. package/dist/reporter/HTMLReporter.js.map +1 -0
  63. package/dist/reporter/JSONReporter.d.ts +17 -0
  64. package/dist/reporter/JSONReporter.d.ts.map +1 -0
  65. package/dist/reporter/JSONReporter.js +77 -0
  66. package/dist/reporter/JSONReporter.js.map +1 -0
  67. package/dist/types/comparison.d.ts +133 -0
  68. package/dist/types/comparison.d.ts.map +1 -0
  69. package/dist/types/comparison.js +3 -0
  70. package/dist/types/comparison.js.map +1 -0
  71. package/dist/types/config.d.ts +138 -0
  72. package/dist/types/config.d.ts.map +1 -0
  73. package/dist/types/config.js +151 -0
  74. package/dist/types/config.js.map +1 -0
  75. package/package.json +72 -0
@@ -0,0 +1,119 @@
1
+ import { STYLE_CATEGORIES } from "../types/config.js";
2
+ // Properties that have high visual impact when different
3
+ const CRITICAL_PROPERTIES = new Set([
4
+ "display",
5
+ "position",
6
+ "width",
7
+ "height",
8
+ "flex-direction",
9
+ "grid-template-columns",
10
+ "grid-template-rows",
11
+ "font-size",
12
+ "visibility",
13
+ "overflow",
14
+ ]);
15
+ // Properties with moderate visual impact
16
+ const WARNING_PROPERTIES = new Set([
17
+ "margin",
18
+ "margin-top",
19
+ "margin-right",
20
+ "margin-bottom",
21
+ "margin-left",
22
+ "padding",
23
+ "padding-top",
24
+ "padding-right",
25
+ "padding-bottom",
26
+ "padding-left",
27
+ "font-family",
28
+ "font-weight",
29
+ "line-height",
30
+ "text-align",
31
+ "color",
32
+ "background-color",
33
+ "border",
34
+ "border-radius",
35
+ "gap",
36
+ "justify-content",
37
+ "align-items",
38
+ "flex-wrap",
39
+ "max-width",
40
+ "max-height",
41
+ "min-width",
42
+ "min-height",
43
+ ]);
44
+ export class StyleComparator {
45
+ /**
46
+ * Compare computed styles from source and target elements.
47
+ */
48
+ static compare(sourceStyles, targetStyles) {
49
+ const differences = [];
50
+ // Build a reverse map: property → category
51
+ const propertyCategory = new Map();
52
+ for (const [cat, props] of Object.entries(STYLE_CATEGORIES)) {
53
+ for (const prop of props) {
54
+ propertyCategory.set(prop, cat);
55
+ }
56
+ }
57
+ // Union of all properties from both
58
+ const allProperties = new Set([
59
+ ...Object.keys(sourceStyles),
60
+ ...Object.keys(targetStyles),
61
+ ]);
62
+ for (const prop of allProperties) {
63
+ const sourceVal = normalizeValue(sourceStyles[prop] ?? "");
64
+ const targetVal = normalizeValue(targetStyles[prop] ?? "");
65
+ if (sourceVal !== targetVal) {
66
+ differences.push({
67
+ property: prop,
68
+ category: propertyCategory.get(prop) ?? "other",
69
+ sourceValue: sourceStyles[prop] ?? "(not set)",
70
+ targetValue: targetStyles[prop] ?? "(not set)",
71
+ severity: classifySeverity(prop),
72
+ });
73
+ }
74
+ }
75
+ // Sort by severity (critical first), then by property name
76
+ const severityOrder = {
77
+ critical: 0,
78
+ warning: 1,
79
+ info: 2,
80
+ };
81
+ differences.sort((a, b) => severityOrder[a.severity] - severityOrder[b.severity] ||
82
+ a.property.localeCompare(b.property));
83
+ return {
84
+ differences,
85
+ summary: {
86
+ total: differences.length,
87
+ critical: differences.filter((d) => d.severity === "critical").length,
88
+ warning: differences.filter((d) => d.severity === "warning").length,
89
+ info: differences.filter((d) => d.severity === "info").length,
90
+ },
91
+ sourceStyleCount: Object.keys(sourceStyles).length,
92
+ targetStyleCount: Object.keys(targetStyles).length,
93
+ };
94
+ }
95
+ }
96
+ // ── Helpers ───────────────────────────────────────────────────────────
97
+ function classifySeverity(property) {
98
+ if (CRITICAL_PROPERTIES.has(property))
99
+ return "critical";
100
+ if (WARNING_PROPERTIES.has(property))
101
+ return "warning";
102
+ return "info";
103
+ }
104
+ /**
105
+ * Normalize CSS values for comparison.
106
+ * E.g., "rgb(255, 0, 0)" vs "rgb(255,0,0)" should match.
107
+ */
108
+ function normalizeValue(value) {
109
+ return value
110
+ .trim()
111
+ .toLowerCase()
112
+ .replace(/\s+/g, " ") // collapse whitespace
113
+ .replace(/,\s*/g, ", ") // normalize comma spacing
114
+ .replace(/\s*\/\s*/g, " / ") // normalize slash spacing
115
+ .replace(/0px/g, "0") // 0px → 0
116
+ .replace(/\.0+(?=\s|,|$|\))/g, "") // remove trailing .0
117
+ .replace(/rgba?\((\d+),\s*(\d+),\s*(\d+),\s*1\)/g, "rgb($1, $2, $3)"); // rgba(x,y,z,1) → rgb(x,y,z)
118
+ }
119
+ //# sourceMappingURL=StyleComparator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StyleComparator.js","sourceRoot":"","sources":["../../src/comparator/StyleComparator.ts"],"names":[],"mappings":"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"}
package/dist/core.d.ts ADDED
@@ -0,0 +1,23 @@
1
+ import type { ComparisonConfig, Breakpoint } from "./types/config.js";
2
+ import type { ComparisonResult, ProgressCallback, ElementInfo } from "./types/comparison.js";
3
+ export declare class WebSectionComparator {
4
+ private browserManager;
5
+ constructor(options?: {
6
+ headless?: boolean;
7
+ timeout?: number;
8
+ });
9
+ /**
10
+ * Run a full comparison across all breakpoints.
11
+ */
12
+ compare(config: ComparisonConfig, onProgress?: ProgressCallback): Promise<ComparisonResult>;
13
+ /**
14
+ * Enumerate matching elements for a selector on a URL.
15
+ * Useful for disambiguation before comparing.
16
+ */
17
+ enumerate(url: string, selector: string, breakpoint?: string): Promise<ElementInfo[]>;
18
+ /**
19
+ * List available breakpoint presets.
20
+ */
21
+ listBreakpoints(): Breakpoint[];
22
+ }
23
+ //# sourceMappingURL=core.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core.d.ts","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EACV,gBAAgB,EAEhB,UAAU,EAEX,MAAM,mBAAmB,CAAC;AAE3B,OAAO,KAAK,EACV,gBAAgB,EAGhB,gBAAgB,EAChB,WAAW,EACZ,MAAM,uBAAuB,CAAC;AAM/B,qBAAa,oBAAoB;IAC/B,OAAO,CAAC,cAAc,CAAiB;gBAE3B,OAAO,CAAC,EAAE;QAAE,QAAQ,CAAC,EAAE,OAAO,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAAE;IAI9D;;OAEG;IACG,OAAO,CACX,MAAM,EAAE,gBAAgB,EACxB,UAAU,CAAC,EAAE,gBAAgB,GAC5B,OAAO,CAAC,gBAAgB,CAAC;IAkI5B;;;OAGG;IACG,SAAS,CACb,GAAG,EAAE,MAAM,EACX,QAAQ,EAAE,MAAM,EAChB,UAAU,CAAC,EAAE,MAAM,GAClB,OAAO,CAAC,WAAW,EAAE,CAAC;IAqBzB;;OAEG;IACH,eAAe;CAGhB"}
package/dist/core.js ADDED
@@ -0,0 +1,211 @@
1
+ import { randomUUID } from "node:crypto";
2
+ import { ComparisonConfigSchema } from "./types/config.js";
3
+ import { BrowserManager } from "./browser/BrowserManager.js";
4
+ import { ViewportManager } from "./browser/ViewportManager.js";
5
+ import { SectionExtractor } from "./extractor/SectionExtractor.js";
6
+ import { DiffAggregator } from "./comparator/DiffAggregator.js";
7
+ export class WebSectionComparator {
8
+ browserManager;
9
+ constructor(options) {
10
+ this.browserManager = new BrowserManager(options);
11
+ }
12
+ /**
13
+ * Run a full comparison across all breakpoints.
14
+ */
15
+ async compare(config, onProgress) {
16
+ const start = Date.now();
17
+ const id = randomUUID().slice(0, 8);
18
+ // Validate config
19
+ const validated = ComparisonConfigSchema.parse(config);
20
+ // Resolve breakpoints
21
+ const resolvedBreakpoints = ViewportManager.resolve(validated.breakpoints, validated.customBreakpoints);
22
+ const resolvedConfig = {
23
+ ...validated,
24
+ resolvedBreakpoints,
25
+ };
26
+ onProgress?.({
27
+ phase: "init",
28
+ message: `Starting comparison with ${resolvedBreakpoints.length} breakpoints`,
29
+ progress: 0,
30
+ });
31
+ // Resolve separate selectors for source and target
32
+ const sourceSelector = validated.selector;
33
+ const targetSelector = validated.targetSelector ?? validated.selector;
34
+ try {
35
+ await this.browserManager.launch();
36
+ const breakpointResults = {};
37
+ for (let i = 0; i < resolvedBreakpoints.length; i++) {
38
+ const bp = resolvedBreakpoints[i];
39
+ const progressBase = Math.round(((i / resolvedBreakpoints.length) * 80) + 10);
40
+ onProgress?.({
41
+ phase: "extracting",
42
+ breakpoint: bp.name,
43
+ message: `Comparing at ${bp.name} (${bp.width}×${bp.height})`,
44
+ progress: progressBase,
45
+ });
46
+ // Create pages for this breakpoint
47
+ const sourcePage = await this.browserManager.createPage(bp);
48
+ const targetPage = await this.browserManager.createPage(bp);
49
+ try {
50
+ // Navigate both pages (each waits for its own selector)
51
+ await Promise.all([
52
+ this.browserManager.navigateAndWait(sourcePage, validated.sourceUrl, {
53
+ waitForSelector: validated.waitForSelector ?? sourceSelector,
54
+ waitForTimeout: validated.waitForTimeout,
55
+ }),
56
+ this.browserManager.navigateAndWait(targetPage, validated.targetUrl, {
57
+ waitForSelector: validated.waitForSelector ?? targetSelector,
58
+ waitForTimeout: validated.waitForTimeout,
59
+ }),
60
+ ]);
61
+ // Run comparison for this breakpoint
62
+ const elementIdx = typeof validated.elementIndex === "number"
63
+ ? validated.elementIndex
64
+ : 0;
65
+ const result = await DiffAggregator.compareBreakpoint(sourcePage, targetPage, bp, {
66
+ selector: sourceSelector,
67
+ targetSelector,
68
+ elementIndex: elementIdx,
69
+ styleCategories: validated.styleCategories,
70
+ pixelThreshold: validated.pixelThreshold,
71
+ includeVisual: validated.includeVisual,
72
+ includeStyles: validated.includeStyles,
73
+ includeLayout: validated.includeLayout,
74
+ });
75
+ breakpointResults[bp.name] = result;
76
+ }
77
+ finally {
78
+ // Always close contexts to free resources
79
+ await sourcePage.context().close();
80
+ await targetPage.context().close();
81
+ }
82
+ }
83
+ onProgress?.({
84
+ phase: "reporting",
85
+ message: "Generating report",
86
+ progress: 90,
87
+ });
88
+ const summary = computeSummary(breakpointResults);
89
+ const result = {
90
+ id,
91
+ timestamp: new Date().toISOString(),
92
+ config: {
93
+ sourceUrl: validated.sourceUrl,
94
+ targetUrl: validated.targetUrl,
95
+ selector: sourceSelector,
96
+ targetSelector: targetSelector !== sourceSelector ? targetSelector : undefined,
97
+ elementIndex: validated.elementIndex,
98
+ },
99
+ breakpoints: breakpointResults,
100
+ summary,
101
+ artifacts: {
102
+ diffImages: {},
103
+ sourceScreenshots: {},
104
+ targetScreenshots: {},
105
+ },
106
+ duration: Date.now() - start,
107
+ };
108
+ onProgress?.({
109
+ phase: "done",
110
+ message: "Comparison complete",
111
+ progress: 100,
112
+ });
113
+ return result;
114
+ }
115
+ finally {
116
+ await this.browserManager.close();
117
+ }
118
+ }
119
+ /**
120
+ * Enumerate matching elements for a selector on a URL.
121
+ * Useful for disambiguation before comparing.
122
+ */
123
+ async enumerate(url, selector, breakpoint) {
124
+ const bp = ViewportManager.resolve([breakpoint ?? "desktop"])[0];
125
+ try {
126
+ await this.browserManager.launch();
127
+ const page = await this.browserManager.createPage(bp);
128
+ try {
129
+ await this.browserManager.navigateAndWait(page, url, {
130
+ waitForSelector: selector,
131
+ });
132
+ return await SectionExtractor.enumerate(page, selector);
133
+ }
134
+ finally {
135
+ await page.context().close();
136
+ }
137
+ }
138
+ finally {
139
+ await this.browserManager.close();
140
+ }
141
+ }
142
+ /**
143
+ * List available breakpoint presets.
144
+ */
145
+ listBreakpoints() {
146
+ return ViewportManager.listPresets();
147
+ }
148
+ }
149
+ // ── Helpers ───────────────────────────────────────────────────────────
150
+ function computeSummary(breakpoints) {
151
+ const entries = Object.entries(breakpoints);
152
+ let totalStyleDiffs = 0;
153
+ let totalLayoutDiffs = 0;
154
+ let totalPixelDiff = 0;
155
+ let pixelDiffCount = 0;
156
+ let breakpointsWithDiffs = 0;
157
+ let worstBreakpoint = null;
158
+ let worstScore = 0;
159
+ for (const [name, result] of entries) {
160
+ if (result.error)
161
+ continue;
162
+ const hasDiffs = (result.styles?.summary.total ?? 0) > 0 ||
163
+ (result.layout?.summary.total ?? 0) > 0 ||
164
+ (result.visual?.diffPercentage ?? 0) > 0;
165
+ if (hasDiffs)
166
+ breakpointsWithDiffs++;
167
+ totalStyleDiffs += result.styles?.summary.total ?? 0;
168
+ totalLayoutDiffs += result.layout?.summary.total ?? 0;
169
+ if (result.visual) {
170
+ totalPixelDiff += result.visual.diffPercentage;
171
+ pixelDiffCount++;
172
+ }
173
+ // Score = weighted sum of issues
174
+ const score = (result.styles?.summary.critical ?? 0) * 10 +
175
+ (result.styles?.summary.warning ?? 0) * 3 +
176
+ (result.styles?.summary.info ?? 0) * 1 +
177
+ (result.layout?.summary.critical ?? 0) * 10 +
178
+ (result.layout?.summary.warning ?? 0) * 3 +
179
+ (result.visual?.diffPercentage ?? 0) * 2;
180
+ if (score > worstScore) {
181
+ worstScore = score;
182
+ worstBreakpoint = name;
183
+ }
184
+ }
185
+ const avgPixelDiff = pixelDiffCount > 0
186
+ ? Math.round((totalPixelDiff / pixelDiffCount) * 100) / 100
187
+ : 0;
188
+ // Determine overall severity
189
+ const hasCritical = entries.some(([, r]) => (r.styles?.summary.critical ?? 0) > 0 ||
190
+ (r.layout?.summary.critical ?? 0) > 0 ||
191
+ (r.visual?.diffPercentage ?? 0) > 5);
192
+ const hasWarning = entries.some(([, r]) => (r.styles?.summary.warning ?? 0) > 0 ||
193
+ (r.layout?.summary.warning ?? 0) > 0 ||
194
+ (r.visual?.diffPercentage ?? 0) > 1);
195
+ return {
196
+ totalBreakpoints: entries.length,
197
+ breakpointsWithDifferences: breakpointsWithDiffs,
198
+ totalStyleDifferences: totalStyleDiffs,
199
+ totalLayoutDifferences: totalLayoutDiffs,
200
+ averagePixelDiff: avgPixelDiff,
201
+ worstBreakpoint,
202
+ overallSeverity: hasCritical
203
+ ? "critical"
204
+ : hasWarning
205
+ ? "warning"
206
+ : breakpointsWithDiffs > 0
207
+ ? "info"
208
+ : "pass",
209
+ };
210
+ }
211
+ //# sourceMappingURL=core.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"core.js","sourceRoot":"","sources":["../src/core.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import type { Locator, Page } from "playwright";
2
+ export interface ScreenshotOptions {
3
+ fullPage?: boolean;
4
+ padding?: number;
5
+ }
6
+ export declare class ScreenshotCapturer {
7
+ /**
8
+ * Capture a screenshot of a specific element.
9
+ */
10
+ static captureElement(locator: Locator, options?: ScreenshotOptions): Promise<Buffer>;
11
+ /**
12
+ * Capture a screenshot of the full page.
13
+ */
14
+ static captureFullPage(page: Page): Promise<Buffer>;
15
+ /**
16
+ * Capture element with surrounding context (padding around it).
17
+ */
18
+ static captureElementWithContext(locator: Locator, padding?: number): Promise<Buffer>;
19
+ }
20
+ //# sourceMappingURL=ScreenshotCapturer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ScreenshotCapturer.d.ts","sourceRoot":"","sources":["../../src/extractor/ScreenshotCapturer.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AAEhD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,qBAAa,kBAAkB;IAC7B;;OAEG;WACU,cAAc,CACzB,OAAO,EAAE,OAAO,EAChB,OAAO,GAAE,iBAAsB,GAC9B,OAAO,CAAC,MAAM,CAAC;IAelB;;OAEG;WACU,eAAe,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,MAAM,CAAC;IAUzD;;OAEG;WACU,yBAAyB,CACpC,OAAO,EAAE,OAAO,EAChB,OAAO,GAAE,MAAW,GACnB,OAAO,CAAC,MAAM,CAAC;CA8BnB"}
@@ -0,0 +1,56 @@
1
+ export class ScreenshotCapturer {
2
+ /**
3
+ * Capture a screenshot of a specific element.
4
+ */
5
+ static async captureElement(locator, options = {}) {
6
+ // Scroll element into view first
7
+ await locator.scrollIntoViewIfNeeded();
8
+ // Small wait to ensure rendering is complete
9
+ await locator.page().waitForTimeout(100);
10
+ const screenshot = await locator.screenshot({
11
+ type: "png",
12
+ animations: "disabled",
13
+ });
14
+ return Buffer.from(screenshot);
15
+ }
16
+ /**
17
+ * Capture a screenshot of the full page.
18
+ */
19
+ static async captureFullPage(page) {
20
+ const screenshot = await page.screenshot({
21
+ type: "png",
22
+ fullPage: true,
23
+ animations: "disabled",
24
+ });
25
+ return Buffer.from(screenshot);
26
+ }
27
+ /**
28
+ * Capture element with surrounding context (padding around it).
29
+ */
30
+ static async captureElementWithContext(locator, padding = 20) {
31
+ await locator.scrollIntoViewIfNeeded();
32
+ await locator.page().waitForTimeout(100);
33
+ const box = await locator.boundingBox();
34
+ if (!box) {
35
+ throw new Error("Element has no bounding box (may be hidden)");
36
+ }
37
+ const page = locator.page();
38
+ const viewport = page.viewportSize();
39
+ if (!viewport) {
40
+ throw new Error("Page has no viewport size");
41
+ }
42
+ const clip = {
43
+ x: Math.max(0, box.x - padding),
44
+ y: Math.max(0, box.y - padding),
45
+ width: Math.min(viewport.width - Math.max(0, box.x - padding), box.width + padding * 2),
46
+ height: box.height + padding * 2,
47
+ };
48
+ const screenshot = await page.screenshot({
49
+ type: "png",
50
+ clip,
51
+ animations: "disabled",
52
+ });
53
+ return Buffer.from(screenshot);
54
+ }
55
+ }
56
+ //# sourceMappingURL=ScreenshotCapturer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ScreenshotCapturer.js","sourceRoot":"","sources":["../../src/extractor/ScreenshotCapturer.ts"],"names":[],"mappings":"AAOA,MAAM,OAAO,kBAAkB;IAC7B;;OAEG;IACH,MAAM,CAAC,KAAK,CAAC,cAAc,CACzB,OAAgB,EAChB,UAA6B,EAAE;QAE/B,iCAAiC;QACjC,MAAM,OAAO,CAAC,sBAAsB,EAAE,CAAC;QAEvC,6CAA6C;QAC7C,MAAM,OAAO,CAAC,IAAI,EAAE,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC;QAEzC,MAAM,UAAU,GAAG,MAAM,OAAO,CAAC,UAAU,CAAC;YAC1C,IAAI,EAAE,KAAK;YACX,UAAU,EAAE,UAAU;SACvB,CAAC,CAAC;QAEH,OAAO,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACjC,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,KAAK,CAAC,eAAe,CAAC,IAAU;QACrC,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC;YACvC,IAAI,EAAE,KAAK;YACX,QAAQ,EAAE,IAAI;YACd,UAAU,EAAE,UAAU;SACvB,CAAC,CAAC;QAEH,OAAO,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACjC,CAAC;IAED;;OAEG;IACH,MAAM,CAAC,KAAK,CAAC,yBAAyB,CACpC,OAAgB,EAChB,UAAkB,EAAE;QAEpB,MAAM,OAAO,CAAC,sBAAsB,EAAE,CAAC;QACvC,MAAM,OAAO,CAAC,IAAI,EAAE,CAAC,cAAc,CAAC,GAAG,CAAC,CAAC;QAEzC,MAAM,GAAG,GAAG,MAAM,OAAO,CAAC,WAAW,EAAE,CAAC;QACxC,IAAI,CAAC,GAAG,EAAE,CAAC;YACT,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAC;QACjE,CAAC;QAED,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,EAAE,CAAC;QAC5B,MAAM,QAAQ,GAAG,IAAI,CAAC,YAAY,EAAE,CAAC;QACrC,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,MAAM,IAAI,KAAK,CAAC,2BAA2B,CAAC,CAAC;QAC/C,CAAC;QAED,MAAM,IAAI,GAAG;YACX,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,OAAO,CAAC;YAC/B,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,OAAO,CAAC;YAC/B,KAAK,EAAE,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC,GAAG,OAAO,CAAC,EAAE,GAAG,CAAC,KAAK,GAAG,OAAO,GAAG,CAAC,CAAC;YACvF,MAAM,EAAE,GAAG,CAAC,MAAM,GAAG,OAAO,GAAG,CAAC;SACjC,CAAC;QAEF,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,UAAU,CAAC;YACvC,IAAI,EAAE,KAAK;YACX,IAAI;YACJ,UAAU,EAAE,UAAU;SACvB,CAAC,CAAC;QAEH,OAAO,MAAM,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;IACjC,CAAC;CACF"}
@@ -0,0 +1,26 @@
1
+ import type { Page } from "playwright";
2
+ import type { ElementInfo } from "../types/comparison.js";
3
+ export declare class SectionExtractor {
4
+ /**
5
+ * Find all elements matching a selector and return their info.
6
+ * Used for disambiguation when multiple elements match.
7
+ */
8
+ static enumerate(page: Page, selector: string): Promise<ElementInfo[]>;
9
+ /**
10
+ * Get a specific element by selector and index.
11
+ * Returns the Playwright ElementHandle locator for further operations.
12
+ */
13
+ static getElement(page: Page, selector: string, index?: number): Promise<{
14
+ info: ElementInfo;
15
+ locator: ReturnType<Page["locator"]>;
16
+ }>;
17
+ /**
18
+ * Validate that a selector matches at least one element.
19
+ */
20
+ static validate(page: Page, selector: string): Promise<{
21
+ valid: boolean;
22
+ count: number;
23
+ error?: string;
24
+ }>;
25
+ }
26
+ //# sourceMappingURL=SectionExtractor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SectionExtractor.d.ts","sourceRoot":"","sources":["../../src/extractor/SectionExtractor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,YAAY,CAAC;AACvC,OAAO,KAAK,EAAE,WAAW,EAAe,MAAM,wBAAwB,CAAC;AAEvE,qBAAa,gBAAgB;IAC3B;;;OAGG;WACU,SAAS,CACpB,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,WAAW,EAAE,CAAC;IA0BzB;;;OAGG;WACU,UAAU,CACrB,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,MAAM,EAChB,KAAK,GAAE,MAAU,GAChB,OAAO,CAAC;QAAE,IAAI,EAAE,WAAW,CAAC;QAAC,OAAO,EAAE,UAAU,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC,CAAA;KAAE,CAAC;IA+CvE;;OAEG;WACU,QAAQ,CACnB,IAAI,EAAE,IAAI,EACV,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC;QAAE,KAAK,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;CAgB9D"}
@@ -0,0 +1,92 @@
1
+ export class SectionExtractor {
2
+ /**
3
+ * Find all elements matching a selector and return their info.
4
+ * Used for disambiguation when multiple elements match.
5
+ */
6
+ static async enumerate(page, selector) {
7
+ return await page.evaluate(({ sel }) => {
8
+ const elements = document.querySelectorAll(sel);
9
+ return Array.from(elements).map((el, index) => {
10
+ const rect = el.getBoundingClientRect();
11
+ const text = (el.textContent || "").trim().slice(0, 80);
12
+ return {
13
+ selector: sel,
14
+ index,
15
+ tagName: el.tagName.toLowerCase(),
16
+ textPreview: text.length === 80 ? text + "…" : text,
17
+ boundingBox: {
18
+ x: Math.round(rect.x),
19
+ y: Math.round(rect.y),
20
+ width: Math.round(rect.width),
21
+ height: Math.round(rect.height),
22
+ },
23
+ exists: true,
24
+ };
25
+ });
26
+ }, { sel: selector });
27
+ }
28
+ /**
29
+ * Get a specific element by selector and index.
30
+ * Returns the Playwright ElementHandle locator for further operations.
31
+ */
32
+ static async getElement(page, selector, index = 0) {
33
+ const locator = page.locator(selector).nth(index);
34
+ const count = await page.locator(selector).count();
35
+ if (count === 0) {
36
+ return {
37
+ info: {
38
+ selector,
39
+ index,
40
+ tagName: "unknown",
41
+ textPreview: "",
42
+ boundingBox: { x: 0, y: 0, width: 0, height: 0 },
43
+ exists: false,
44
+ },
45
+ locator,
46
+ };
47
+ }
48
+ if (index >= count) {
49
+ throw new Error(`Element index ${index} out of range. Selector "${selector}" matched ${count} element(s).`);
50
+ }
51
+ const info = await locator.evaluate((el, idx) => {
52
+ const rect = el.getBoundingClientRect();
53
+ const text = (el.textContent || "").trim().slice(0, 80);
54
+ return {
55
+ selector: "", // filled below
56
+ index: idx,
57
+ tagName: el.tagName.toLowerCase(),
58
+ textPreview: text.length === 80 ? text + "…" : text,
59
+ boundingBox: {
60
+ x: Math.round(rect.x),
61
+ y: Math.round(rect.y),
62
+ width: Math.round(rect.width),
63
+ height: Math.round(rect.height),
64
+ },
65
+ exists: true,
66
+ };
67
+ }, index);
68
+ info.selector = selector;
69
+ return { info, locator };
70
+ }
71
+ /**
72
+ * Validate that a selector matches at least one element.
73
+ */
74
+ static async validate(page, selector) {
75
+ try {
76
+ const count = await page.locator(selector).count();
77
+ return {
78
+ valid: count > 0,
79
+ count,
80
+ error: count === 0 ? `No elements found matching "${selector}"` : undefined,
81
+ };
82
+ }
83
+ catch (err) {
84
+ return {
85
+ valid: false,
86
+ count: 0,
87
+ error: `Invalid selector "${selector}": ${err.message}`,
88
+ };
89
+ }
90
+ }
91
+ }
92
+ //# sourceMappingURL=SectionExtractor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SectionExtractor.js","sourceRoot":"","sources":["../../src/extractor/SectionExtractor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ import type { Locator } from "playwright";
2
+ import { type StyleCategory } from "../types/config.js";
3
+ import type { LayoutMetrics } from "../types/comparison.js";
4
+ export declare class StyleExtractor {
5
+ /**
6
+ * Extract computed styles for specific categories from an element.
7
+ */
8
+ static extractStyles(locator: Locator, categories?: StyleCategory[]): Promise<Record<string, string>>;
9
+ /**
10
+ * Extract layout metrics (box model) from an element.
11
+ */
12
+ static extractLayoutMetrics(locator: Locator): Promise<LayoutMetrics>;
13
+ /**
14
+ * Extract computed styles for ALL properties (not just categorized ones).
15
+ * Useful for deep analysis.
16
+ */
17
+ static extractAllStyles(locator: Locator): Promise<Record<string, string>>;
18
+ /**
19
+ * Get the flat list of CSS properties for given categories.
20
+ */
21
+ private static getProperties;
22
+ }
23
+ //# sourceMappingURL=StyleExtractor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"StyleExtractor.d.ts","sourceRoot":"","sources":["../../src/extractor/StyleExtractor.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAQ,OAAO,EAAE,MAAM,YAAY,CAAC;AAChD,OAAO,EAAoB,KAAK,aAAa,EAAE,MAAM,oBAAoB,CAAC;AAC1E,OAAO,KAAK,EAAE,aAAa,EAAc,MAAM,wBAAwB,CAAC;AAExE,qBAAa,cAAc;IACzB;;OAEG;WACU,aAAa,CACxB,OAAO,EAAE,OAAO,EAChB,UAAU,CAAC,EAAE,aAAa,EAAE,GAC3B,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAkBlC;;OAEG;WACU,oBAAoB,CAC/B,OAAO,EAAE,OAAO,GACf,OAAO,CAAC,aAAa,CAAC;IAsCzB;;;OAGG;WACU,gBAAgB,CAC3B,OAAO,EAAE,OAAO,GACf,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAclC;;OAEG;IACH,OAAO,CAAC,MAAM,CAAC,aAAa;CAa7B"}