@testivai/witness-selenium 0.3.10 → 0.3.11

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.
package/README.md CHANGED
@@ -57,7 +57,7 @@ All of these stay `changed` unless you configure a pass criterion; `noiseAutoPas
57
57
 
58
58
  ## Full-page screenshots
59
59
 
60
- - **Chrome / Edge** — true full-page capture via CDP (`captureBeyondViewport`), the same mechanism Playwright uses. Automatic; nothing to configure.
60
+ - **Chrome / Edge** — true full-page capture via CDP (`captureBeyondViewport`), the same mechanism Playwright uses. Automatic; nothing to configure. Chrome can return a frame painted for the wrong viewport while it sets up that capture, so the adapter waits for a freshly rendered frame, then captures until two captures in a row are identical (at most four). A stable page costs one extra screenshot; `TESTIVAI_DEBUG=true` logs when captures differed.
61
61
  - **Firefox / Safari** — the Selenium JS bindings expose no full-page API, so captures are viewport-sized. Set the window to the size you want compared; runs are consistent either way. (The Python and Java TestivAI adapters do full-page on Firefox via its native API.)
62
62
 
63
63
  ## Stabilized captures
@@ -0,0 +1,30 @@
1
+ import { type CaptureLog } from '@testivai/witness';
2
+ import type { WitnessDriver } from '../types';
3
+ /** A Chromium driver: one that can send raw CDP commands. */
4
+ export type CdpDriver = Required<Pick<WitnessDriver, 'sendAndGetDevToolsCommand'>>;
5
+ /**
6
+ * Full-page screenshot through CDP: wait for a fresh frame, measure the
7
+ * content, then capture beyond the viewport until two captures in a row match
8
+ * (at most MAX_STABLE_CAPTURES from @testivai/witness).
9
+ *
10
+ * Chrome can hand back a frame painted with styles resolved against the wrong
11
+ * viewport while it sets up a beyond-viewport capture: narrow-viewport media
12
+ * rules, a collapsed 100vh hero, or em text drawn at a `max-width: 480px`
13
+ * body size. The page measures correctly but the pixels do not, so an
14
+ * unchanged page flips between "changed" and "passed" across runs. See
15
+ * capture/stable-capture.ts in @testivai/witness, which the standalone CLI
16
+ * and the Playwright adapter share.
17
+ *
18
+ * Selenium reaches the page only through `sendAndGetDevToolsCommand`, so the
19
+ * frame wait is CDP `Runtime.evaluate` with `awaitPromise`. Every command gets
20
+ * an explicit params object instead of relying on the bindings' default.
21
+ *
22
+ * A stable page costs one extra screenshot.
23
+ *
24
+ * @param driver A Chromium (Chrome/Edge) driver.
25
+ * @param log Optional debug sink.
26
+ * @returns The PNG bytes of the kept capture.
27
+ * @throws When the page reports no usable size or a capture returns no data,
28
+ * so the caller can fall back to a viewport screenshot.
29
+ */
30
+ export declare function captureFullPageScreenshot(driver: CdpDriver, log?: CaptureLog): Promise<Buffer>;
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.captureFullPageScreenshot = captureFullPageScreenshot;
4
+ const witness_1 = require("@testivai/witness");
5
+ /**
6
+ * Full-page screenshot through CDP: wait for a fresh frame, measure the
7
+ * content, then capture beyond the viewport until two captures in a row match
8
+ * (at most MAX_STABLE_CAPTURES from @testivai/witness).
9
+ *
10
+ * Chrome can hand back a frame painted with styles resolved against the wrong
11
+ * viewport while it sets up a beyond-viewport capture: narrow-viewport media
12
+ * rules, a collapsed 100vh hero, or em text drawn at a `max-width: 480px`
13
+ * body size. The page measures correctly but the pixels do not, so an
14
+ * unchanged page flips between "changed" and "passed" across runs. See
15
+ * capture/stable-capture.ts in @testivai/witness, which the standalone CLI
16
+ * and the Playwright adapter share.
17
+ *
18
+ * Selenium reaches the page only through `sendAndGetDevToolsCommand`, so the
19
+ * frame wait is CDP `Runtime.evaluate` with `awaitPromise`. Every command gets
20
+ * an explicit params object instead of relying on the bindings' default.
21
+ *
22
+ * A stable page costs one extra screenshot.
23
+ *
24
+ * @param driver A Chromium (Chrome/Edge) driver.
25
+ * @param log Optional debug sink.
26
+ * @returns The PNG bytes of the kept capture.
27
+ * @throws When the page reports no usable size or a capture returns no data,
28
+ * so the caller can fall back to a viewport screenshot.
29
+ */
30
+ async function captureFullPageScreenshot(driver, log) {
31
+ const waitOnce = async () => {
32
+ const evaluated = await driver.sendAndGetDevToolsCommand('Runtime.evaluate', {
33
+ expression: witness_1.FRESH_FRAME_EXPRESSION,
34
+ awaitPromise: true,
35
+ returnByValue: true,
36
+ });
37
+ return evaluated?.result?.value === true;
38
+ };
39
+ const settle = () => (0, witness_1.settleFreshFrame)(waitOnce, log);
40
+ await settle();
41
+ const metrics = await driver.sendAndGetDevToolsCommand('Page.getLayoutMetrics', {});
42
+ const size = metrics?.cssContentSize ?? metrics?.contentSize;
43
+ const width = Math.ceil(size?.width ?? 0);
44
+ const height = Math.ceil(size?.height ?? 0);
45
+ if (!(width > 0 && height > 0)) {
46
+ throw new Error(`page reported unusable content size ${width}x${height}`);
47
+ }
48
+ log?.(`page ${width}x${height}, viewport ${metrics.cssLayoutViewport?.clientWidth}x${metrics.cssLayoutViewport?.clientHeight}`);
49
+ const data = await (0, witness_1.captureUntilStable)({
50
+ capture: async () => {
51
+ const shot = await driver.sendAndGetDevToolsCommand('Page.captureScreenshot', {
52
+ format: 'png',
53
+ captureBeyondViewport: true,
54
+ clip: { x: 0, y: 0, width, height, scale: 1 },
55
+ });
56
+ if (typeof shot?.data !== 'string' || shot.data.length === 0) {
57
+ throw new Error('Page.captureScreenshot returned no data');
58
+ }
59
+ return shot.data;
60
+ },
61
+ settle,
62
+ log,
63
+ });
64
+ return Buffer.from(data, 'base64');
65
+ }
package/dist/witness.js CHANGED
@@ -58,6 +58,7 @@ exports.witness = witness;
58
58
  const witness_1 = require("@testivai/witness");
59
59
  const fs = __importStar(require("fs"));
60
60
  const path = __importStar(require("path"));
61
+ const full_page_1 = require("./capture/full-page");
61
62
  /** id of the style element injected for the duration of a capture */
62
63
  const STYLE_ID = '__testivai_capture_css__';
63
64
  /**
@@ -158,13 +159,16 @@ async function waitForFonts(driver) {
158
159
  async function captureScreenshot(driver) {
159
160
  // Chromium drivers expose raw CDP; captureBeyondViewport gives a true
160
161
  // full-page capture without resizing the window (which breaks 100vh
161
- // layouts — the reason we never use the resize trick).
162
+ // layouts — the reason we never use the resize trick). It waits for a
163
+ // fresh frame and captures until two in a row match: Chrome can return a
164
+ // frame painted for the wrong viewport while it sets up the capture
165
+ // (capture/full-page.ts).
162
166
  if (typeof driver.sendAndGetDevToolsCommand === 'function') {
167
+ const captureLog = process.env.TESTIVAI_DEBUG === 'true'
168
+ ? (message) => console.log(`[TestivAI] Full-page capture: ${message}`)
169
+ : undefined;
163
170
  try {
164
- const result = await driver.sendAndGetDevToolsCommand('Page.captureScreenshot', { captureBeyondViewport: true, format: 'png' });
165
- if (result && typeof result.data === 'string' && result.data.length > 0) {
166
- return Buffer.from(result.data, 'base64');
167
- }
171
+ return await (0, full_page_1.captureFullPageScreenshot)(driver, captureLog);
168
172
  }
169
173
  catch {
170
174
  // fall through to the viewport screenshot
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testivai/witness-selenium",
3
- "version": "0.3.10",
3
+ "version": "0.3.11",
4
4
  "description": "Selenium WebDriver adapter for TestivAI Witness — local-first visual regression with pixel + DOM comparison",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",