@testivai/witness-playwright 0.1.7 → 0.1.8

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