@bughunters/vision 1.0.5 → 1.0.6

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.
@@ -1 +1 @@
1
- {"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../src/check.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,OAAO,EAAY,MAAM,kBAAkB,CAAC;AAOhE,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,IAAI,GAAG,OAAO,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAKD,wBAAgB,uBAAuB,IAAI,MAAM,GAAG,IAAI,CAEvD;AAoHD,wBAAsB,cAAc,CAAC,IAAI,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAyJ/E"}
1
+ {"version":3,"file":"check.d.ts","sourceRoot":"","sources":["../src/check.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,IAAI,EAAE,OAAO,EAAY,MAAM,kBAAkB,CAAC;AAOhE,MAAM,WAAW,qBAAqB;IACpC,MAAM,EAAE,IAAI,GAAG,OAAO,CAAC;IACvB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAKD,wBAAgB,uBAAuB,IAAI,MAAM,GAAG,IAAI,CAEvD;AAoHD,wBAAsB,cAAc,CAAC,IAAI,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC,CAqK/E"}
package/dist/check.js CHANGED
@@ -161,11 +161,17 @@ async function runVisionCheck(opts) {
161
161
  }
162
162
  const resolvedDir = path.resolve(process.cwd(), snapshotsDir);
163
163
  fs.mkdirSync(resolvedDir, { recursive: true });
164
- // Build unique, collision-free filenames scoped to both the test title and the step name.
165
- // Format: <safe-test-title>--<safe-step-name>.baseline.png
164
+ // Build unique, collision-free filenames scoped to project + test title + step name.
165
+ // Format: [<safe-project>--]<safe-test-title>--<safe-step-name>.baseline.png
166
+ // Project prefix is required when multiple Playwright projects run the same test
167
+ // (e.g. widget-chromium, widget-firefox) — without it all projects share one file.
168
+ const projectName = testInfo?.project?.name || undefined;
169
+ const safeProject = projectName ? sanitize(projectName) : '';
166
170
  const safeTitle = testInfo?.title ? sanitize(testInfo.title) : 'unknown';
167
171
  const safeName = sanitize(name);
168
- const filePrefix = `${safeTitle}--${safeName}`;
172
+ const filePrefix = safeProject
173
+ ? `${safeProject}--${safeTitle}--${safeName}`
174
+ : `${safeTitle}--${safeName}`;
169
175
  // Human-readable label used in the HTML report: "Test title › step name"
170
176
  const testName = testInfo?.title ? `${testInfo.title} › ${name}` : name;
171
177
  const baselinePath = path.join(resolvedDir, `${filePrefix}.baseline.png`);
@@ -176,6 +182,7 @@ async function runVisionCheck(opts) {
176
182
  console.log(`📸 [BugHunters Vision] Baseline created: ${name}`);
177
183
  (0, results_1.appendResult)(resolvedDir, {
178
184
  testName,
185
+ projectName,
179
186
  status: 'BASELINE_CREATED',
180
187
  reason: 'First run detected — screenshot saved as the new baseline.',
181
188
  method: null,
@@ -196,6 +203,7 @@ async function runVisionCheck(opts) {
196
203
  console.log(`✅ [BugHunters Vision] FAST PIXEL MATCH: ${name}`);
197
204
  (0, results_1.appendResult)(resolvedDir, {
198
205
  testName,
206
+ projectName,
199
207
  status: 'PASS',
200
208
  reason: 'Identical (Fast Pixel Match) — pixel-perfect match, no AI evaluation needed.',
201
209
  method: 'FAST_PIXEL_MATCH',
@@ -212,6 +220,7 @@ async function runVisionCheck(opts) {
212
220
  // strict: fail immediately without calling AI
213
221
  (0, results_1.appendResult)(resolvedDir, {
214
222
  testName,
223
+ projectName,
215
224
  status: 'FAIL',
216
225
  reason: `Pixel mismatch detected (${diffLabel}). Strict mode — AI evaluation disabled.`,
217
226
  method: 'FAST_PIXEL_MATCH',
@@ -233,6 +242,7 @@ async function runVisionCheck(opts) {
233
242
  const reason = humanReadableApiError(err instanceof Error ? err.message : String(err));
234
243
  (0, results_1.appendResult)(resolvedDir, {
235
244
  testName,
245
+ projectName,
236
246
  status: 'FAIL',
237
247
  reason,
238
248
  method: 'AI',
@@ -248,6 +258,7 @@ async function runVisionCheck(opts) {
248
258
  console.log(`⚠️ [BugHunters Vision] AI error: ${name} — ${result.reason}`);
249
259
  (0, results_1.appendResult)(resolvedDir, {
250
260
  testName,
261
+ projectName,
251
262
  status: 'ERROR',
252
263
  reason: result.reason,
253
264
  method: 'AI',
@@ -260,6 +271,7 @@ async function runVisionCheck(opts) {
260
271
  }
261
272
  (0, results_1.appendResult)(resolvedDir, {
262
273
  testName,
274
+ projectName,
263
275
  status: result.status,
264
276
  reason: result.reason,
265
277
  method: 'AI',
@@ -1 +1 @@
1
- {"version":3,"file":"reporter.d.ts","sourceRoot":"","sources":["../src/reporter.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAI1D,MAAM,WAAW,+BAA+B;IAC9C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA4hCD,cAAM,wBAAyB,YAAW,QAAQ;IAChD,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,SAAS,CAAS;gBAEd,OAAO,CAAC,EAAE,+BAA+B;IAKrD,aAAa,IAAI,OAAO;IAIxB,OAAO,IAAI,IAAI;IAKf,KAAK,IAAI,IAAI;CAkDd;AAED,eAAe,wBAAwB,CAAC;AACxC,OAAO,EAAE,wBAAwB,EAAE,CAAC"}
1
+ {"version":3,"file":"reporter.d.ts","sourceRoot":"","sources":["../src/reporter.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,2BAA2B,CAAC;AAI1D,MAAM,WAAW,+BAA+B;IAC9C,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AA4iCD,cAAM,wBAAyB,YAAW,QAAQ;IAChD,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,SAAS,CAAS;gBAEd,OAAO,CAAC,EAAE,+BAA+B;IAKrD,aAAa,IAAI,OAAO;IAIxB,OAAO,IAAI,IAAI;IAKf,KAAK,IAAI,IAAI;CAkDd;AAED,eAAe,wBAAwB,CAAC;AACxC,OAAO,EAAE,wBAAwB,EAAE,CAAC"}
package/dist/reporter.js CHANGED
@@ -37,27 +37,37 @@ exports.BugHuntersVisionReporter = void 0;
37
37
  const fs = __importStar(require("fs"));
38
38
  const path = __importStar(require("path"));
39
39
  const check_1 = require("./check");
40
- // ─── Group flat step results by Playwright test title ─────────────────────────
40
+ // ─── Group flat step results by Playwright project + test title ───────────────
41
+ // The grouping key is "projectName||testTitle" so that the same test running
42
+ // on different browser projects (chromium, firefox, android…) produces separate
43
+ // groups — each with its own baseline files and its own row in the report.
41
44
  function groupResults(results) {
42
45
  const order = [];
43
46
  const map = new Map();
44
47
  for (const r of results) {
45
48
  const sep = r.testName.indexOf(' › ');
46
- const title = sep >= 0 ? r.testName.slice(0, sep) : r.testName;
47
- if (!map.has(title)) {
48
- map.set(title, []);
49
- order.push(title);
49
+ const testTitle = sep >= 0 ? r.testName.slice(0, sep) : r.testName;
50
+ // Composite key keeps project-variants separate while remaining stable
51
+ const key = r.projectName ? `${r.projectName}||${testTitle}` : testTitle;
52
+ if (!map.has(key)) {
53
+ map.set(key, []);
54
+ order.push(key);
50
55
  }
51
- map.get(title).push(r);
56
+ map.get(key).push(r);
52
57
  }
53
- return order.map(testTitle => {
54
- const steps = map.get(testTitle);
58
+ return order.map(key => {
59
+ const steps = map.get(key);
60
+ const firstResult = steps[0];
61
+ const sep = firstResult.testName.indexOf(' › ');
62
+ const testTitle = sep >= 0 ? firstResult.testName.slice(0, sep) : firstResult.testName;
63
+ const projectName = firstResult.projectName;
55
64
  const hasFail = steps.some(s => s.status === 'FAIL');
56
65
  const hasError = steps.some(s => s.status === 'ERROR');
57
66
  const allBase = steps.every(s => s.status === 'BASELINE_CREATED');
58
67
  const status = hasFail ? 'FAIL' : hasError ? 'ERROR' : allBase ? 'BASELINE_CREATED' : 'PASS';
59
68
  return {
60
69
  testTitle,
70
+ projectName,
61
71
  status,
62
72
  passedSteps: steps.filter(s => s.status === 'PASS').length,
63
73
  failedSteps: steps.filter(s => s.status === 'FAIL').length,
@@ -270,6 +280,7 @@ body {
270
280
  letter-spacing: .4px; font-family: ui-monospace, monospace; white-space: nowrap;
271
281
  }
272
282
  .badge.m-method { background: var(--surface-2); color: var(--text-3); border: 1px solid var(--border); }
283
+ .badge.m-proj { background: var(--surface-2); color: var(--text-2); border: 1px solid var(--border); font-family: inherit; letter-spacing: 0; }
273
284
  .badge.m-ai { background: var(--accent-soft); color: var(--accent); border: 1px solid var(--accent-border); }
274
285
  .badge.m-warn { background: var(--pixel-soft); color: var(--pixel); border: 1px solid var(--pixel-border); }
275
286
  .badge.m-pixel { background: var(--pixel-soft); color: var(--pixel); border: 1px solid var(--pixel-border); }
@@ -845,7 +856,10 @@ function getJs() {
845
856
  var snip = n + ' ' + checkWord
846
857
  + (parts.length > 0 ? ' \u00b7 ' + parts.join(' \u00b7 ') : ' \u00b7 all passed');
847
858
 
848
- var stepsHtml = g.steps.map(buildStep).join('');
859
+ var stepsHtml = g.steps.map(buildStep).join('');
860
+ var projBadge = g.projectName
861
+ ? '<span class="badge m-proj">' + esc(g.projectName) + '</span>'
862
+ : '';
849
863
 
850
864
  return '<div class="test-group ' + s.cls + '" data-gst="' + g.status + '">'
851
865
  + '<div class="group-row" data-g="' + gIdx + '">'
@@ -855,6 +869,7 @@ function getJs() {
855
869
  + '<div class="test-snip">' + esc(snip) + '</div>'
856
870
  + '</div>'
857
871
  + '<div class="badges">'
872
+ + projBadge
858
873
  + '<span class="badge ' + s.badge + '">' + s.label + '</span>'
859
874
  + '</div>'
860
875
  + '<span class="chev" id="gc' + gIdx + '">\u203a</span>'
package/dist/types.d.ts CHANGED
@@ -2,6 +2,7 @@ export type VisionMode = 'ai' | 'strict' | 'off';
2
2
  export interface TestResult {
3
3
  index: number;
4
4
  testName: string;
5
+ projectName?: string;
5
6
  status: 'PASS' | 'FAIL' | 'BASELINE_CREATED' | 'ERROR';
6
7
  reason: string;
7
8
  method: 'AI' | 'FAST_PIXEL_MATCH' | null;
@@ -11,6 +12,7 @@ export interface TestResult {
11
12
  }
12
13
  export interface TestGroup {
13
14
  testTitle: string;
15
+ projectName?: string;
14
16
  status: 'PASS' | 'FAIL' | 'BASELINE_CREATED' | 'ERROR';
15
17
  passedSteps: number;
16
18
  failedSteps: number;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG,IAAI,GAAG,QAAQ,GAAG,KAAK,CAAC;AAEjD,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,kBAAkB,GAAG,OAAO,CAAC;IACvD,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,IAAI,GAAG,kBAAkB,GAAG,IAAI,CAAC;IACzC,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;CACnB;AAGD,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,kBAAkB,GAAG,OAAO,CAAC;IACvD,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,UAAU,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,IAAI;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,UAAU,GAAG,IAAI,GAAG,QAAQ,GAAG,KAAK,CAAC;AAEjD,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,kBAAkB,GAAG,OAAO,CAAC;IACvD,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,IAAI,GAAG,kBAAkB,GAAG,IAAI,CAAC;IACzC,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,MAAM,CAAC;CACnB;AAGD,MAAM,WAAW,SAAS;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,kBAAkB,GAAG,OAAO,CAAC;IACvD,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,KAAK,EAAE,UAAU,EAAE,CAAC;CACrB;AAED,MAAM,WAAW,IAAI;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;CACjB"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@bughunters/vision",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "description": "InfoSec-friendly AI Visual Testing plugin for Playwright.",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
@@ -24,7 +24,7 @@
24
24
  "reporter.d.ts",
25
25
  "README.md"
26
26
  ],
27
- "homepage": "https://bughunters.vision",
27
+ "homepage": "https://bughunters.dev",
28
28
  "repository": {
29
29
  "type": "git",
30
30
  "url": "git+https://github.com/bughunters/vision.git"