@testivai/witness-playwright 1.0.0 → 1.1.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.
- package/__tests__/unit/reporter.spec.js +6 -3
- package/__tests__/unit/reporter.spec.ts +32 -3
- package/dist/config/loader.js +1 -1
- package/dist/reporter.js +4 -4
- package/dist/snapshot.js +30 -30
- package/dist/types.d.ts +4 -4
- package/package.json +1 -1
- package/src/config/loader.ts +1 -1
- package/src/reporter.ts +4 -4
- package/src/snapshot.ts +30 -30
- package/src/types.ts +4 -4
|
@@ -5,7 +5,7 @@ const reporter_1 = require("../../src/reporter");
|
|
|
5
5
|
// This prevents a type-checking conflict within the Jest environment.
|
|
6
6
|
jest.mock('../../src/reporter-types', () => ({}));
|
|
7
7
|
describe('TestivAIPlaywrightReporter', () => {
|
|
8
|
-
const OLD_ENV = process.env;
|
|
8
|
+
const OLD_ENV = { ...process.env };
|
|
9
9
|
beforeEach(() => {
|
|
10
10
|
jest.resetModules();
|
|
11
11
|
process.env = { ...OLD_ENV };
|
|
@@ -22,16 +22,19 @@ describe('TestivAIPlaywrightReporter', () => {
|
|
|
22
22
|
expect(reporter.options.apiKey).toBe('env-key');
|
|
23
23
|
});
|
|
24
24
|
test('should be disabled if API URL is not provided', async () => {
|
|
25
|
+
delete process.env.TESTIVAI_API_URL;
|
|
26
|
+
delete process.env.TESTIVAI_API_KEY;
|
|
25
27
|
const reporter = new reporter_1.TestivAIPlaywrightReporter();
|
|
26
28
|
// onBegin should set the apiUrl to undefined, disabling the reporter.
|
|
27
|
-
await reporter.onBegin({}, {});
|
|
29
|
+
await reporter.onBegin({}, { suites: [] });
|
|
28
30
|
expect(reporter.options.apiUrl).toBeUndefined();
|
|
29
31
|
});
|
|
30
32
|
test('should be disabled if API Key is not provided', async () => {
|
|
33
|
+
delete process.env.TESTIVAI_API_KEY;
|
|
31
34
|
process.env.TESTIVAI_API_URL = 'http://env.api';
|
|
32
35
|
const reporter = new reporter_1.TestivAIPlaywrightReporter();
|
|
33
36
|
// onBegin should set the apiUrl to undefined, disabling the reporter.
|
|
34
|
-
await reporter.onBegin({}, {});
|
|
37
|
+
await reporter.onBegin({}, { suites: [] });
|
|
35
38
|
expect(reporter.options.apiUrl).toBeUndefined();
|
|
36
39
|
});
|
|
37
40
|
});
|
|
@@ -5,7 +5,7 @@ import { TestivAIPlaywrightReporter } from '../../src/reporter';
|
|
|
5
5
|
jest.mock('../../src/reporter-types', () => ({}));
|
|
6
6
|
|
|
7
7
|
describe('TestivAIPlaywrightReporter', () => {
|
|
8
|
-
const OLD_ENV = process.env;
|
|
8
|
+
const OLD_ENV = { ...process.env };
|
|
9
9
|
|
|
10
10
|
beforeEach(() => {
|
|
11
11
|
jest.resetModules();
|
|
@@ -27,17 +27,46 @@ describe('TestivAIPlaywrightReporter', () => {
|
|
|
27
27
|
});
|
|
28
28
|
|
|
29
29
|
test('should be disabled if API URL is not provided', async () => {
|
|
30
|
+
delete process.env.TESTIVAI_API_URL;
|
|
31
|
+
delete process.env.TESTIVAI_API_KEY;
|
|
30
32
|
const reporter = new TestivAIPlaywrightReporter();
|
|
31
33
|
// onBegin should set the apiUrl to undefined, disabling the reporter.
|
|
32
|
-
await reporter.onBegin({} as any, {} as any);
|
|
34
|
+
await reporter.onBegin({} as any, { suites: [] } as any);
|
|
33
35
|
expect((reporter as any).options.apiUrl).toBeUndefined();
|
|
34
36
|
});
|
|
35
37
|
|
|
36
38
|
test('should be disabled if API Key is not provided', async () => {
|
|
39
|
+
delete process.env.TESTIVAI_API_KEY;
|
|
37
40
|
process.env.TESTIVAI_API_URL = 'http://env.api';
|
|
38
41
|
const reporter = new TestivAIPlaywrightReporter();
|
|
39
42
|
// onBegin should set the apiUrl to undefined, disabling the reporter.
|
|
40
|
-
await reporter.onBegin({} as any, {} as any);
|
|
43
|
+
await reporter.onBegin({} as any, { suites: [] } as any);
|
|
41
44
|
expect((reporter as any).options.apiUrl).toBeUndefined();
|
|
42
45
|
});
|
|
46
|
+
|
|
47
|
+
test('SnapshotPayload uses structure/styles keys (not dom/css)', () => {
|
|
48
|
+
// Verify the reporter module's SnapshotPayload type uses renamed fields
|
|
49
|
+
// by constructing a payload object matching the reporter's expected shape.
|
|
50
|
+
// This ensures the terminology rename is enforced in the reporter's output.
|
|
51
|
+
const payload = {
|
|
52
|
+
structure: { html: '<html></html>' },
|
|
53
|
+
styles: { computed_styles: {} },
|
|
54
|
+
layout: { x: 0, y: 0, width: 1024, height: 768, top: 0, left: 0, right: 1024, bottom: 768 },
|
|
55
|
+
timestamp: Date.now(),
|
|
56
|
+
testName: 'test',
|
|
57
|
+
snapshotName: 'snapshot',
|
|
58
|
+
url: 'http://localhost',
|
|
59
|
+
viewport: { width: 1024, height: 768 },
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
// New field names exist
|
|
63
|
+
expect(payload).toHaveProperty('structure');
|
|
64
|
+
expect(payload).toHaveProperty('styles');
|
|
65
|
+
expect(payload.structure).toHaveProperty('html');
|
|
66
|
+
|
|
67
|
+
// Old field names must NOT exist
|
|
68
|
+
expect(payload).not.toHaveProperty('dom');
|
|
69
|
+
expect(payload).not.toHaveProperty('css');
|
|
70
|
+
expect(payload).not.toHaveProperty('domAnalysis');
|
|
71
|
+
});
|
|
43
72
|
});
|
package/dist/config/loader.js
CHANGED
package/dist/reporter.js
CHANGED
|
@@ -124,7 +124,7 @@ class TestivAIPlaywrightReporter {
|
|
|
124
124
|
if (this.options.debug) {
|
|
125
125
|
console.log(`Testivai Reporter: [DEBUG] Found ${jsonFiles.length} snapshot(s) to process`);
|
|
126
126
|
}
|
|
127
|
-
// Build snapshots array matching
|
|
127
|
+
// Build snapshots array matching Witness SDK format
|
|
128
128
|
const snapshots = [];
|
|
129
129
|
for (const jsonFile of jsonFiles) {
|
|
130
130
|
const metadataPath = path.join(this.tempDir, jsonFile);
|
|
@@ -189,7 +189,7 @@ class TestivAIPlaywrightReporter {
|
|
|
189
189
|
};
|
|
190
190
|
snapshots.push(snapshotPayload);
|
|
191
191
|
}
|
|
192
|
-
// Build batch payload matching
|
|
192
|
+
// Build batch payload matching Witness SDK format
|
|
193
193
|
const batchPayload = {
|
|
194
194
|
git: this.gitInfo,
|
|
195
195
|
browser: this.browserInfo,
|
|
@@ -198,7 +198,7 @@ class TestivAIPlaywrightReporter {
|
|
|
198
198
|
runId: this.runId,
|
|
199
199
|
ci: this.ciInfo,
|
|
200
200
|
};
|
|
201
|
-
// Compress and upload (same as
|
|
201
|
+
// Compress and upload (same as Witness SDK)
|
|
202
202
|
const payloadJson = JSON.stringify(batchPayload);
|
|
203
203
|
const compressionResult = await this.compressionHelper.compress(payloadJson);
|
|
204
204
|
if (this.options.debug && compressionResult.compressionRatio > 0) {
|
|
@@ -209,7 +209,7 @@ class TestivAIPlaywrightReporter {
|
|
|
209
209
|
'Content-Type': 'application/json',
|
|
210
210
|
...compressionResult.headers,
|
|
211
211
|
};
|
|
212
|
-
// Upload to same endpoint as
|
|
212
|
+
// Upload to same endpoint as Witness SDK
|
|
213
213
|
const startBatchResponse = await axios_1.default.post(`${this.options.apiUrl}/api/v1/ingest/start-batch`, compressionResult.data, { headers });
|
|
214
214
|
const batchId = startBatchResponse.data.batch_id || startBatchResponse.data.batchId;
|
|
215
215
|
// Show success message (brief in normal mode, detailed in debug mode)
|
package/dist/snapshot.js
CHANGED
|
@@ -92,7 +92,7 @@ async function snapshot(page, testInfo, name, config) {
|
|
|
92
92
|
// 1. Capture full-page screenshot
|
|
93
93
|
const screenshotPath = path.join(outputDir, `${baseFilename}.png`);
|
|
94
94
|
// Check if scroll-and-stitch is explicitly requested (backup method)
|
|
95
|
-
if (effectiveConfig.
|
|
95
|
+
if (effectiveConfig.useBrowserCapture === false) {
|
|
96
96
|
// Use scroll-and-stitch approach (backup method)
|
|
97
97
|
if (process.env.TESTIVAI_DEBUG === 'true') {
|
|
98
98
|
console.log('[TestivAI] Using scroll-and-stitch approach (backup method)');
|
|
@@ -236,12 +236,12 @@ async function snapshot(page, testInfo, name, config) {
|
|
|
236
236
|
}
|
|
237
237
|
}
|
|
238
238
|
else {
|
|
239
|
-
// Use
|
|
239
|
+
// Use browser capture approach (default)
|
|
240
240
|
if (process.env.TESTIVAI_DEBUG === 'true') {
|
|
241
|
-
console.log('[TestivAI] Using
|
|
241
|
+
console.log('[TestivAI] Using browser capture approach (default) for full-page screenshot');
|
|
242
242
|
}
|
|
243
243
|
try {
|
|
244
|
-
// Create a
|
|
244
|
+
// Create a browser session
|
|
245
245
|
const client = await page.context().newCDPSession(page);
|
|
246
246
|
// Enable Page domain
|
|
247
247
|
await client.send('Page.enable');
|
|
@@ -269,7 +269,7 @@ async function snapshot(page, testInfo, name, config) {
|
|
|
269
269
|
const pageWidth = Math.ceil(layoutMetrics.contentSize.width);
|
|
270
270
|
const pageHeight = Math.ceil(layoutMetrics.contentSize.height);
|
|
271
271
|
if (process.env.TESTIVAI_DEBUG === 'true') {
|
|
272
|
-
console.log('[TestivAI]
|
|
272
|
+
console.log('[TestivAI] Browser layout metrics:', {
|
|
273
273
|
pageWidth,
|
|
274
274
|
pageHeight,
|
|
275
275
|
viewportWidth: layoutMetrics.layoutViewport.clientWidth,
|
|
@@ -298,11 +298,11 @@ async function snapshot(page, testInfo, name, config) {
|
|
|
298
298
|
styleTags[styleTags.length - 1].remove();
|
|
299
299
|
}
|
|
300
300
|
`);
|
|
301
|
-
// Close
|
|
301
|
+
// Close browser session
|
|
302
302
|
await client.detach();
|
|
303
303
|
}
|
|
304
304
|
catch (error) {
|
|
305
|
-
console.error('[TestivAI]
|
|
305
|
+
console.error('[TestivAI] Browser screenshot failed:', error.message);
|
|
306
306
|
// Fallback to regular screenshot
|
|
307
307
|
await page.screenshot({ path: screenshotPath, fullPage: true });
|
|
308
308
|
}
|
|
@@ -312,14 +312,14 @@ async function snapshot(page, testInfo, name, config) {
|
|
|
312
312
|
const structurePath = path.join(outputDir, `${baseFilename}.html`);
|
|
313
313
|
const htmlContent = await page.content();
|
|
314
314
|
await fs.writeFile(structurePath, htmlContent);
|
|
315
|
-
// 2.5. Capture computed styles using
|
|
315
|
+
// 2.5. Capture computed styles using browser session
|
|
316
316
|
// @renamed: cssPath → stylesPath (IP protection)
|
|
317
317
|
const stylesPath = path.join(outputDir, `${baseFilename}.css.json`);
|
|
318
318
|
try {
|
|
319
|
-
const
|
|
319
|
+
const browserSession = await page.context().newCDPSession(page);
|
|
320
320
|
// Enable DOM and CSS domains
|
|
321
|
-
await
|
|
322
|
-
await
|
|
321
|
+
await browserSession.send('DOM.enable');
|
|
322
|
+
await browserSession.send('CSS.enable');
|
|
323
323
|
// Get all elements and their computed styles
|
|
324
324
|
const computedStyles = {};
|
|
325
325
|
// Visual properties we care about
|
|
@@ -332,7 +332,7 @@ async function snapshot(page, testInfo, name, config) {
|
|
|
332
332
|
'transform', 'opacity', 'visibility', 'z-index'
|
|
333
333
|
];
|
|
334
334
|
// Execute script to get all elements with unique identifiers
|
|
335
|
-
const elementsData = await
|
|
335
|
+
const elementsData = await browserSession.send('Runtime.evaluate', {
|
|
336
336
|
expression: `
|
|
337
337
|
(function() {
|
|
338
338
|
// Helper to get stable CSS selector path for an element
|
|
@@ -399,7 +399,7 @@ async function snapshot(page, testInfo, name, config) {
|
|
|
399
399
|
for (let i = 0; i < sampleSize; i++) {
|
|
400
400
|
const element = elements[i];
|
|
401
401
|
try {
|
|
402
|
-
const styleResult = await
|
|
402
|
+
const styleResult = await browserSession.send('Runtime.evaluate', {
|
|
403
403
|
expression: `
|
|
404
404
|
(function() {
|
|
405
405
|
const el = document.querySelectorAll('*')[${element.index}];
|
|
@@ -435,9 +435,9 @@ async function snapshot(page, testInfo, name, config) {
|
|
|
435
435
|
}
|
|
436
436
|
}
|
|
437
437
|
// Disable domains and close session
|
|
438
|
-
await
|
|
439
|
-
await
|
|
440
|
-
await
|
|
438
|
+
await browserSession.send('CSS.disable');
|
|
439
|
+
await browserSession.send('DOM.disable');
|
|
440
|
+
await browserSession.detach();
|
|
441
441
|
// Save computed styles to file
|
|
442
442
|
await fs.writeJson(stylesPath, {
|
|
443
443
|
computed_styles: computedStyles,
|
|
@@ -449,7 +449,7 @@ async function snapshot(page, testInfo, name, config) {
|
|
|
449
449
|
}
|
|
450
450
|
}
|
|
451
451
|
catch (error) {
|
|
452
|
-
console.warn('[TestivAI] Failed to capture CSS via
|
|
452
|
+
console.warn('[TestivAI] Failed to capture CSS via browser session:', error);
|
|
453
453
|
// Continue without CSS data
|
|
454
454
|
}
|
|
455
455
|
// 3. Extract bounding boxes for requested selectors
|
|
@@ -468,21 +468,21 @@ async function snapshot(page, testInfo, name, config) {
|
|
|
468
468
|
};
|
|
469
469
|
}
|
|
470
470
|
}
|
|
471
|
-
// 4. Capture performance metrics using
|
|
471
|
+
// 4. Capture performance metrics using browser session (if enabled)
|
|
472
472
|
let performanceMetrics = undefined;
|
|
473
473
|
const metricsEnabled = effectiveConfig.performanceMetrics?.enabled ?? true; // Default: enabled
|
|
474
474
|
if (metricsEnabled) {
|
|
475
475
|
try {
|
|
476
|
-
// Get
|
|
477
|
-
const
|
|
476
|
+
// Get browser session from Playwright page
|
|
477
|
+
const browserSession = await page.context().newCDPSession(page);
|
|
478
478
|
// Enable Performance domain
|
|
479
|
-
await
|
|
480
|
-
// Get
|
|
481
|
-
const
|
|
479
|
+
await browserSession.send('Performance.enable');
|
|
480
|
+
// Get browser performance metrics
|
|
481
|
+
const browserMetrics = await browserSession.send('Performance.getMetrics');
|
|
482
482
|
// Convert metrics array to object
|
|
483
|
-
const
|
|
484
|
-
|
|
485
|
-
|
|
483
|
+
const browserMetricsObj = {};
|
|
484
|
+
browserMetrics.metrics.forEach((metric) => {
|
|
485
|
+
browserMetricsObj[metric.name] = metric.value;
|
|
486
486
|
});
|
|
487
487
|
// Get navigation timing and Web Vitals via page.evaluate
|
|
488
488
|
const timingData = await page.evaluate(() => {
|
|
@@ -555,11 +555,11 @@ async function snapshot(page, testInfo, name, config) {
|
|
|
555
555
|
};
|
|
556
556
|
});
|
|
557
557
|
// Disable Performance domain
|
|
558
|
-
await
|
|
559
|
-
await
|
|
560
|
-
// Structure identical to
|
|
558
|
+
await browserSession.send('Performance.disable');
|
|
559
|
+
await browserSession.detach();
|
|
560
|
+
// Structure identical to Witness SDK
|
|
561
561
|
performanceMetrics = {
|
|
562
|
-
cdp:
|
|
562
|
+
cdp: browserMetricsObj,
|
|
563
563
|
timing: timingData,
|
|
564
564
|
timestamp: Date.now()
|
|
565
565
|
};
|
package/dist/types.d.ts
CHANGED
|
@@ -115,7 +115,7 @@ export interface StructureChange {
|
|
|
115
115
|
}
|
|
116
116
|
/**
|
|
117
117
|
* Performance timing metrics
|
|
118
|
-
* @deprecated Use performanceMetrics with
|
|
118
|
+
* @deprecated Use performanceMetrics with browser Performance.getMetrics instead
|
|
119
119
|
*/
|
|
120
120
|
export interface PerformanceTimings {
|
|
121
121
|
/** Navigation start time */
|
|
@@ -137,7 +137,7 @@ export interface PerformanceTimings {
|
|
|
137
137
|
}
|
|
138
138
|
/**
|
|
139
139
|
* Lighthouse performance results
|
|
140
|
-
* @deprecated Lighthouse has been removed. Use performanceMetrics with
|
|
140
|
+
* @deprecated Lighthouse has been removed. Use performanceMetrics with browser Performance.getMetrics instead
|
|
141
141
|
*/
|
|
142
142
|
export interface LighthouseResults {
|
|
143
143
|
/** Performance score (0-100) */
|
|
@@ -214,8 +214,8 @@ export interface TestivAIConfig {
|
|
|
214
214
|
structure?: Partial<StructureAnalysisConfig>;
|
|
215
215
|
/** Element selectors to capture (existing option) */
|
|
216
216
|
selectors?: string[];
|
|
217
|
-
/** Use
|
|
218
|
-
|
|
217
|
+
/** Use browser capture for full-page screenshots (default: true, set to false for scroll-and-stitch) */
|
|
218
|
+
useBrowserCapture?: boolean;
|
|
219
219
|
}
|
|
220
220
|
/**
|
|
221
221
|
* Layout/Bounding box data for an element
|
package/package.json
CHANGED
package/src/config/loader.ts
CHANGED
package/src/reporter.ts
CHANGED
|
@@ -108,7 +108,7 @@ export class TestivAIPlaywrightReporter implements Reporter {
|
|
|
108
108
|
console.log(`Testivai Reporter: [DEBUG] Found ${jsonFiles.length} snapshot(s) to process`);
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
// Build snapshots array matching
|
|
111
|
+
// Build snapshots array matching Witness SDK format
|
|
112
112
|
const snapshots: SnapshotPayload[] = [];
|
|
113
113
|
|
|
114
114
|
for (const jsonFile of jsonFiles) {
|
|
@@ -180,7 +180,7 @@ export class TestivAIPlaywrightReporter implements Reporter {
|
|
|
180
180
|
snapshots.push(snapshotPayload);
|
|
181
181
|
}
|
|
182
182
|
|
|
183
|
-
// Build batch payload matching
|
|
183
|
+
// Build batch payload matching Witness SDK format
|
|
184
184
|
const batchPayload: Omit<BatchPayload, 'batchId'> = {
|
|
185
185
|
git: this.gitInfo!,
|
|
186
186
|
browser: this.browserInfo!,
|
|
@@ -190,7 +190,7 @@ export class TestivAIPlaywrightReporter implements Reporter {
|
|
|
190
190
|
ci: this.ciInfo,
|
|
191
191
|
};
|
|
192
192
|
|
|
193
|
-
// Compress and upload (same as
|
|
193
|
+
// Compress and upload (same as Witness SDK)
|
|
194
194
|
const payloadJson = JSON.stringify(batchPayload);
|
|
195
195
|
const compressionResult = await this.compressionHelper.compress(payloadJson);
|
|
196
196
|
|
|
@@ -204,7 +204,7 @@ export class TestivAIPlaywrightReporter implements Reporter {
|
|
|
204
204
|
...compressionResult.headers,
|
|
205
205
|
};
|
|
206
206
|
|
|
207
|
-
// Upload to same endpoint as
|
|
207
|
+
// Upload to same endpoint as Witness SDK
|
|
208
208
|
const startBatchResponse = await axios.post(
|
|
209
209
|
`${this.options.apiUrl}/api/v1/ingest/start-batch`,
|
|
210
210
|
compressionResult.data,
|
package/src/snapshot.ts
CHANGED
|
@@ -67,7 +67,7 @@ export async function snapshot(
|
|
|
67
67
|
const screenshotPath = path.join(outputDir, `${baseFilename}.png`);
|
|
68
68
|
|
|
69
69
|
// Check if scroll-and-stitch is explicitly requested (backup method)
|
|
70
|
-
if (effectiveConfig.
|
|
70
|
+
if (effectiveConfig.useBrowserCapture === false) {
|
|
71
71
|
// Use scroll-and-stitch approach (backup method)
|
|
72
72
|
if (process.env.TESTIVAI_DEBUG === 'true') {
|
|
73
73
|
console.log('[TestivAI] Using scroll-and-stitch approach (backup method)');
|
|
@@ -230,13 +230,13 @@ export async function snapshot(
|
|
|
230
230
|
}
|
|
231
231
|
}
|
|
232
232
|
} else {
|
|
233
|
-
// Use
|
|
233
|
+
// Use browser capture approach (default)
|
|
234
234
|
if (process.env.TESTIVAI_DEBUG === 'true') {
|
|
235
|
-
console.log('[TestivAI] Using
|
|
235
|
+
console.log('[TestivAI] Using browser capture approach (default) for full-page screenshot');
|
|
236
236
|
}
|
|
237
237
|
|
|
238
238
|
try {
|
|
239
|
-
// Create a
|
|
239
|
+
// Create a browser session
|
|
240
240
|
const client = await page.context().newCDPSession(page);
|
|
241
241
|
|
|
242
242
|
// Enable Page domain
|
|
@@ -270,7 +270,7 @@ export async function snapshot(
|
|
|
270
270
|
const pageHeight = Math.ceil(layoutMetrics.contentSize.height);
|
|
271
271
|
|
|
272
272
|
if (process.env.TESTIVAI_DEBUG === 'true') {
|
|
273
|
-
console.log('[TestivAI]
|
|
273
|
+
console.log('[TestivAI] Browser layout metrics:', {
|
|
274
274
|
pageWidth,
|
|
275
275
|
pageHeight,
|
|
276
276
|
viewportWidth: layoutMetrics.layoutViewport.clientWidth,
|
|
@@ -303,11 +303,11 @@ export async function snapshot(
|
|
|
303
303
|
}
|
|
304
304
|
`);
|
|
305
305
|
|
|
306
|
-
// Close
|
|
306
|
+
// Close browser session
|
|
307
307
|
await client.detach();
|
|
308
308
|
|
|
309
309
|
} catch (error: any) {
|
|
310
|
-
console.error('[TestivAI]
|
|
310
|
+
console.error('[TestivAI] Browser screenshot failed:', error.message);
|
|
311
311
|
// Fallback to regular screenshot
|
|
312
312
|
await page.screenshot({ path: screenshotPath, fullPage: true });
|
|
313
313
|
}
|
|
@@ -319,15 +319,15 @@ export async function snapshot(
|
|
|
319
319
|
const htmlContent = await page.content();
|
|
320
320
|
await fs.writeFile(structurePath, htmlContent);
|
|
321
321
|
|
|
322
|
-
// 2.5. Capture computed styles using
|
|
322
|
+
// 2.5. Capture computed styles using browser session
|
|
323
323
|
// @renamed: cssPath → stylesPath (IP protection)
|
|
324
324
|
const stylesPath = path.join(outputDir, `${baseFilename}.css.json`);
|
|
325
325
|
try {
|
|
326
|
-
const
|
|
326
|
+
const browserSession = await page.context().newCDPSession(page);
|
|
327
327
|
|
|
328
328
|
// Enable DOM and CSS domains
|
|
329
|
-
await
|
|
330
|
-
await
|
|
329
|
+
await browserSession.send('DOM.enable');
|
|
330
|
+
await browserSession.send('CSS.enable');
|
|
331
331
|
|
|
332
332
|
// Get all elements and their computed styles
|
|
333
333
|
const computedStyles: Record<string, Record<string, string>> = {};
|
|
@@ -343,7 +343,7 @@ export async function snapshot(
|
|
|
343
343
|
];
|
|
344
344
|
|
|
345
345
|
// Execute script to get all elements with unique identifiers
|
|
346
|
-
const elementsData = await
|
|
346
|
+
const elementsData = await browserSession.send('Runtime.evaluate', {
|
|
347
347
|
expression: `
|
|
348
348
|
(function() {
|
|
349
349
|
// Helper to get stable CSS selector path for an element
|
|
@@ -412,7 +412,7 @@ export async function snapshot(
|
|
|
412
412
|
for (let i = 0; i < sampleSize; i++) {
|
|
413
413
|
const element = elements[i];
|
|
414
414
|
try {
|
|
415
|
-
const styleResult = await
|
|
415
|
+
const styleResult = await browserSession.send('Runtime.evaluate', {
|
|
416
416
|
expression: `
|
|
417
417
|
(function() {
|
|
418
418
|
const el = document.querySelectorAll('*')[${element.index}];
|
|
@@ -449,9 +449,9 @@ export async function snapshot(
|
|
|
449
449
|
}
|
|
450
450
|
|
|
451
451
|
// Disable domains and close session
|
|
452
|
-
await
|
|
453
|
-
await
|
|
454
|
-
await
|
|
452
|
+
await browserSession.send('CSS.disable');
|
|
453
|
+
await browserSession.send('DOM.disable');
|
|
454
|
+
await browserSession.detach();
|
|
455
455
|
|
|
456
456
|
// Save computed styles to file
|
|
457
457
|
await fs.writeJson(stylesPath, {
|
|
@@ -464,7 +464,7 @@ export async function snapshot(
|
|
|
464
464
|
console.log(`[TestivAI] Captured ${Object.keys(computedStyles).length} element styles`);
|
|
465
465
|
}
|
|
466
466
|
} catch (error) {
|
|
467
|
-
console.warn('[TestivAI] Failed to capture CSS via
|
|
467
|
+
console.warn('[TestivAI] Failed to capture CSS via browser session:', error);
|
|
468
468
|
// Continue without CSS data
|
|
469
469
|
}
|
|
470
470
|
|
|
@@ -486,26 +486,26 @@ export async function snapshot(
|
|
|
486
486
|
}
|
|
487
487
|
}
|
|
488
488
|
|
|
489
|
-
// 4. Capture performance metrics using
|
|
489
|
+
// 4. Capture performance metrics using browser session (if enabled)
|
|
490
490
|
let performanceMetrics: any = undefined;
|
|
491
491
|
|
|
492
492
|
const metricsEnabled = effectiveConfig.performanceMetrics?.enabled ?? true; // Default: enabled
|
|
493
493
|
|
|
494
494
|
if (metricsEnabled) {
|
|
495
495
|
try {
|
|
496
|
-
// Get
|
|
497
|
-
const
|
|
496
|
+
// Get browser session from Playwright page
|
|
497
|
+
const browserSession = await page.context().newCDPSession(page);
|
|
498
498
|
|
|
499
499
|
// Enable Performance domain
|
|
500
|
-
await
|
|
500
|
+
await browserSession.send('Performance.enable');
|
|
501
501
|
|
|
502
|
-
// Get
|
|
503
|
-
const
|
|
502
|
+
// Get browser performance metrics
|
|
503
|
+
const browserMetrics = await browserSession.send('Performance.getMetrics');
|
|
504
504
|
|
|
505
505
|
// Convert metrics array to object
|
|
506
|
-
const
|
|
507
|
-
|
|
508
|
-
|
|
506
|
+
const browserMetricsObj: any = {};
|
|
507
|
+
browserMetrics.metrics.forEach((metric: any) => {
|
|
508
|
+
browserMetricsObj[metric.name] = metric.value;
|
|
509
509
|
});
|
|
510
510
|
|
|
511
511
|
// Get navigation timing and Web Vitals via page.evaluate
|
|
@@ -583,12 +583,12 @@ export async function snapshot(
|
|
|
583
583
|
});
|
|
584
584
|
|
|
585
585
|
// Disable Performance domain
|
|
586
|
-
await
|
|
587
|
-
await
|
|
586
|
+
await browserSession.send('Performance.disable');
|
|
587
|
+
await browserSession.detach();
|
|
588
588
|
|
|
589
|
-
// Structure identical to
|
|
589
|
+
// Structure identical to Witness SDK
|
|
590
590
|
performanceMetrics = {
|
|
591
|
-
cdp:
|
|
591
|
+
cdp: browserMetricsObj,
|
|
592
592
|
timing: timingData,
|
|
593
593
|
timestamp: Date.now()
|
|
594
594
|
};
|
package/src/types.ts
CHANGED
|
@@ -122,7 +122,7 @@ export interface StructureChange {
|
|
|
122
122
|
|
|
123
123
|
/**
|
|
124
124
|
* Performance timing metrics
|
|
125
|
-
* @deprecated Use performanceMetrics with
|
|
125
|
+
* @deprecated Use performanceMetrics with browser Performance.getMetrics instead
|
|
126
126
|
*/
|
|
127
127
|
export interface PerformanceTimings {
|
|
128
128
|
/** Navigation start time */
|
|
@@ -145,7 +145,7 @@ export interface PerformanceTimings {
|
|
|
145
145
|
|
|
146
146
|
/**
|
|
147
147
|
* Lighthouse performance results
|
|
148
|
-
* @deprecated Lighthouse has been removed. Use performanceMetrics with
|
|
148
|
+
* @deprecated Lighthouse has been removed. Use performanceMetrics with browser Performance.getMetrics instead
|
|
149
149
|
*/
|
|
150
150
|
export interface LighthouseResults {
|
|
151
151
|
/** Performance score (0-100) */
|
|
@@ -225,8 +225,8 @@ export interface TestivAIConfig {
|
|
|
225
225
|
structure?: Partial<StructureAnalysisConfig>;
|
|
226
226
|
/** Element selectors to capture (existing option) */
|
|
227
227
|
selectors?: string[];
|
|
228
|
-
/** Use
|
|
229
|
-
|
|
228
|
+
/** Use browser capture for full-page screenshots (default: true, set to false for scroll-and-stitch) */
|
|
229
|
+
useBrowserCapture?: boolean;
|
|
230
230
|
}
|
|
231
231
|
|
|
232
232
|
/**
|