@testivai/witness-playwright 0.1.7 → 0.1.9

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/dist/snapshot.js CHANGED
@@ -75,21 +75,80 @@ async function snapshot(page, testInfo, name, config) {
75
75
  // Load project configuration and merge with test-specific overrides
76
76
  const projectConfig = await (0, loader_1.loadConfig)();
77
77
  const effectiveConfig = (0, loader_1.mergeTestConfig)(projectConfig, config);
78
+ // Debug: Log config
79
+ if (process.env.TESTIVAI_DEBUG === 'true') {
80
+ console.log('[TestivAI] Config:', {
81
+ projectConfig,
82
+ testConfig: config,
83
+ effectiveConfig
84
+ });
85
+ }
78
86
  const outputDir = path.join(process.cwd(), '.testivai', 'temp');
79
87
  await fs.ensureDir(outputDir);
80
88
  const snapshotName = name || getSnapshotNameFromUrl(page.url());
81
89
  const timestamp = Date.now();
82
90
  const safeName = snapshotName.replace(/[^a-z0-9]/gi, '_').toLowerCase();
83
91
  const baseFilename = `${timestamp}_${safeName}`;
84
- // 1. Capture full-page screenshot using scroll-and-stitch approach
85
- // This is the same technique used by GoFullPage Chrome extension
92
+ // 1. Capture full-page screenshot
86
93
  const screenshotPath = path.join(outputDir, `${baseFilename}.png`);
87
- // Get viewport dimensions
88
- const viewport = page.viewportSize();
89
- const viewportWidth = viewport?.width || 1280;
90
- const viewportHeight = viewport?.height || 720;
91
- // Find the main scrollable container and get its dimensions
92
- const scrollableInfo = await page.evaluate(`
94
+ // Check if CDP approach is enabled
95
+ if (effectiveConfig.useCDP) {
96
+ // Use Chrome DevTools Protocol for full-page capture
97
+ if (process.env.TESTIVAI_DEBUG === 'true') {
98
+ console.log('[TestivAI] Using CDP approach for full-page screenshot');
99
+ }
100
+ try {
101
+ // Create a CDP session
102
+ const client = await page.context().newCDPSession(page);
103
+ // Enable Page domain
104
+ await client.send('Page.enable');
105
+ // Get layout metrics to determine full page size
106
+ const layoutMetrics = await client.send('Page.getLayoutMetrics');
107
+ // Calculate full page dimensions
108
+ const pageWidth = Math.ceil(layoutMetrics.contentSize.width);
109
+ const pageHeight = Math.ceil(layoutMetrics.contentSize.height);
110
+ if (process.env.TESTIVAI_DEBUG === 'true') {
111
+ console.log('[TestivAI] CDP Layout metrics:', {
112
+ pageWidth,
113
+ pageHeight,
114
+ viewportWidth: layoutMetrics.layoutViewport.clientWidth,
115
+ viewportHeight: layoutMetrics.layoutViewport.clientHeight
116
+ });
117
+ }
118
+ // Capture screenshot with captureBeyondViewport: true
119
+ const screenshot = await client.send('Page.captureScreenshot', {
120
+ format: 'png',
121
+ captureBeyondViewport: true,
122
+ clip: {
123
+ x: 0,
124
+ y: 0,
125
+ width: pageWidth,
126
+ height: pageHeight,
127
+ scale: 1
128
+ }
129
+ });
130
+ // Save the screenshot
131
+ await fs.writeFile(screenshotPath, Buffer.from(screenshot.data, 'base64'));
132
+ // Close CDP session
133
+ await client.detach();
134
+ }
135
+ catch (error) {
136
+ console.error('[TestivAI] CDP screenshot failed:', error.message);
137
+ // Fallback to regular screenshot
138
+ await page.screenshot({ path: screenshotPath, fullPage: true });
139
+ }
140
+ }
141
+ else {
142
+ // Use scroll-and-stitch approach (default)
143
+ if (process.env.TESTIVAI_DEBUG === 'true') {
144
+ console.log('[TestivAI] Using scroll-and-stitch approach for full-page screenshot');
145
+ }
146
+ // Get viewport dimensions
147
+ const viewport = page.viewportSize();
148
+ const viewportWidth = viewport?.width || 1280;
149
+ const viewportHeight = viewport?.height || 720;
150
+ // Find the main scrollable container and get its dimensions
151
+ const scrollableInfo = await page.evaluate(`
93
152
  (function() {
94
153
  var mainScrollable = null;
95
154
  var maxScrollHeight = 0;
@@ -134,91 +193,92 @@ async function snapshot(page, testInfo, name, config) {
134
193
  };
135
194
  })()
136
195
  `);
137
- // Calculate number of screenshots needed
138
- const totalHeight = scrollableInfo.scrollHeight;
139
- const captureHeight = scrollableInfo.clientHeight;
140
- const numCaptures = Math.ceil(totalHeight / captureHeight);
141
- // Debug logging (only when TESTIVAI_DEBUG is enabled)
142
- if (process.env.TESTIVAI_DEBUG === 'true') {
143
- console.log(`[TestivAI] Scroll-and-stitch info:`, {
144
- hasScrollable: scrollableInfo.hasScrollable,
145
- scrollableId: scrollableInfo.scrollableId,
146
- totalHeight,
147
- captureHeight,
148
- numCaptures,
149
- viewportWidth,
150
- viewportHeight
151
- });
152
- }
153
- // If only one capture needed, just take a regular screenshot
154
- if (numCaptures <= 1) {
155
- await page.screenshot({ path: screenshotPath, fullPage: true });
156
- }
157
- else {
158
- // Scroll-and-stitch approach
159
- const screenshots = [];
160
- for (let i = 0; i < numCaptures; i++) {
161
- const scrollPosition = i * captureHeight;
162
- // Scroll to position
163
- if (scrollableInfo.hasScrollable && scrollableInfo.scrollableId) {
164
- await page.evaluate(`
196
+ // Calculate number of screenshots needed
197
+ const totalHeight = scrollableInfo.scrollHeight;
198
+ const captureHeight = scrollableInfo.clientHeight;
199
+ const numCaptures = Math.ceil(totalHeight / captureHeight);
200
+ // Debug logging (only when TESTIVAI_DEBUG is enabled)
201
+ if (process.env.TESTIVAI_DEBUG === 'true') {
202
+ console.log(`[TestivAI] Scroll-and-stitch info:`, {
203
+ hasScrollable: scrollableInfo.hasScrollable,
204
+ scrollableId: scrollableInfo.scrollableId,
205
+ totalHeight,
206
+ captureHeight,
207
+ numCaptures,
208
+ viewportWidth,
209
+ viewportHeight
210
+ });
211
+ }
212
+ // If only one capture needed, just take a regular screenshot
213
+ if (numCaptures <= 1) {
214
+ await page.screenshot({ path: screenshotPath, fullPage: true });
215
+ }
216
+ else {
217
+ // Scroll-and-stitch approach
218
+ const screenshots = [];
219
+ for (let i = 0; i < numCaptures; i++) {
220
+ const scrollPosition = i * captureHeight;
221
+ // Scroll to position
222
+ if (scrollableInfo.hasScrollable && scrollableInfo.scrollableId) {
223
+ await page.evaluate(`
165
224
  document.getElementById('${scrollableInfo.scrollableId}').scrollTop = ${scrollPosition};
166
225
  `);
226
+ }
227
+ else {
228
+ await page.evaluate(`window.scrollTo(0, ${scrollPosition})`);
229
+ }
230
+ // Wait for scroll and any lazy-loaded content
231
+ await page.waitForTimeout(100);
232
+ // Capture this viewport
233
+ const screenshotBuffer = await page.screenshot({ fullPage: false });
234
+ screenshots.push(screenshotBuffer);
167
235
  }
168
- else {
169
- await page.evaluate(`window.scrollTo(0, ${scrollPosition})`);
170
- }
171
- // Wait for scroll and any lazy-loaded content
172
- await page.waitForTimeout(100);
173
- // Capture this viewport
174
- const screenshotBuffer = await page.screenshot({ fullPage: false });
175
- screenshots.push(screenshotBuffer);
176
- }
177
- // Stitch screenshots together using sharp
178
- // Calculate the actual height of the last capture (may be partial)
179
- const lastCaptureHeight = totalHeight - (captureHeight * (numCaptures - 1));
180
- // Create composite image
181
- const compositeInputs = screenshots.map((buffer, index) => {
182
- const isLast = index === screenshots.length - 1;
183
- const yOffset = index * captureHeight;
184
- // For the last screenshot, we need to crop from the bottom
185
- if (isLast && lastCaptureHeight < captureHeight) {
236
+ // Stitch screenshots together using sharp
237
+ // Calculate the actual height of the last capture (may be partial)
238
+ const lastCaptureHeight = totalHeight - (captureHeight * (numCaptures - 1));
239
+ // Create composite image
240
+ const compositeInputs = screenshots.map((buffer, index) => {
241
+ const isLast = index === screenshots.length - 1;
242
+ const yOffset = index * captureHeight;
243
+ // For the last screenshot, we need to crop from the bottom
244
+ if (isLast && lastCaptureHeight < captureHeight) {
245
+ return {
246
+ input: buffer,
247
+ top: yOffset,
248
+ left: 0,
249
+ // We'll handle the cropping separately
250
+ };
251
+ }
186
252
  return {
187
253
  input: buffer,
188
254
  top: yOffset,
189
255
  left: 0,
190
- // We'll handle the cropping separately
191
256
  };
192
- }
193
- return {
194
- input: buffer,
195
- top: yOffset,
196
- left: 0,
197
- };
198
- });
199
- // Create the final stitched image
200
- const finalImage = (0, sharp_1.default)({
201
- create: {
202
- width: viewportWidth,
203
- height: totalHeight,
204
- channels: 4,
205
- background: { r: 255, g: 255, b: 255, alpha: 1 }
206
- }
207
- });
208
- // Composite all screenshots
209
- const stitchedImage = await finalImage
210
- .composite(compositeInputs)
211
- .png()
212
- .toBuffer();
213
- await fs.writeFile(screenshotPath, stitchedImage);
214
- // Restore original scroll position
215
- if (scrollableInfo.hasScrollable && scrollableInfo.scrollableId) {
216
- await page.evaluate(`
257
+ });
258
+ // Create the final stitched image
259
+ const finalImage = (0, sharp_1.default)({
260
+ create: {
261
+ width: viewportWidth,
262
+ height: totalHeight,
263
+ channels: 4,
264
+ background: { r: 255, g: 255, b: 255, alpha: 1 }
265
+ }
266
+ });
267
+ // Composite all screenshots
268
+ const stitchedImage = await finalImage
269
+ .composite(compositeInputs)
270
+ .png()
271
+ .toBuffer();
272
+ await fs.writeFile(screenshotPath, stitchedImage);
273
+ // Restore original scroll position
274
+ if (scrollableInfo.hasScrollable && scrollableInfo.scrollableId) {
275
+ await page.evaluate(`
217
276
  document.getElementById('${scrollableInfo.scrollableId}').scrollTop = ${scrollableInfo.scrollTop};
218
277
  `);
219
- }
220
- else {
221
- await page.evaluate(`window.scrollTo(0, ${scrollableInfo.scrollTop})`);
278
+ }
279
+ else {
280
+ await page.evaluate(`window.scrollTo(0, ${scrollableInfo.scrollTop})`);
281
+ }
222
282
  }
223
283
  }
224
284
  // 2. Dump full-page DOM
package/dist/types.d.ts CHANGED
@@ -133,6 +133,8 @@ export interface TestivAIConfig {
133
133
  performance?: Partial<PerformanceConfig>;
134
134
  /** Element selectors to capture (existing option) */
135
135
  selectors?: string[];
136
+ /** Use Chrome DevTools Protocol for full-page capture (default: false - uses scroll-and-stitch) */
137
+ useCDP?: boolean;
136
138
  }
137
139
  /**
138
140
  * 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": "0.1.7",
3
+ "version": "0.1.9",
4
4
  "description": "Playwright sensor for Testivai Visual Regression Test system",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
package/src/snapshot.ts CHANGED
@@ -46,6 +46,15 @@ export async function snapshot(
46
46
  const projectConfig = await loadConfig();
47
47
  const effectiveConfig = mergeTestConfig(projectConfig, config);
48
48
 
49
+ // Debug: Log config
50
+ if (process.env.TESTIVAI_DEBUG === 'true') {
51
+ console.log('[TestivAI] Config:', {
52
+ projectConfig,
53
+ testConfig: config,
54
+ effectiveConfig
55
+ });
56
+ }
57
+
49
58
  const outputDir = path.join(process.cwd(), '.testivai', 'temp');
50
59
  await fs.ensureDir(outputDir);
51
60
 
@@ -54,17 +63,76 @@ export async function snapshot(
54
63
  const safeName = snapshotName.replace(/[^a-z0-9]/gi, '_').toLowerCase();
55
64
  const baseFilename = `${timestamp}_${safeName}`;
56
65
 
57
- // 1. Capture full-page screenshot using scroll-and-stitch approach
58
- // This is the same technique used by GoFullPage Chrome extension
66
+ // 1. Capture full-page screenshot
59
67
  const screenshotPath = path.join(outputDir, `${baseFilename}.png`);
60
68
 
61
- // Get viewport dimensions
62
- const viewport = page.viewportSize();
63
- const viewportWidth = viewport?.width || 1280;
64
- const viewportHeight = viewport?.height || 720;
65
-
66
- // Find the main scrollable container and get its dimensions
67
- const scrollableInfo = await page.evaluate(`
69
+ // Check if CDP approach is enabled
70
+ if (effectiveConfig.useCDP) {
71
+ // Use Chrome DevTools Protocol for full-page capture
72
+ if (process.env.TESTIVAI_DEBUG === 'true') {
73
+ console.log('[TestivAI] Using CDP approach for full-page screenshot');
74
+ }
75
+
76
+ try {
77
+ // Create a CDP session
78
+ const client = await page.context().newCDPSession(page);
79
+
80
+ // Enable Page domain
81
+ await client.send('Page.enable');
82
+
83
+ // Get layout metrics to determine full page size
84
+ const layoutMetrics = await client.send('Page.getLayoutMetrics');
85
+
86
+ // Calculate full page dimensions
87
+ const pageWidth = Math.ceil(layoutMetrics.contentSize.width);
88
+ const pageHeight = Math.ceil(layoutMetrics.contentSize.height);
89
+
90
+ if (process.env.TESTIVAI_DEBUG === 'true') {
91
+ console.log('[TestivAI] CDP Layout metrics:', {
92
+ pageWidth,
93
+ pageHeight,
94
+ viewportWidth: layoutMetrics.layoutViewport.clientWidth,
95
+ viewportHeight: layoutMetrics.layoutViewport.clientHeight
96
+ });
97
+ }
98
+
99
+ // Capture screenshot with captureBeyondViewport: true
100
+ const screenshot = await client.send('Page.captureScreenshot', {
101
+ format: 'png',
102
+ captureBeyondViewport: true,
103
+ clip: {
104
+ x: 0,
105
+ y: 0,
106
+ width: pageWidth,
107
+ height: pageHeight,
108
+ scale: 1
109
+ }
110
+ });
111
+
112
+ // Save the screenshot
113
+ await fs.writeFile(screenshotPath, Buffer.from(screenshot.data, 'base64'));
114
+
115
+ // Close CDP session
116
+ await client.detach();
117
+
118
+ } catch (error: any) {
119
+ console.error('[TestivAI] CDP screenshot failed:', error.message);
120
+ // Fallback to regular screenshot
121
+ await page.screenshot({ path: screenshotPath, fullPage: true });
122
+ }
123
+ } else {
124
+ // Use scroll-and-stitch approach (default)
125
+ if (process.env.TESTIVAI_DEBUG === 'true') {
126
+ console.log('[TestivAI] Using scroll-and-stitch approach for full-page screenshot');
127
+ }
128
+
129
+ // Get viewport dimensions
130
+ const viewport = page.viewportSize();
131
+ const viewportWidth = viewport?.width || 1280;
132
+ const viewportHeight = viewport?.height || 720;
133
+
134
+ // Find the main scrollable container and get its dimensions
135
+ const scrollableInfo = await page.evaluate(`
68
136
  (function() {
69
137
  var mainScrollable = null;
70
138
  var maxScrollHeight = 0;
@@ -213,6 +281,7 @@ export async function snapshot(
213
281
  } else {
214
282
  await page.evaluate(`window.scrollTo(0, ${scrollableInfo.scrollTop})`);
215
283
  }
284
+ }
216
285
  }
217
286
 
218
287
  // 2. Dump full-page DOM
package/src/types.ts CHANGED
@@ -141,6 +141,8 @@ export interface TestivAIConfig {
141
141
  performance?: Partial<PerformanceConfig>;
142
142
  /** Element selectors to capture (existing option) */
143
143
  selectors?: string[];
144
+ /** Use Chrome DevTools Protocol for full-page capture (default: false - uses scroll-and-stitch) */
145
+ useCDP?: boolean;
144
146
  }
145
147
 
146
148
  /**