@testivai/witness-playwright 1.2.0 → 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.
|
|
@@ -96,27 +97,15 @@ async function snapshot(page, testInfo, name, config) {
|
|
|
96
97
|
const screenshotPath = path.join(outputDir, `${baseFilename}.png`);
|
|
97
98
|
// In local mode: hide elements matching ignoreSelectors before the screenshot
|
|
98
99
|
// so dynamic content (version badges, timestamps, ads) doesn't cause false diffs.
|
|
99
|
-
// Sources (merged, deduped):
|
|
100
|
+
// Sources (merged, deduped) — see config/ignore-selectors.ts:
|
|
100
101
|
// 1. .testivai/config.json → ignoreSelectors (global, OSS config)
|
|
101
102
|
// 2. testivai.config.ts → ignoreSelectors (global, power users)
|
|
102
103
|
// 3. testivai.witness(...) → { ignoreSelectors } (per-snapshot override)
|
|
103
104
|
let ignoreStyleEl = null;
|
|
104
105
|
if (isLocalMode) {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
const witnessConfigPath = path.join(process.cwd(), '.testivai', 'config.json');
|
|
109
|
-
if (fs.existsSync(witnessConfigPath)) {
|
|
110
|
-
const raw = JSON.parse(fs.readFileSync(witnessConfigPath, 'utf-8'));
|
|
111
|
-
witnessConfigIgnore = raw.ignoreSelectors ?? [];
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
catch { /* ignore malformed config */ }
|
|
115
|
-
const playwrightConfigIgnore = projectConfig.ignoreSelectors ?? [];
|
|
116
|
-
const perSnapshotIgnore = effectiveConfig.ignoreSelectors ?? [];
|
|
117
|
-
const allSelectors = [...new Set([...witnessConfigIgnore, ...playwrightConfigIgnore, ...perSnapshotIgnore])];
|
|
118
|
-
if (allSelectors.length > 0) {
|
|
119
|
-
const css = allSelectors.map(s => `${s} { visibility: hidden !important; }`).join('\n');
|
|
106
|
+
const allSelectors = (0, ignore_selectors_1.collectIgnoreSelectors)(process.cwd(), projectConfig, effectiveConfig);
|
|
107
|
+
const css = (0, ignore_selectors_1.buildIgnoreSelectorsCSS)(allSelectors);
|
|
108
|
+
if (css) {
|
|
120
109
|
ignoreStyleEl = await page.addStyleTag({ content: css });
|
|
121
110
|
}
|
|
122
111
|
}
|