@testivai/witness-playwright 1.1.5 → 1.2.1

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.
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Helpers for the ignoreSelectors feature.
3
+ *
4
+ * Three sources, merged with deduplication (last-write-wins dedup via Set):
5
+ * 1. .testivai/config.json — global OSS config (`ignoreSelectors` array)
6
+ * 2. testivai.config.ts/js — power-user project config (`ignoreSelectors`)
7
+ * 3. testivai.witness() call — per-snapshot override (`ignoreSelectors`)
8
+ *
9
+ * These functions are pure (no Playwright dependency) so they can be
10
+ * exercised in plain Jest unit tests without a browser.
11
+ */
12
+ import { TestivAIConfig, TestivAIProjectConfig } from '../types';
13
+ /**
14
+ * Read ignoreSelectors from `.testivai/config.json`.
15
+ * Returns `[]` when the file doesn't exist or is malformed.
16
+ *
17
+ * @param projectRoot Directory that contains the `.testivai/` folder
18
+ */
19
+ export declare function readWitnessConfigSelectors(projectRoot: string): string[];
20
+ /**
21
+ * Collect and deduplicate ignoreSelectors from all three sources.
22
+ *
23
+ * Order of precedence for deduplication: earlier sources win (Set preserves
24
+ * insertion order, so the first occurrence of any selector survives).
25
+ *
26
+ * @param projectRoot Path to the project root (where `.testivai/` lives)
27
+ * @param projectConfig Loaded `testivai.config.ts` project config
28
+ * @param effectiveConfig Merged per-snapshot TestivAIConfig
29
+ */
30
+ export declare function collectIgnoreSelectors(projectRoot: string, projectConfig: TestivAIProjectConfig, effectiveConfig: TestivAIConfig): string[];
31
+ /**
32
+ * Build a `<style>` tag content that hides every given selector via
33
+ * `visibility: hidden !important`.
34
+ *
35
+ * Returns an empty string when the selectors list is empty so callers can
36
+ * guard with `if (css)` before calling `page.addStyleTag`.
37
+ */
38
+ export declare function buildIgnoreSelectorsCSS(selectors: string[]): string;
@@ -0,0 +1,97 @@
1
+ "use strict";
2
+ /**
3
+ * Helpers for the ignoreSelectors feature.
4
+ *
5
+ * Three sources, merged with deduplication (last-write-wins dedup via Set):
6
+ * 1. .testivai/config.json — global OSS config (`ignoreSelectors` array)
7
+ * 2. testivai.config.ts/js — power-user project config (`ignoreSelectors`)
8
+ * 3. testivai.witness() call — per-snapshot override (`ignoreSelectors`)
9
+ *
10
+ * These functions are pure (no Playwright dependency) so they can be
11
+ * exercised in plain Jest unit tests without a browser.
12
+ */
13
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ var desc = Object.getOwnPropertyDescriptor(m, k);
16
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
17
+ desc = { enumerable: true, get: function() { return m[k]; } };
18
+ }
19
+ Object.defineProperty(o, k2, desc);
20
+ }) : (function(o, m, k, k2) {
21
+ if (k2 === undefined) k2 = k;
22
+ o[k2] = m[k];
23
+ }));
24
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
25
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
26
+ }) : function(o, v) {
27
+ o["default"] = v;
28
+ });
29
+ var __importStar = (this && this.__importStar) || (function () {
30
+ var ownKeys = function(o) {
31
+ ownKeys = Object.getOwnPropertyNames || function (o) {
32
+ var ar = [];
33
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
34
+ return ar;
35
+ };
36
+ return ownKeys(o);
37
+ };
38
+ return function (mod) {
39
+ if (mod && mod.__esModule) return mod;
40
+ var result = {};
41
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
42
+ __setModuleDefault(result, mod);
43
+ return result;
44
+ };
45
+ })();
46
+ Object.defineProperty(exports, "__esModule", { value: true });
47
+ exports.readWitnessConfigSelectors = readWitnessConfigSelectors;
48
+ exports.collectIgnoreSelectors = collectIgnoreSelectors;
49
+ exports.buildIgnoreSelectorsCSS = buildIgnoreSelectorsCSS;
50
+ const fs = __importStar(require("fs"));
51
+ const path = __importStar(require("path"));
52
+ /**
53
+ * Read ignoreSelectors from `.testivai/config.json`.
54
+ * Returns `[]` when the file doesn't exist or is malformed.
55
+ *
56
+ * @param projectRoot Directory that contains the `.testivai/` folder
57
+ */
58
+ function readWitnessConfigSelectors(projectRoot) {
59
+ try {
60
+ const configPath = path.join(projectRoot, '.testivai', 'config.json');
61
+ if (!fs.existsSync(configPath))
62
+ return [];
63
+ const raw = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
64
+ return Array.isArray(raw.ignoreSelectors) ? raw.ignoreSelectors : [];
65
+ }
66
+ catch {
67
+ return [];
68
+ }
69
+ }
70
+ /**
71
+ * Collect and deduplicate ignoreSelectors from all three sources.
72
+ *
73
+ * Order of precedence for deduplication: earlier sources win (Set preserves
74
+ * insertion order, so the first occurrence of any selector survives).
75
+ *
76
+ * @param projectRoot Path to the project root (where `.testivai/` lives)
77
+ * @param projectConfig Loaded `testivai.config.ts` project config
78
+ * @param effectiveConfig Merged per-snapshot TestivAIConfig
79
+ */
80
+ function collectIgnoreSelectors(projectRoot, projectConfig, effectiveConfig) {
81
+ const fromWitnessConfig = readWitnessConfigSelectors(projectRoot);
82
+ const fromProjectConfig = projectConfig.ignoreSelectors ?? [];
83
+ const fromSnapshot = effectiveConfig.ignoreSelectors ?? [];
84
+ return [...new Set([...fromWitnessConfig, ...fromProjectConfig, ...fromSnapshot])];
85
+ }
86
+ /**
87
+ * Build a `<style>` tag content that hides every given selector via
88
+ * `visibility: hidden !important`.
89
+ *
90
+ * Returns an empty string when the selectors list is empty so callers can
91
+ * guard with `if (css)` before calling `page.addStyleTag`.
92
+ */
93
+ function buildIgnoreSelectorsCSS(selectors) {
94
+ if (selectors.length === 0)
95
+ return '';
96
+ return selectors.map(s => `${s} { visibility: hidden !important; }`).join('\n');
97
+ }
package/dist/snapshot.js CHANGED
@@ -42,6 +42,7 @@ const path = __importStar(require("path"));
42
42
  const url_1 = require("url");
43
43
  const sharp_1 = __importDefault(require("sharp"));
44
44
  const loader_1 = require("./config/loader");
45
+ const ignore_selectors_1 = require("./config/ignore-selectors");
45
46
  /**
46
47
  * Generates a safe filename from a URL.
47
48
  * @param pageUrl The URL of the page.
@@ -94,6 +95,20 @@ async function snapshot(page, testInfo, name, config) {
94
95
  const baseFilename = `${timestamp}_${safeName}`;
95
96
  // 1. Capture full-page screenshot
96
97
  const screenshotPath = path.join(outputDir, `${baseFilename}.png`);
98
+ // In local mode: hide elements matching ignoreSelectors before the screenshot
99
+ // so dynamic content (version badges, timestamps, ads) doesn't cause false diffs.
100
+ // Sources (merged, deduped) — see config/ignore-selectors.ts:
101
+ // 1. .testivai/config.json → ignoreSelectors (global, OSS config)
102
+ // 2. testivai.config.ts → ignoreSelectors (global, power users)
103
+ // 3. testivai.witness(...) → { ignoreSelectors } (per-snapshot override)
104
+ let ignoreStyleEl = null;
105
+ if (isLocalMode) {
106
+ const allSelectors = (0, ignore_selectors_1.collectIgnoreSelectors)(process.cwd(), projectConfig, effectiveConfig);
107
+ const css = (0, ignore_selectors_1.buildIgnoreSelectorsCSS)(allSelectors);
108
+ if (css) {
109
+ ignoreStyleEl = await page.addStyleTag({ content: css });
110
+ }
111
+ }
97
112
  // Check if scroll-and-stitch is explicitly requested (backup method)
98
113
  if (effectiveConfig.useBrowserCapture === false) {
99
114
  // Use scroll-and-stitch approach (backup method)
@@ -310,6 +325,11 @@ async function snapshot(page, testInfo, name, config) {
310
325
  await page.screenshot({ path: screenshotPath, fullPage: true });
311
326
  }
312
327
  }
328
+ // Restore any elements hidden for ignoreSelectors
329
+ if (ignoreStyleEl) {
330
+ await ignoreStyleEl.evaluate((el) => el.remove()).catch(() => { });
331
+ ignoreStyleEl = null;
332
+ }
313
333
  // 1.5. Local mode: also place the screenshot in the layout expected by
314
334
  // @testivai/witness/report (subdirectory keyed by snapshot name).
315
335
  // This is what `BaselineStore.listTemp()` and `compareAll()` enumerate.
package/dist/types.d.ts CHANGED
@@ -216,6 +216,18 @@ export interface TestivAIConfig {
216
216
  selectors?: string[];
217
217
  /** Use browser capture for full-page screenshots (default: true, set to false for scroll-and-stitch) */
218
218
  useBrowserCapture?: boolean;
219
+ /**
220
+ * CSS selectors for elements to hide during screenshot capture (local mode).
221
+ * Elements are set to `visibility: hidden` before the screenshot is taken and
222
+ * restored immediately after — so dynamic content (version badges, timestamps,
223
+ * ads, live clocks) never contributes to the pixel diff.
224
+ *
225
+ * Can also be set globally in `.testivai/config.json` under `ignoreSelectors`.
226
+ * Per-snapshot values are merged with (not replaced by) the global list.
227
+ *
228
+ * Example: ["[data-testivai-ignore]", ".version-badge", "#live-chat-widget"]
229
+ */
230
+ ignoreSelectors?: string[];
219
231
  }
220
232
  /**
221
233
  * Layout/Bounding box data for an element
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testivai/witness-playwright",
3
- "version": "1.1.5",
3
+ "version": "1.2.1",
4
4
  "description": "Playwright sensor for Testivai Visual Regression Test system",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -52,7 +52,7 @@
52
52
  "sharp": "^0.34.5",
53
53
  "simple-git": "^3.21.0",
54
54
  "@testivai/common": "0.2.2",
55
- "@testivai/witness": "1.0.4"
55
+ "@testivai/witness": "1.1.0"
56
56
  },
57
57
  "devDependencies": {
58
58
  "@playwright/test": "^1.60.0",