@testivai/witness 1.1.0 → 1.2.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 (44) hide show
  1. package/dist/bin/testivai.js +2 -3
  2. package/dist/bin/testivai.js.map +1 -1
  3. package/dist/commands/capture.d.ts.map +1 -1
  4. package/dist/commands/capture.js +27 -4
  5. package/dist/commands/capture.js.map +1 -1
  6. package/dist/config/local-config.d.ts +45 -0
  7. package/dist/config/local-config.d.ts.map +1 -1
  8. package/dist/config/local-config.js +4 -0
  9. package/dist/config/local-config.js.map +1 -1
  10. package/dist/diff/dom-diff.d.ts +13 -2
  11. package/dist/diff/dom-diff.d.ts.map +1 -1
  12. package/dist/diff/dom-diff.js +32 -9
  13. package/dist/diff/dom-diff.js.map +1 -1
  14. package/dist/report/compare.d.ts +16 -0
  15. package/dist/report/compare.d.ts.map +1 -1
  16. package/dist/report/compare.js +34 -3
  17. package/dist/report/compare.js.map +1 -1
  18. package/dist/report/generator.d.ts +8 -0
  19. package/dist/report/generator.d.ts.map +1 -1
  20. package/dist/report/generator.js +12 -2
  21. package/dist/report/generator.js.map +1 -1
  22. package/dist/report/index.d.ts +1 -1
  23. package/dist/report/index.d.ts.map +1 -1
  24. package/dist/report/results.d.ts +8 -0
  25. package/dist/report/results.d.ts.map +1 -1
  26. package/dist/report/template.js +3 -1
  27. package/dist/report/template.js.map +1 -1
  28. package/dist/standalone/crawl.d.ts +23 -0
  29. package/dist/standalone/crawl.d.ts.map +1 -0
  30. package/dist/standalone/crawl.js +83 -0
  31. package/dist/standalone/crawl.js.map +1 -0
  32. package/dist/standalone/launcher.d.ts +23 -0
  33. package/dist/standalone/launcher.d.ts.map +1 -0
  34. package/dist/standalone/launcher.js +152 -0
  35. package/dist/standalone/launcher.js.map +1 -0
  36. package/dist/standalone/run.d.ts +27 -0
  37. package/dist/standalone/run.d.ts.map +1 -0
  38. package/dist/standalone/run.js +252 -0
  39. package/dist/standalone/run.js.map +1 -0
  40. package/package.json +7 -7
  41. package/dist/__tests__/setup.d.ts +0 -4
  42. package/dist/__tests__/setup.d.ts.map +0 -1
  43. package/dist/__tests__/setup.js +0 -24
  44. package/dist/__tests__/setup.js.map +0 -1
@@ -228,7 +228,7 @@ function renderSnapshot(snapshot) {
228
228
  <span class="snapshot-name">${escapeHtml(snapshot.name)}</span>
229
229
  <span class="snapshot-badge ${badgeClass}">${snapshot.status}</span>
230
230
  </div>
231
- ${snapshot.status === 'changed' ? `<div class="snapshot-stats">Diff: ${snapshot.diffPercent.toFixed(2)}% (${snapshot.diffCount} pixels)</div>` : ''}
231
+ ${snapshot.status === 'changed' || snapshot.autoPassed ? `<div class="snapshot-stats">Diff: ${snapshot.diffPercent.toFixed(2)}% (${snapshot.diffCount} pixels)${snapshot.autoPassed ? ` — auto-passed: ${snapshot.autoPassed === 'noise' ? 'DOM unchanged, within noise tolerance' : 'within configured diff tolerance'}` : ''}</div>` : ''}
232
232
  ${renderDomHint(snapshot)}
233
233
  <div class="${gridClass}">
234
234
  ${hasBaseline ? `<div class="diff-col"><label>Baseline</label><img src="${snapshot.baselinePath}" alt="Baseline"></div>` : ''}
@@ -274,6 +274,8 @@ function renderDomHint(snapshot) {
274
274
  parts.push(`${s.removed} removed`);
275
275
  if (s.attributeChanges > 0)
276
276
  parts.push(`${s.attributeChanges} attribute change${s.attributeChanges === 1 ? '' : 's'}`);
277
+ if (s.textChanges)
278
+ parts.push(`${s.textChanges} text change${s.textChanges === 1 ? '' : 's'}`);
277
279
  return `
278
280
  <div class="dom-hint changed" title="Structural DOM changes detected alongside the pixel diff.">
279
281
  <span class="label">DOM changed</span>
@@ -1 +1 @@
1
- {"version":3,"file":"template.js","sourceRoot":"","sources":["../../src/report/template.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"template.js","sourceRoot":"","sources":["../../src/report/template.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Standalone witness mode — pure helpers for URL/page handling.
3
+ *
4
+ * `testivai witness <url>` captures a running app with no test suite at all:
5
+ * routes come from config/flags or a same-origin link crawl of the start
6
+ * page. These helpers are pure so they can be unit-tested without a browser.
7
+ */
8
+ /** Snapshot name from a URL path: '/' → 'home', '/pricing/plans' → 'pricing-plans'. */
9
+ export declare function pageNameFromUrl(url: string): string;
10
+ /** Resolve explicit page paths (from --pages or config) against the base URL. */
11
+ export declare function resolvePages(baseUrl: string, pages: string[]): string[];
12
+ /**
13
+ * Filter raw hrefs collected from the start page down to crawlable pages:
14
+ * same-origin, http(s), no downloads, hash stripped, deduped, capped.
15
+ * The start URL itself is always first.
16
+ */
17
+ export declare function filterCrawledLinks(startUrl: string, hrefs: string[], maxPages: number): string[];
18
+ /** Parse a `1280x800`-style viewport flag. */
19
+ export declare function parseViewport(value: string): {
20
+ width: number;
21
+ height: number;
22
+ };
23
+ //# sourceMappingURL=crawl.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crawl.d.ts","sourceRoot":"","sources":["../../src/standalone/crawl.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAMH,uFAAuF;AACvF,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,CASnD;AAED,iFAAiF;AACjF,wBAAgB,YAAY,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,MAAM,EAAE,CAOvE;AAED;;;;GAIG;AACH,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,EAAE,QAAQ,EAAE,MAAM,GAAG,MAAM,EAAE,CA4BhG;AAED,8CAA8C;AAC9C,wBAAgB,aAAa,CAAC,KAAK,EAAE,MAAM,GAAG;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,MAAM,CAAA;CAAE,CAM9E"}
@@ -0,0 +1,83 @@
1
+ "use strict";
2
+ /**
3
+ * Standalone witness mode — pure helpers for URL/page handling.
4
+ *
5
+ * `testivai witness <url>` captures a running app with no test suite at all:
6
+ * routes come from config/flags or a same-origin link crawl of the start
7
+ * page. These helpers are pure so they can be unit-tested without a browser.
8
+ */
9
+ Object.defineProperty(exports, "__esModule", { value: true });
10
+ exports.pageNameFromUrl = pageNameFromUrl;
11
+ exports.resolvePages = resolvePages;
12
+ exports.filterCrawledLinks = filterCrawledLinks;
13
+ exports.parseViewport = parseViewport;
14
+ /** File extensions that are downloads, not pages. */
15
+ const NON_PAGE_EXTENSIONS = /\.(png|jpe?g|gif|svg|webp|ico|css|js|mjs|json|xml|txt|pdf|zip|tar|gz|mp[34]|webm|woff2?|ttf|eot)$/i;
16
+ /** Snapshot name from a URL path: '/' → 'home', '/pricing/plans' → 'pricing-plans'. */
17
+ function pageNameFromUrl(url) {
18
+ const { pathname } = new URL(url);
19
+ const clean = pathname.replace(/^\/+|\/+$/g, '');
20
+ if (!clean)
21
+ return 'home';
22
+ return clean
23
+ .split('/')
24
+ .map((segment) => segment.replace(/[^a-z0-9_-]+/gi, '_'))
25
+ .join('-')
26
+ .toLowerCase();
27
+ }
28
+ /** Resolve explicit page paths (from --pages or config) against the base URL. */
29
+ function resolvePages(baseUrl, pages) {
30
+ const base = new URL(baseUrl);
31
+ const resolved = pages
32
+ .map((p) => p.trim())
33
+ .filter((p) => p.length > 0)
34
+ .map((p) => new URL(p, base).href);
35
+ return [...new Set(resolved)];
36
+ }
37
+ /**
38
+ * Filter raw hrefs collected from the start page down to crawlable pages:
39
+ * same-origin, http(s), no downloads, hash stripped, deduped, capped.
40
+ * The start URL itself is always first.
41
+ */
42
+ function filterCrawledLinks(startUrl, hrefs, maxPages) {
43
+ const start = new URL(startUrl);
44
+ const seen = new Set();
45
+ const pages = [];
46
+ const push = (raw) => {
47
+ let url;
48
+ try {
49
+ url = new URL(raw, start);
50
+ }
51
+ catch {
52
+ return;
53
+ }
54
+ if (url.protocol !== 'http:' && url.protocol !== 'https:')
55
+ return;
56
+ if (url.origin !== start.origin)
57
+ return;
58
+ if (NON_PAGE_EXTENSIONS.test(url.pathname))
59
+ return;
60
+ url.hash = '';
61
+ const key = url.pathname.replace(/\/+$/, '') + url.search;
62
+ if (seen.has(key))
63
+ return;
64
+ seen.add(key);
65
+ pages.push(url.href);
66
+ };
67
+ push(startUrl);
68
+ for (const href of hrefs) {
69
+ if (pages.length >= maxPages)
70
+ break;
71
+ push(href);
72
+ }
73
+ return pages.slice(0, maxPages);
74
+ }
75
+ /** Parse a `1280x800`-style viewport flag. */
76
+ function parseViewport(value) {
77
+ const match = /^(\d{3,5})x(\d{3,5})$/.exec(value.trim());
78
+ if (!match) {
79
+ throw new Error(`Invalid viewport "${value}" — expected WIDTHxHEIGHT, e.g. 1280x800`);
80
+ }
81
+ return { width: parseInt(match[1], 10), height: parseInt(match[2], 10) };
82
+ }
83
+ //# sourceMappingURL=crawl.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"crawl.js","sourceRoot":"","sources":["../../src/standalone/crawl.ts"],"names":[],"mappings":";AAAA;;;;;;GAMG;;AAOH,0CASC;AAGD,oCAOC;AAOD,gDA4BC;AAGD,sCAMC;AApED,qDAAqD;AACrD,MAAM,mBAAmB,GACvB,oGAAoG,CAAC;AAEvG,uFAAuF;AACvF,SAAgB,eAAe,CAAC,GAAW;IACzC,MAAM,EAAE,QAAQ,EAAE,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,CAAC;IAClC,MAAM,KAAK,GAAG,QAAQ,CAAC,OAAO,CAAC,YAAY,EAAE,EAAE,CAAC,CAAC;IACjD,IAAI,CAAC,KAAK;QAAE,OAAO,MAAM,CAAC;IAC1B,OAAO,KAAK;SACT,KAAK,CAAC,GAAG,CAAC;SACV,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE,CAAC,OAAO,CAAC,OAAO,CAAC,gBAAgB,EAAE,GAAG,CAAC,CAAC;SACxD,IAAI,CAAC,GAAG,CAAC;SACT,WAAW,EAAE,CAAC;AACnB,CAAC;AAED,iFAAiF;AACjF,SAAgB,YAAY,CAAC,OAAe,EAAE,KAAe;IAC3D,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC;IAC9B,MAAM,QAAQ,GAAG,KAAK;SACnB,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;SACpB,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,MAAM,GAAG,CAAC,CAAC;SAC3B,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,CAAC;IACrC,OAAO,CAAC,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC,CAAC;AAChC,CAAC;AAED;;;;GAIG;AACH,SAAgB,kBAAkB,CAAC,QAAgB,EAAE,KAAe,EAAE,QAAgB;IACpF,MAAM,KAAK,GAAG,IAAI,GAAG,CAAC,QAAQ,CAAC,CAAC;IAChC,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,MAAM,KAAK,GAAa,EAAE,CAAC;IAE3B,MAAM,IAAI,GAAG,CAAC,GAAW,EAAQ,EAAE;QACjC,IAAI,GAAQ,CAAC;QACb,IAAI,CAAC;YACH,GAAG,GAAG,IAAI,GAAG,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC;QAC5B,CAAC;QAAC,MAAM,CAAC;YACP,OAAO;QACT,CAAC;QACD,IAAI,GAAG,CAAC,QAAQ,KAAK,OAAO,IAAI,GAAG,CAAC,QAAQ,KAAK,QAAQ;YAAE,OAAO;QAClE,IAAI,GAAG,CAAC,MAAM,KAAK,KAAK,CAAC,MAAM;YAAE,OAAO;QACxC,IAAI,mBAAmB,CAAC,IAAI,CAAC,GAAG,CAAC,QAAQ,CAAC;YAAE,OAAO;QACnD,GAAG,CAAC,IAAI,GAAG,EAAE,CAAC;QACd,MAAM,GAAG,GAAG,GAAG,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,GAAG,CAAC,MAAM,CAAC;QAC1D,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,OAAO;QAC1B,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACd,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACvB,CAAC,CAAC;IAEF,IAAI,CAAC,QAAQ,CAAC,CAAC;IACf,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,IAAI,KAAK,CAAC,MAAM,IAAI,QAAQ;YAAE,MAAM;QACpC,IAAI,CAAC,IAAI,CAAC,CAAC;IACb,CAAC;IACD,OAAO,KAAK,CAAC,KAAK,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;AAClC,CAAC;AAED,8CAA8C;AAC9C,SAAgB,aAAa,CAAC,KAAa;IACzC,MAAM,KAAK,GAAG,uBAAuB,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;IACzD,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CAAC,qBAAqB,KAAK,0CAA0C,CAAC,CAAC;IACxF,CAAC;IACD,OAAO,EAAE,KAAK,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,MAAM,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC;AAC3E,CAAC"}
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Headless Chrome launcher for standalone witness mode.
3
+ *
4
+ * `testivai witness <url>` must work with zero setup, so when no debuggable
5
+ * Chrome is already running we launch our own headless instance and clean it
6
+ * up afterwards. Resolution order for the executable:
7
+ *
8
+ * 1. TESTIVAI_CHROME_PATH env var (also how CI points at a Playwright/
9
+ * puppeteer-downloaded Chromium)
10
+ * 2. Well-known platform install paths
11
+ * 3. Names on PATH (google-chrome, chromium, ...)
12
+ */
13
+ export declare function findChrome(): string | null;
14
+ export interface LaunchedChrome {
15
+ port: number;
16
+ kill: () => void;
17
+ }
18
+ /**
19
+ * Launch a throwaway headless Chrome with remote debugging enabled.
20
+ * Uses a temp profile so the user's browser state is never touched.
21
+ */
22
+ export declare function launchChrome(executable: string, port: number): Promise<LaunchedChrome>;
23
+ //# sourceMappingURL=launcher.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"launcher.d.ts","sourceRoot":"","sources":["../../src/standalone/launcher.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAoBH,wBAAgB,UAAU,IAAI,MAAM,GAAG,IAAI,CAqB1C;AAED,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,IAAI,CAAC;CAClB;AAuBD;;;GAGG;AACH,wBAAsB,YAAY,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CA0C5F"}
@@ -0,0 +1,152 @@
1
+ "use strict";
2
+ /**
3
+ * Headless Chrome launcher for standalone witness mode.
4
+ *
5
+ * `testivai witness <url>` must work with zero setup, so when no debuggable
6
+ * Chrome is already running we launch our own headless instance and clean it
7
+ * up afterwards. Resolution order for the executable:
8
+ *
9
+ * 1. TESTIVAI_CHROME_PATH env var (also how CI points at a Playwright/
10
+ * puppeteer-downloaded Chromium)
11
+ * 2. Well-known platform install paths
12
+ * 3. Names on PATH (google-chrome, chromium, ...)
13
+ */
14
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
15
+ if (k2 === undefined) k2 = k;
16
+ var desc = Object.getOwnPropertyDescriptor(m, k);
17
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
18
+ desc = { enumerable: true, get: function() { return m[k]; } };
19
+ }
20
+ Object.defineProperty(o, k2, desc);
21
+ }) : (function(o, m, k, k2) {
22
+ if (k2 === undefined) k2 = k;
23
+ o[k2] = m[k];
24
+ }));
25
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
26
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
27
+ }) : function(o, v) {
28
+ o["default"] = v;
29
+ });
30
+ var __importStar = (this && this.__importStar) || (function () {
31
+ var ownKeys = function(o) {
32
+ ownKeys = Object.getOwnPropertyNames || function (o) {
33
+ var ar = [];
34
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
35
+ return ar;
36
+ };
37
+ return ownKeys(o);
38
+ };
39
+ return function (mod) {
40
+ if (mod && mod.__esModule) return mod;
41
+ var result = {};
42
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
43
+ __setModuleDefault(result, mod);
44
+ return result;
45
+ };
46
+ })();
47
+ Object.defineProperty(exports, "__esModule", { value: true });
48
+ exports.findChrome = findChrome;
49
+ exports.launchChrome = launchChrome;
50
+ const child_process_1 = require("child_process");
51
+ const fs = __importStar(require("fs"));
52
+ const os = __importStar(require("os"));
53
+ const path = __importStar(require("path"));
54
+ const http = __importStar(require("http"));
55
+ const logger_1 = require("../utils/logger");
56
+ const MAC_PATHS = [
57
+ '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',
58
+ '/Applications/Chromium.app/Contents/MacOS/Chromium',
59
+ '/Applications/Google Chrome Canary.app/Contents/MacOS/Google Chrome Canary',
60
+ ];
61
+ const LINUX_NAMES = ['google-chrome', 'google-chrome-stable', 'chromium-browser', 'chromium'];
62
+ const WIN_PATHS = [
63
+ 'C:\\Program Files\\Google\\Chrome\\Application\\chrome.exe',
64
+ 'C:\\Program Files (x86)\\Google\\Chrome\\Application\\chrome.exe',
65
+ ];
66
+ function findChrome() {
67
+ const fromEnv = process.env.TESTIVAI_CHROME_PATH;
68
+ if (fromEnv && fs.existsSync(fromEnv))
69
+ return fromEnv;
70
+ const candidates = process.platform === 'darwin' ? MAC_PATHS : process.platform === 'win32' ? WIN_PATHS : [];
71
+ for (const candidate of candidates) {
72
+ if (fs.existsSync(candidate))
73
+ return candidate;
74
+ }
75
+ if (process.platform !== 'win32') {
76
+ for (const name of LINUX_NAMES) {
77
+ try {
78
+ const found = (0, child_process_1.execFileSync)('which', [name], { encoding: 'utf-8' }).trim();
79
+ if (found)
80
+ return found;
81
+ }
82
+ catch {
83
+ // not on PATH — try the next name
84
+ }
85
+ }
86
+ }
87
+ return null;
88
+ }
89
+ /** Wait until the DevTools endpoint answers, up to timeoutMs. */
90
+ async function waitForDevtools(port, timeoutMs) {
91
+ const deadline = Date.now() + timeoutMs;
92
+ while (Date.now() < deadline) {
93
+ const ok = await new Promise((resolve) => {
94
+ const req = http.get({ host: '127.0.0.1', port, path: '/json/version', timeout: 1000 }, (res) => {
95
+ res.resume();
96
+ resolve(res.statusCode === 200);
97
+ });
98
+ req.on('error', () => resolve(false));
99
+ req.on('timeout', () => {
100
+ req.destroy();
101
+ resolve(false);
102
+ });
103
+ });
104
+ if (ok)
105
+ return true;
106
+ await new Promise((r) => setTimeout(r, 150));
107
+ }
108
+ return false;
109
+ }
110
+ /**
111
+ * Launch a throwaway headless Chrome with remote debugging enabled.
112
+ * Uses a temp profile so the user's browser state is never touched.
113
+ */
114
+ async function launchChrome(executable, port) {
115
+ const userDataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'testivai-chrome-'));
116
+ const args = [
117
+ `--remote-debugging-port=${port}`,
118
+ `--user-data-dir=${userDataDir}`,
119
+ '--headless=new',
120
+ '--no-first-run',
121
+ '--no-default-browser-check',
122
+ '--disable-gpu',
123
+ '--disable-extensions',
124
+ '--disable-background-networking',
125
+ '--hide-scrollbars',
126
+ 'about:blank',
127
+ ];
128
+ logger_1.logger.debug(`Launching Chrome: ${executable}`);
129
+ const child = (0, child_process_1.spawn)(executable, args, { stdio: 'ignore' });
130
+ const kill = () => {
131
+ try {
132
+ child.kill('SIGKILL');
133
+ }
134
+ catch {
135
+ // already gone
136
+ }
137
+ try {
138
+ fs.rmSync(userDataDir, { recursive: true, force: true });
139
+ }
140
+ catch {
141
+ // best-effort temp cleanup
142
+ }
143
+ };
144
+ const ready = await waitForDevtools(port, 12000);
145
+ if (!ready) {
146
+ kill();
147
+ throw new Error(`Chrome did not open its debugging endpoint on port ${port} within 12s. ` +
148
+ `Set TESTIVAI_CHROME_PATH to a working Chrome/Chromium binary if the auto-detected one is broken.`);
149
+ }
150
+ return { port, kill };
151
+ }
152
+ //# sourceMappingURL=launcher.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"launcher.js","sourceRoot":"","sources":["../../src/standalone/launcher.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Standalone witness mode — orchestrator.
3
+ *
4
+ * `testivai witness http://localhost:3000` with no test suite:
5
+ * 1. find or launch headless Chrome
6
+ * 2. resolve pages (config/flags, or same-origin crawl of the start page)
7
+ * 3. per page: stabilize → full-page screenshot + DOM → BaselineStore temp
8
+ * 4. compare against baselines → HTML report + results.json
9
+ *
10
+ * Everything downstream of capture (diff, tolerances, noise hint, report,
11
+ * approve, GitHub Action) is the exact same pipeline the test-suite adapters
12
+ * feed.
13
+ */
14
+ export interface StandaloneOptions {
15
+ /** Explicit page paths; disables crawling when provided. */
16
+ pages?: string[];
17
+ /** Crawl cap when discovering links. Default 10. */
18
+ maxPages?: number;
19
+ viewport?: {
20
+ width: number;
21
+ height: number;
22
+ };
23
+ /** Reuse an already-running debuggable Chrome on this port. */
24
+ port?: number;
25
+ }
26
+ export declare function runStandaloneWitness(startUrl: string, options: StandaloneOptions): Promise<void>;
27
+ //# sourceMappingURL=run.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.d.ts","sourceRoot":"","sources":["../../src/standalone/run.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAmBH,MAAM,WAAW,iBAAiB;IAChC,4DAA4D;IAC5D,KAAK,CAAC,EAAE,MAAM,EAAE,CAAC;IACjB,oDAAoD;IACpD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC;IAC7C,+DAA+D;IAC/D,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAmJD,wBAAsB,oBAAoB,CACxC,QAAQ,EAAE,MAAM,EAChB,OAAO,EAAE,iBAAiB,GACzB,OAAO,CAAC,IAAI,CAAC,CAwGf"}
@@ -0,0 +1,252 @@
1
+ "use strict";
2
+ /**
3
+ * Standalone witness mode — orchestrator.
4
+ *
5
+ * `testivai witness http://localhost:3000` with no test suite:
6
+ * 1. find or launch headless Chrome
7
+ * 2. resolve pages (config/flags, or same-origin crawl of the start page)
8
+ * 3. per page: stabilize → full-page screenshot + DOM → BaselineStore temp
9
+ * 4. compare against baselines → HTML report + results.json
10
+ *
11
+ * Everything downstream of capture (diff, tolerances, noise hint, report,
12
+ * approve, GitHub Action) is the exact same pipeline the test-suite adapters
13
+ * feed.
14
+ */
15
+ var __importDefault = (this && this.__importDefault) || function (mod) {
16
+ return (mod && mod.__esModule) ? mod : { "default": mod };
17
+ };
18
+ Object.defineProperty(exports, "__esModule", { value: true });
19
+ exports.runStandaloneWitness = runStandaloneWitness;
20
+ const chrome_remote_interface_1 = __importDefault(require("chrome-remote-interface"));
21
+ const chalk_1 = __importDefault(require("chalk"));
22
+ const store_1 = require("../baselines/store");
23
+ const local_config_1 = require("../config/local-config");
24
+ const generator_1 = require("../report/generator");
25
+ const logger_1 = require("../utils/logger");
26
+ const crawl_1 = require("./crawl");
27
+ const launcher_1 = require("./launcher");
28
+ /** Mirrors the adapters' stabilization CSS (see packages/playwright config/stabilize):
29
+ * near-zero durations complete animations at their FINAL state, so pages
30
+ * with entry animations render fully instead of freezing hidden. */
31
+ const STABILIZE_CSS = '*, *::before, *::after { animation-duration: 0.001s !important; animation-delay: 0s !important; ' +
32
+ 'animation-iteration-count: 1 !important; transition-duration: 0.001s !important; ' +
33
+ 'transition-delay: 0s !important; caret-color: transparent !important; scroll-behavior: auto !important; }';
34
+ async function evaluate(client, expression) {
35
+ const result = await client.Runtime.evaluate({ expression, returnByValue: true });
36
+ return result?.result?.value;
37
+ }
38
+ async function waitFor(client, expression, timeoutMs, intervalMs = 150) {
39
+ const deadline = Date.now() + timeoutMs;
40
+ while (Date.now() < deadline) {
41
+ try {
42
+ if ((await evaluate(client, expression)) === true)
43
+ return;
44
+ }
45
+ catch {
46
+ // page mid-navigation; retry
47
+ }
48
+ await new Promise((r) => setTimeout(r, intervalMs));
49
+ }
50
+ }
51
+ async function openPage(port, url) {
52
+ const target = await chrome_remote_interface_1.default.New({ port, url });
53
+ const client = await (0, chrome_remote_interface_1.default)({ port, target: target.id });
54
+ await Promise.all([client.Page.enable(), client.Runtime.enable()]);
55
+ return { client, targetId: target.id };
56
+ }
57
+ async function closePage(port, page) {
58
+ try {
59
+ await page.client.close();
60
+ }
61
+ catch {
62
+ /* already closed */
63
+ }
64
+ try {
65
+ await chrome_remote_interface_1.default.Close({ port, id: page.targetId });
66
+ }
67
+ catch {
68
+ /* target already gone */
69
+ }
70
+ }
71
+ /** Stabilize + hide ignored elements. Returns true when the style tag was injected. */
72
+ async function prepPage(client, stabilize, ignoreSelectors) {
73
+ const cssParts = [];
74
+ if (stabilize)
75
+ cssParts.push(STABILIZE_CSS);
76
+ if (ignoreSelectors.length > 0) {
77
+ cssParts.push(ignoreSelectors.map((s) => `${s} { visibility: hidden !important; }`).join('\n'));
78
+ }
79
+ if (cssParts.length === 0)
80
+ return;
81
+ await evaluate(client, `(() => { const el = document.createElement('style');` +
82
+ ` el.textContent = ${JSON.stringify(cssParts.join('\n'))};` +
83
+ ` document.head.appendChild(el); return true; })()`);
84
+ if (stabilize) {
85
+ // Remote sites load webfonts over the real network from a cold profile;
86
+ // a fallback-font capture diffs 30%+ against a webfont baseline. Wait
87
+ // generously — resolves in well under a second once fonts are cached
88
+ // by the OS/network layer.
89
+ await waitFor(client, `document.fonts ? document.fonts.status !== 'loading' : true`, 10000);
90
+ await evaluate(client, `document.fonts && document.fonts.ready`); // settle
91
+ }
92
+ }
93
+ /**
94
+ * Serialize the DOM with ignored elements removed. `ignoreSelectors`
95
+ * excludes an element from the pixel diff (visibility:hidden), so it must
96
+ * be excluded from the DOM/text signal too — otherwise dynamic ignored
97
+ * content (counters, feeds) flags a DOM change the pixels can't show.
98
+ */
99
+ function domSnapshotExpression(ignoreSelectors) {
100
+ return `(() => {
101
+ const clone = document.documentElement.cloneNode(true);
102
+ for (const sel of ${JSON.stringify(ignoreSelectors)}) {
103
+ try { clone.querySelectorAll(sel).forEach(el => el.remove()); } catch {}
104
+ }
105
+ return clone.outerHTML;
106
+ })()`;
107
+ }
108
+ /**
109
+ * Scroll stepwise through the page (viewport-height hops, bounded), then
110
+ * back to the top. Fires IntersectionObserver reveal-on-scroll content that
111
+ * a headless capture would otherwise never render — without resizing the
112
+ * viewport, which would explode 100vh-based layouts.
113
+ */
114
+ async function revealPage(client) {
115
+ try {
116
+ await client.Runtime.evaluate({
117
+ expression: `(async () => {
118
+ const step = window.innerHeight || 800;
119
+ const max = Math.min(document.documentElement.scrollHeight, step * 40);
120
+ for (let y = 0; y <= max; y += step) {
121
+ window.scrollTo(0, y);
122
+ await new Promise(r => setTimeout(r, 120));
123
+ }
124
+ window.scrollTo(0, 0);
125
+ await new Promise(r => setTimeout(r, 250));
126
+ return true;
127
+ })()`,
128
+ returnByValue: true,
129
+ awaitPromise: true,
130
+ });
131
+ }
132
+ catch {
133
+ // best-effort: capture proceeds from the top either way
134
+ }
135
+ }
136
+ /** Full-page screenshot via layout metrics (same technique as the Playwright adapter's CDP path). */
137
+ async function captureFullPage(client) {
138
+ // Freshly-launched Chrome can report zero-size content before its first
139
+ // real layout — poll briefly until metrics are usable.
140
+ let width = 0;
141
+ let height = 0;
142
+ const deadline = Date.now() + 5000;
143
+ for (;;) {
144
+ const metrics = await client.Page.getLayoutMetrics();
145
+ width = Math.ceil(metrics.cssContentSize?.width ?? metrics.contentSize.width);
146
+ height = Math.ceil(metrics.cssContentSize?.height ?? metrics.contentSize.height);
147
+ if ((width > 0 && height > 0) || Date.now() >= deadline)
148
+ break;
149
+ await new Promise((r) => setTimeout(r, 200));
150
+ }
151
+ if (width <= 0 || height <= 0) {
152
+ throw new Error(`page reported unusable content size ${width}x${height}`);
153
+ }
154
+ const shot = await client.Page.captureScreenshot({
155
+ format: 'png',
156
+ captureBeyondViewport: true,
157
+ clip: { x: 0, y: 0, width, height, scale: 1 },
158
+ });
159
+ return Buffer.from(shot.data, 'base64');
160
+ }
161
+ async function runStandaloneWitness(startUrl, options) {
162
+ const projectRoot = process.cwd();
163
+ const config = (0, local_config_1.loadLocalConfig)(projectRoot);
164
+ const viewport = options.viewport ?? config.viewport ?? { width: 1280, height: 800 };
165
+ const maxPages = options.maxPages ?? config.maxPages ?? 10;
166
+ const explicitPages = options.pages ?? config.pages;
167
+ // 1. Browser: reuse a provided port, else launch our own headless Chrome
168
+ let launched = null;
169
+ let port = options.port ?? 0;
170
+ if (!port) {
171
+ const executable = (0, launcher_1.findChrome)();
172
+ if (!executable) {
173
+ throw new Error('No Chrome/Chromium found. Install Google Chrome, or set TESTIVAI_CHROME_PATH to a Chrome binary ' +
174
+ '(a Playwright-downloaded chromium works: npx playwright install chromium).');
175
+ }
176
+ port = 9222 + Math.floor(Math.random() * 800);
177
+ launched = await (0, launcher_1.launchChrome)(executable, port);
178
+ }
179
+ const store = new store_1.BaselineStore(projectRoot);
180
+ const captured = [];
181
+ try {
182
+ // 2. Resolve the page list
183
+ let pages;
184
+ if (explicitPages && explicitPages.length > 0) {
185
+ pages = (0, crawl_1.resolvePages)(startUrl, explicitPages);
186
+ }
187
+ else {
188
+ const first = await openPage(port, startUrl);
189
+ await waitFor(first.client, `document.readyState === 'complete'`, 15000);
190
+ const hrefs = (await evaluate(first.client, `Array.from(document.querySelectorAll('a[href]')).map(a => a.href)`)) ?? [];
191
+ await closePage(port, first);
192
+ pages = (0, crawl_1.filterCrawledLinks)(startUrl, hrefs, maxPages);
193
+ }
194
+ logger_1.logger.info(`Witnessing ${pages.length} page(s) at ${viewport.width}x${viewport.height}`);
195
+ // 3. Capture every page
196
+ for (const url of pages) {
197
+ const name = (0, crawl_1.pageNameFromUrl)(url);
198
+ const page = await openPage(port, url);
199
+ try {
200
+ await page.client.Emulation.setDeviceMetricsOverride({
201
+ width: viewport.width,
202
+ height: viewport.height,
203
+ deviceScaleFactor: 1,
204
+ mobile: false,
205
+ });
206
+ await waitFor(page.client, `document.readyState === 'complete'`, 15000);
207
+ await prepPage(page.client, config.stabilize, config.ignoreSelectors ?? []);
208
+ await revealPage(page.client);
209
+ const screenshot = await captureFullPage(page.client);
210
+ const dom = await evaluate(page.client, domSnapshotExpression(config.ignoreSelectors ?? []));
211
+ store.writeTemp(name, screenshot, typeof dom === 'string' ? dom : undefined);
212
+ captured.push(name);
213
+ console.log(chalk_1.default.gray(` ✓ ${name} (${url})`));
214
+ }
215
+ catch (err) {
216
+ console.log(chalk_1.default.yellow(` ⚠ skipped ${name}: ${err.message}`));
217
+ }
218
+ finally {
219
+ await closePage(port, page);
220
+ }
221
+ }
222
+ }
223
+ finally {
224
+ if (launched)
225
+ launched.kill();
226
+ }
227
+ if (captured.length === 0) {
228
+ throw new Error('No pages captured. Is the app running at that URL?');
229
+ }
230
+ // 4. Diff + report through the standard pipeline (tolerances from config.json)
231
+ const report = (0, generator_1.generateReport)({
232
+ projectRoot,
233
+ reportDir: config.reportDir ?? 'visual-report',
234
+ threshold: config.threshold,
235
+ autoOpen: config.autoOpen,
236
+ version: require('../../package.json').version,
237
+ });
238
+ const { summary } = report;
239
+ console.log();
240
+ console.log(chalk_1.default.cyan.bold(' ═══ TestivAI Visual Report ═══'));
241
+ console.log(` Total: ${summary.total} | Passed: ${summary.passed} | Changed: ${summary.changed} | New: ${summary.newSnapshots}`);
242
+ if (summary.newSnapshots > 0) {
243
+ console.log(chalk_1.default.gray(' New baselines: npx testivai approve --all (then commit .testivai/baselines/)'));
244
+ }
245
+ if (summary.changed > 0) {
246
+ console.log(chalk_1.default.gray(` Review: ${config.reportDir ?? 'visual-report'}/index.html — approve with: npx testivai approve <name>`));
247
+ }
248
+ if (config.failOnDiff && summary.changed > 0) {
249
+ process.exitCode = 1;
250
+ }
251
+ }
252
+ //# sourceMappingURL=run.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"run.js","sourceRoot":"","sources":["../../src/standalone/run.ts"],"names":[],"mappings":"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"}