web-corders-vrt 0.1.3 → 0.1.5

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.
@@ -12,7 +12,7 @@ export function printTerminalReport(report: VrtReport): void {
12
12
  console.log("=".repeat(50));
13
13
  console.log("");
14
14
  console.log(
15
- `Comparing: ${chalk.cyan(meta.beforeUrl)} vs ${chalk.cyan(meta.afterUrl)}`,
15
+ `Comparing: ${chalk.cyan(meta.referenceUrl)} vs ${chalk.cyan(meta.afterUrl)}`,
16
16
  );
17
17
  console.log("");
18
18
 
@@ -52,22 +52,6 @@ export function printTerminalReport(report: VrtReport): void {
52
52
  ` ${vpLabel} ${chalk.red("❌ FAIL")} ${chalk.red(diffStr)} ${chalk.dim(thresholdStr)}`,
53
53
  );
54
54
 
55
- // 差分領域の詳細
56
- for (const region of result.diffRegions.slice(0, 5)) {
57
- const { boundingBox: bb, locationHint: lh } = region;
58
- console.log(
59
- chalk.dim(
60
- ` → Region ${region.id}: ${bb.width}x${bb.height} at (${bb.x}, ${bb.y}) - ${lh.estimatedElement}`,
61
- ),
62
- );
63
- }
64
- if (result.diffRegions.length > 5) {
65
- console.log(
66
- chalk.dim(
67
- ` → ...and ${result.diffRegions.length - 5} more regions`,
68
- ),
69
- );
70
- }
71
55
  }
72
56
  }
73
57
 
package/src/schemas.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
2
 
3
3
  export const cliOptionsSchema = z.object({
4
- before: z.string().url("--before must be a valid URL"),
4
+ reference: z.string().url("--reference must be a valid URL"),
5
5
  after: z.string().url("--after must be a valid URL"),
6
6
  paths: z.string().transform((val) => val.split(",").map((p) => p.trim())),
7
7
  threshold: z.coerce.number().min(0).max(100).default(0.1),
@@ -0,0 +1,35 @@
1
+ ---
2
+ name: web-corders-vrt
3
+ description: WEB用VRT(Visual Regression Test)ツール。本番ドメインとローカル開発またはプレビュードメインの間で、特定パスのビジュアル差分を検出する。
4
+ ---
5
+
6
+ ## 使い方
7
+
8
+ ```bash
9
+ npx web-corders-vrt run \
10
+ --reference ${referenceDomain} \
11
+ --after <開発環境のドメイン|ユーザーから指示がない場合はhttp://localhost:3000> \
12
+ --paths <テスト対象のパス(カンマ区切り)> \
13
+ ```
14
+
15
+ ## 手順
16
+
17
+ 1. ユーザーから比較対象のドメインを指定された場合、それに従う。指示がない場合、afterは開発サーバーとし、起動していなければ `npm run dev` で起動して待機する。ここで起動したドメインとプロトコルをafterのドメインとする。
18
+ 2. 上記のコマンドでVRTを実行する(`--paths` はタスクに応じて設定する)
19
+ 3. `./vrt-results/` 内の最新ディレクトリにある `report.json` を読む
20
+ 4. `status: "fail"` のテスト結果に注目する
21
+ 5. 該当するdiff画像(`*--diff.png`)をReadツールで視覚的に確認する
22
+ 6. diff画像から修正すべきCSSやHTMLの場所を特定する
23
+ 7. ソースコードを修正する
24
+ 8. 再度VRTを実行して修正が反映されたことを確認する
25
+
26
+ ## オプション
27
+
28
+ | オプション | 説明 |
29
+ | -------------------- | ------------------------------------------- |
30
+ | `--reference <url>` | リファレンスプロトコル+ドメイン(本番環境) |
31
+ | `--after <url>` | 比較先Uプロトコル+ドメイン(開発環境) |
32
+ | `--paths <paths>` | テスト対象のページパス(カンマ区切り) |
33
+ | `--threshold <n>` | 差分許容率(%)。デフォルト: 0.1 |
34
+ | `--hide <selectors>` | 非表示にするCSSセレクタ(カンマ区切り) |
35
+ | `--no-open` | HTMLレポートをブラウザで開かない |
@@ -13,7 +13,7 @@
13
13
  <h1>web-corders-vrt</h1>
14
14
  <div class="meta">
15
15
  {{TIMESTAMP}} | Duration: {{DURATION}}s<br />
16
- Before: {{BEFORE_URL}} → After: {{AFTER_URL}}
16
+ Reference: {{REFERENCE_URL}} → After: {{AFTER_URL}}
17
17
  </div>
18
18
  </div>
19
19
  <div class="summary">
@@ -6,3 +6,7 @@ declare module "*.css" {
6
6
  const content: string;
7
7
  export default content;
8
8
  }
9
+ declare module "*.md" {
10
+ const content: string;
11
+ export default content;
12
+ }
package/src/types.ts CHANGED
@@ -3,7 +3,7 @@ export interface VrtReport {
3
3
  version: "1.0";
4
4
  meta: {
5
5
  timestamp: string;
6
- beforeUrl: string;
6
+ referenceUrl: string;
7
7
  afterUrl: string;
8
8
  duration: number;
9
9
  command: string;
@@ -24,7 +24,7 @@ export interface VrtTestResult {
24
24
  path: string;
25
25
  name: string;
26
26
  url: {
27
- before: string;
27
+ reference: string;
28
28
  after: string;
29
29
  };
30
30
  };
@@ -42,47 +42,24 @@ export interface VrtTestResult {
42
42
  dimensions: {
43
43
  width: number;
44
44
  height: number;
45
- beforeHeight: number;
45
+ referenceHeight: number;
46
46
  afterHeight: number;
47
47
  };
48
48
  };
49
- diffRegions: VrtDiffRegion[];
50
49
  screenshots: {
51
- before: string;
50
+ reference: string;
52
51
  after: string;
53
52
  diff: string;
54
53
  };
55
54
  error?: string;
56
55
  }
57
56
 
58
- /** 差分が検出された領域 */
59
- export interface VrtDiffRegion {
60
- id: number;
61
- boundingBox: {
62
- x: number;
63
- y: number;
64
- width: number;
65
- height: number;
66
- };
67
- diffPixelCount: number;
68
- diffPercentageInRegion: number;
69
- locationHint: {
70
- verticalPosition: VerticalPosition;
71
- horizontalPosition: HorizontalPosition;
72
- fromTopPx: number;
73
- fromLeftPx: number;
74
- estimatedElement: string;
75
- };
76
- }
77
-
78
57
  export type ViewportType = "sp" | "pc";
79
58
  export type TestStatus = "pass" | "fail" | "error";
80
- export type VerticalPosition = "top" | "upper" | "middle" | "lower" | "bottom";
81
- export type HorizontalPosition = "left" | "center" | "right" | "full-width";
82
59
 
83
60
  /** CLI実行時の解決済みオプション */
84
61
  export interface ResolvedOptions {
85
- beforeUrl: string;
62
+ referenceUrl: string;
86
63
  afterUrl: string;
87
64
  paths: string[];
88
65
  threshold: number;
@@ -111,7 +88,7 @@ export interface ComparisonResult {
111
88
  dimensions: {
112
89
  width: number;
113
90
  height: number;
114
- beforeHeight: number;
91
+ referenceHeight: number;
115
92
  afterHeight: number;
116
93
  };
117
94
  }
@@ -85,7 +85,7 @@ describe("compareImages", () => {
85
85
  });
86
86
 
87
87
  it("一部だけ異なる画像の場合、差分率が正しく計算される", () => {
88
- const before = createSolidPng(100, 100, 255, 255, 255);
88
+ const reference = createSolidPng(100, 100, 255, 255, 255);
89
89
  // 右下10x10だけ赤にする
90
90
  const after = createPngWithRegion(100, 100, 255, 255, 255, {
91
91
  x: 90,
@@ -97,7 +97,7 @@ describe("compareImages", () => {
97
97
  b: 0,
98
98
  });
99
99
 
100
- const result = compareImages(before, after);
100
+ const result = compareImages(reference, after);
101
101
 
102
102
  expect(result.diffCount).toBeGreaterThan(0);
103
103
  // 10*10=100 / 100*100=10000 = 1%
@@ -112,14 +112,14 @@ describe("compareImages", () => {
112
112
 
113
113
  expect(result.dimensions.width).toBe(100);
114
114
  expect(result.dimensions.height).toBe(100);
115
- expect(result.dimensions.beforeHeight).toBe(50);
115
+ expect(result.dimensions.referenceHeight).toBe(50);
116
116
  expect(result.dimensions.afterHeight).toBe(100);
117
117
  // 白でパディングされるので一部は同じ、拡張部分も白=白で差分なし
118
118
  expect(result.diffCount).toBe(0);
119
119
  });
120
120
 
121
121
  it("threshold以下の差分はpassになる", () => {
122
- const before = createSolidPng(100, 100, 255, 255, 255);
122
+ const reference = createSolidPng(100, 100, 255, 255, 255);
123
123
  // 1ピクセルだけ微妙に違う
124
124
  const afterPng = new PNG({ width: 100, height: 100 });
125
125
  for (let i = 0; i < afterPng.data.length; i += 4) {
@@ -132,7 +132,7 @@ describe("compareImages", () => {
132
132
  afterPng.data[0] = 200;
133
133
  const after = PNG.sync.write(afterPng);
134
134
 
135
- const result = compareImages(before, after, 1.0); // threshold 1%
135
+ const result = compareImages(reference, after, 1.0); // threshold 1%
136
136
  // 1px / 10000px = 0.01% < 1%
137
137
  expect(result.passed).toBe(true);
138
138
  });
package/tsup.config.ts CHANGED
@@ -12,6 +12,7 @@ export default defineConfig({
12
12
  loader: {
13
13
  ".html": "text",
14
14
  ".css": "text",
15
+ ".md": "text",
15
16
  },
16
17
  banner: {
17
18
  js: "#!/usr/bin/env node",
@@ -1,277 +0,0 @@
1
- import { PNG } from "pngjs";
2
- import type {
3
- VrtDiffRegion,
4
- VerticalPosition,
5
- HorizontalPosition,
6
- } from "../types.js";
7
-
8
- export interface RegionDetectorOptions {
9
- /** この面積(px)未満の領域は無視する */
10
- minRegionSize?: number;
11
- /** この距離(px)以下の差分ピクセルを同一領域としてマージする */
12
- mergingDistance?: number;
13
- }
14
-
15
- /**
16
- * diff画像から差分領域を検出し、座標・位置ヒントを返す。
17
- * Connected Component Labeling + bounding box 方式。
18
- */
19
- export function detectDiffRegions(
20
- diffImageBuffer: Buffer,
21
- options: RegionDetectorOptions = {},
22
- ): VrtDiffRegion[] {
23
- const { minRegionSize = 10, mergingDistance = 50 } = options;
24
-
25
- const png = PNG.sync.read(diffImageBuffer);
26
- const { width, height, data } = png;
27
-
28
- // 1. diff ピクセル(赤色)の座標を収集
29
- const diffPixels: Array<{ x: number; y: number }> = [];
30
- for (let y = 0; y < height; y++) {
31
- for (let x = 0; x < width; x++) {
32
- const idx = (y * width + x) * 4;
33
- const r = data[idx];
34
- const g = data[idx + 1];
35
- const b = data[idx + 2];
36
- const a = data[idx + 3];
37
-
38
- // pixelmatch の diffColor [255, 0, 0] または diffColorAlt [0, 200, 0] を検出
39
- if (
40
- a > 100 &&
41
- ((r > 200 && g < 100 && b < 100) || (r < 100 && g > 150 && b < 100))
42
- ) {
43
- diffPixels.push({ x, y });
44
- }
45
- }
46
- }
47
-
48
- if (diffPixels.length === 0) {
49
- return [];
50
- }
51
-
52
- // 2. グリッドベースのクラスタリング(高速化のため)
53
- const clusters = clusterPixels(diffPixels, mergingDistance);
54
-
55
- // 3. 各クラスタの bounding box を計算し、フィルタリング
56
- const regions: VrtDiffRegion[] = clusters
57
- .map((cluster, idx) => {
58
- let minX = Infinity;
59
- let maxX = -Infinity;
60
- let minY = Infinity;
61
- let maxY = -Infinity;
62
- for (const p of cluster) {
63
- if (p.x < minX) minX = p.x;
64
- if (p.x > maxX) maxX = p.x;
65
- if (p.y < minY) minY = p.y;
66
- if (p.y > maxY) maxY = p.y;
67
- }
68
- const regionWidth = maxX - minX + 1;
69
- const regionHeight = maxY - minY + 1;
70
-
71
- return {
72
- id: idx + 1,
73
- boundingBox: {
74
- x: minX,
75
- y: minY,
76
- width: regionWidth,
77
- height: regionHeight,
78
- },
79
- diffPixelCount: cluster.length,
80
- diffPercentageInRegion:
81
- (cluster.length / (regionWidth * regionHeight)) * 100,
82
- locationHint: estimatePosition(
83
- minX,
84
- minY,
85
- regionWidth,
86
- regionHeight,
87
- width,
88
- height,
89
- ),
90
- };
91
- })
92
- .filter((r) => r.diffPixelCount >= minRegionSize)
93
- .sort((a, b) => b.diffPixelCount - a.diffPixelCount);
94
-
95
- // IDを振り直す
96
- return regions.map((r, i) => ({ ...r, id: i + 1 }));
97
- }
98
-
99
- /**
100
- * ピクセルをグリッドベースでクラスタリングする。
101
- * mergingDistance をセルサイズとしてグリッドに分割し、
102
- * 隣接セルのピクセルを同一クラスタとしてマージする。
103
- */
104
- function clusterPixels(
105
- pixels: Array<{ x: number; y: number }>,
106
- distance: number,
107
- ): Array<Array<{ x: number; y: number }>> {
108
- const cellSize = Math.max(distance, 1);
109
- const grid = new Map<string, Array<{ x: number; y: number }>>();
110
-
111
- // グリッドに割り当て
112
- for (const p of pixels) {
113
- const cellX = Math.floor(p.x / cellSize);
114
- const cellY = Math.floor(p.y / cellSize);
115
- const key = `${cellX},${cellY}`;
116
- if (!grid.has(key)) {
117
- grid.set(key, []);
118
- }
119
- grid.get(key)!.push(p);
120
- }
121
-
122
- // Union-Find でセルをクラスタリング
123
- const cellKeys = Array.from(grid.keys());
124
- const parent = new Map<string, string>();
125
-
126
- function find(key: string): string {
127
- if (!parent.has(key)) parent.set(key, key);
128
- // 反復的にルートを探索
129
- let root = key;
130
- while (parent.get(root) !== root) {
131
- root = parent.get(root)!;
132
- }
133
- // パス圧縮
134
- let current = key;
135
- while (current !== root) {
136
- const next = parent.get(current)!;
137
- parent.set(current, root);
138
- current = next;
139
- }
140
- return root;
141
- }
142
-
143
- function union(a: string, b: string): void {
144
- const ra = find(a);
145
- const rb = find(b);
146
- if (ra !== rb) parent.set(ra, rb);
147
- }
148
-
149
- // 隣接セルをマージ
150
- for (const key of cellKeys) {
151
- const [cx, cy] = key.split(",").map(Number);
152
- for (let dx = -1; dx <= 1; dx++) {
153
- for (let dy = -1; dy <= 1; dy++) {
154
- if (dx === 0 && dy === 0) continue;
155
- const neighborKey = `${cx + dx},${cy + dy}`;
156
- if (grid.has(neighborKey)) {
157
- union(key, neighborKey);
158
- }
159
- }
160
- }
161
- }
162
-
163
- // クラスタごとにピクセルを集約
164
- const clusters = new Map<string, Array<{ x: number; y: number }>>();
165
- for (const key of cellKeys) {
166
- const root = find(key);
167
- if (!clusters.has(root)) {
168
- clusters.set(root, []);
169
- }
170
- clusters.get(root)!.push(...grid.get(key)!);
171
- }
172
-
173
- return Array.from(clusters.values());
174
- }
175
-
176
- /**
177
- * 差分領域の位置から、CSSの修正箇所を推定するヒントを生成する。
178
- */
179
- function estimatePosition(
180
- x: number,
181
- y: number,
182
- regionWidth: number,
183
- regionHeight: number,
184
- pageWidth: number,
185
- pageHeight: number,
186
- ): VrtDiffRegion["locationHint"] {
187
- // 垂直位置の推定
188
- const centerY = y + regionHeight / 2;
189
- const yRatio = centerY / pageHeight;
190
- let verticalPosition: VerticalPosition;
191
- if (yRatio < 0.1) verticalPosition = "top";
192
- else if (yRatio < 0.3) verticalPosition = "upper";
193
- else if (yRatio < 0.7) verticalPosition = "middle";
194
- else if (yRatio < 0.9) verticalPosition = "lower";
195
- else verticalPosition = "bottom";
196
-
197
- // 水平位置の推定
198
- const centerX = x + regionWidth / 2;
199
- const widthRatio = regionWidth / pageWidth;
200
- let horizontalPosition: HorizontalPosition;
201
- if (widthRatio > 0.8) {
202
- horizontalPosition = "full-width";
203
- } else if (centerX < pageWidth * 0.33) {
204
- horizontalPosition = "left";
205
- } else if (centerX > pageWidth * 0.67) {
206
- horizontalPosition = "right";
207
- } else {
208
- horizontalPosition = "center";
209
- }
210
-
211
- // 要素の推定
212
- const estimatedElement = guessElement(
213
- verticalPosition,
214
- horizontalPosition,
215
- regionWidth,
216
- regionHeight,
217
- pageWidth,
218
- y,
219
- );
220
-
221
- return {
222
- verticalPosition,
223
- horizontalPosition,
224
- fromTopPx: y,
225
- fromLeftPx: x,
226
- estimatedElement,
227
- };
228
- }
229
-
230
- /**
231
- * 位置とサイズから、可能性のあるUI要素を推測する。
232
- */
233
- function guessElement(
234
- vPos: VerticalPosition,
235
- hPos: HorizontalPosition,
236
- width: number,
237
- height: number,
238
- pageWidth: number,
239
- fromTop: number,
240
- ): string {
241
- // ページ最上部の全幅要素 → ヘッダー/ナビゲーション
242
- if (vPos === "top" && hPos === "full-width" && fromTop < 100) {
243
- return "Likely a header or navigation bar";
244
- }
245
-
246
- // ページ最下部の全幅要素 → フッター
247
- if (vPos === "bottom" && hPos === "full-width") {
248
- return "Likely a footer";
249
- }
250
-
251
- // 上部の大きな要素 → ヒーローセクション
252
- if (vPos === "upper" && hPos === "full-width" && height > 200) {
253
- return "Likely a hero section or banner";
254
- }
255
-
256
- // 小さなボタンサイズ
257
- if (width < 200 && height < 60) {
258
- return "Likely a button or small UI element";
259
- }
260
-
261
- // 横長の細い要素 → テキスト行
262
- if (width > pageWidth * 0.5 && height < 40) {
263
- return "Likely a text line or heading";
264
- }
265
-
266
- // カードサイズ
267
- if (width > 200 && width < 500 && height > 100 && height < 400) {
268
- return "Likely a card or content block";
269
- }
270
-
271
- // 画像サイズ
272
- if (width > 100 && height > 100 && width < pageWidth * 0.8) {
273
- return "Likely an image or media element";
274
- }
275
-
276
- return `UI element at ~${fromTop}px from top`;
277
- }
@@ -1,147 +0,0 @@
1
- import { describe, it, expect } from "vitest";
2
- import { PNG } from "pngjs";
3
- import { detectDiffRegions } from "../src/core/region-detector.js";
4
-
5
- /**
6
- * pixelmatchが出力するような diff画像を手動で生成する。
7
- * 指定領域を赤 (255, 0, 0) にし、残りを半透明にする。
8
- */
9
- function createDiffImage(
10
- width: number,
11
- height: number,
12
- regions: Array<{ x: number; y: number; w: number; h: number }>,
13
- ): Buffer {
14
- const png = new PNG({ width, height });
15
-
16
- // 背景を半透明に
17
- for (let i = 0; i < png.data.length; i += 4) {
18
- png.data[i] = 0;
19
- png.data[i + 1] = 0;
20
- png.data[i + 2] = 0;
21
- png.data[i + 3] = 25; // alpha 0.1 * 255 ≈ 25
22
- }
23
-
24
- // 差分領域を赤にする
25
- for (const region of regions) {
26
- for (let y = region.y; y < region.y + region.h && y < height; y++) {
27
- for (let x = region.x; x < region.x + region.w && x < width; x++) {
28
- const idx = (y * width + x) * 4;
29
- png.data[idx] = 255; // R
30
- png.data[idx + 1] = 0; // G
31
- png.data[idx + 2] = 0; // B
32
- png.data[idx + 3] = 255; // A
33
- }
34
- }
35
- }
36
-
37
- return PNG.sync.write(png);
38
- }
39
-
40
- describe("detectDiffRegions", () => {
41
- it("差分がない画像からは領域を検出しない", () => {
42
- const png = new PNG({ width: 100, height: 100 });
43
- for (let i = 0; i < png.data.length; i += 4) {
44
- png.data[i] = 0;
45
- png.data[i + 1] = 0;
46
- png.data[i + 2] = 0;
47
- png.data[i + 3] = 25;
48
- }
49
- const buffer = PNG.sync.write(png);
50
-
51
- const regions = detectDiffRegions(buffer);
52
- expect(regions).toHaveLength(0);
53
- });
54
-
55
- it("単一の差分領域を正しく検出する", () => {
56
- const diffImage = createDiffImage(200, 200, [
57
- { x: 50, y: 50, w: 40, h: 30 },
58
- ]);
59
-
60
- const regions = detectDiffRegions(diffImage, { minRegionSize: 1 });
61
-
62
- expect(regions.length).toBeGreaterThanOrEqual(1);
63
- const region = regions[0];
64
-
65
- // bounding box が概ね正しい
66
- expect(region.boundingBox.x).toBeGreaterThanOrEqual(50);
67
- expect(region.boundingBox.y).toBeGreaterThanOrEqual(50);
68
- expect(region.boundingBox.x + region.boundingBox.width).toBeLessThanOrEqual(
69
- 100,
70
- );
71
- expect(
72
- region.boundingBox.y + region.boundingBox.height,
73
- ).toBeLessThanOrEqual(90);
74
- });
75
-
76
- it("離れた2つの差分領域を別々に検出する", () => {
77
- const diffImage = createDiffImage(400, 400, [
78
- { x: 10, y: 10, w: 30, h: 30 },
79
- { x: 350, y: 350, w: 30, h: 30 },
80
- ]);
81
-
82
- const regions = detectDiffRegions(diffImage, {
83
- minRegionSize: 1,
84
- mergingDistance: 20, // 離れてるのでマージされない
85
- });
86
-
87
- expect(regions.length).toBe(2);
88
- });
89
-
90
- it("近い差分領域はマージされる", () => {
91
- const diffImage = createDiffImage(200, 200, [
92
- { x: 10, y: 10, w: 20, h: 20 },
93
- { x: 40, y: 10, w: 20, h: 20 }, // 10px しか離れてない
94
- ]);
95
-
96
- const regions = detectDiffRegions(diffImage, {
97
- minRegionSize: 1,
98
- mergingDistance: 50, // 50px 以下はマージ
99
- });
100
-
101
- expect(regions.length).toBe(1);
102
- });
103
-
104
- it("locationHint が正しく設定される", () => {
105
- // ページ上部にフル幅の差分
106
- const diffImage = createDiffImage(1440, 900, [
107
- { x: 0, y: 10, w: 1440, h: 60 },
108
- ]);
109
-
110
- const regions = detectDiffRegions(diffImage, { minRegionSize: 1 });
111
-
112
- expect(regions.length).toBeGreaterThanOrEqual(1);
113
- const hint = regions[0].locationHint;
114
- expect(hint.verticalPosition).toBe("top");
115
- expect(hint.horizontalPosition).toBe("full-width");
116
- expect(hint.estimatedElement).toContain("header");
117
- });
118
-
119
- it("小さすぎる差分はminRegionSizeでフィルタされる", () => {
120
- const diffImage = createDiffImage(200, 200, [
121
- { x: 50, y: 50, w: 2, h: 2 }, // 4ピクセルしかない
122
- ]);
123
-
124
- const regions = detectDiffRegions(diffImage, { minRegionSize: 10 });
125
- expect(regions).toHaveLength(0);
126
- });
127
-
128
- it("IDが大きい差分順に振られる", () => {
129
- const diffImage = createDiffImage(400, 400, [
130
- { x: 10, y: 10, w: 10, h: 10 }, // 100px
131
- { x: 300, y: 300, w: 50, h: 50 }, // 2500px
132
- ]);
133
-
134
- const regions = detectDiffRegions(diffImage, {
135
- minRegionSize: 1,
136
- mergingDistance: 20,
137
- });
138
-
139
- expect(regions.length).toBe(2);
140
- // 大きい差分が先(id=1)
141
- expect(regions[0].diffPixelCount).toBeGreaterThan(
142
- regions[1].diffPixelCount,
143
- );
144
- expect(regions[0].id).toBe(1);
145
- expect(regions[1].id).toBe(2);
146
- });
147
- });