@testivai/witness-playwright 0.1.4 → 0.1.6
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 +97 -9
- package/package.json +1 -1
- package/progress.md +36 -1
- package/src/snapshot.ts +100 -9
package/dist/snapshot.js
CHANGED
|
@@ -79,17 +79,105 @@ async function snapshot(page, testInfo, name, config) {
|
|
|
79
79
|
const baseFilename = `${timestamp}_${safeName}`;
|
|
80
80
|
// 1. Capture full-page screenshot
|
|
81
81
|
const screenshotPath = path.join(outputDir, `${baseFilename}.png`);
|
|
82
|
-
//
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
82
|
+
// Temporarily disable overflow constraints and fixed heights to enable full-page capture
|
|
83
|
+
// This handles common SPA patterns like h-screen overflow-hidden with internal scrolling
|
|
84
|
+
/* eslint-disable no-eval */
|
|
85
|
+
await page.evaluate(`
|
|
86
|
+
window.__testivaiOriginalStyles = [];
|
|
87
|
+
|
|
88
|
+
// Process all elements to find and fix viewport-constraining styles
|
|
89
|
+
document.querySelectorAll('*').forEach(function(el) {
|
|
90
|
+
var computed = window.getComputedStyle(el);
|
|
91
|
+
|
|
92
|
+
// Check for overflow constraints
|
|
93
|
+
var hasOverflowConstraint =
|
|
94
|
+
computed.overflow === 'auto' ||
|
|
95
|
+
computed.overflow === 'scroll' ||
|
|
96
|
+
computed.overflow === 'hidden' ||
|
|
97
|
+
computed.overflowY === 'auto' ||
|
|
98
|
+
computed.overflowY === 'scroll' ||
|
|
99
|
+
computed.overflowY === 'hidden';
|
|
100
|
+
|
|
101
|
+
// Check for fixed height constraints (100vh, 100%, or specific pixel values on containers)
|
|
102
|
+
var hasHeightConstraint =
|
|
103
|
+
computed.height === '100vh' ||
|
|
104
|
+
(computed.height.endsWith('px') && el.scrollHeight > el.clientHeight) ||
|
|
105
|
+
(computed.maxHeight && computed.maxHeight !== 'none');
|
|
106
|
+
|
|
107
|
+
if (hasOverflowConstraint || hasHeightConstraint) {
|
|
108
|
+
window.__testivaiOriginalStyles.push({
|
|
109
|
+
element: el,
|
|
110
|
+
overflow: el.style.overflow,
|
|
111
|
+
overflowY: el.style.overflowY,
|
|
112
|
+
height: el.style.height,
|
|
113
|
+
maxHeight: el.style.maxHeight,
|
|
114
|
+
minHeight: el.style.minHeight
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
// For scrollable containers, expand to full scroll height
|
|
118
|
+
if (hasOverflowConstraint && el.scrollHeight > el.clientHeight) {
|
|
119
|
+
el.style.height = el.scrollHeight + 'px';
|
|
120
|
+
el.style.minHeight = el.scrollHeight + 'px';
|
|
121
|
+
} else if (hasHeightConstraint) {
|
|
122
|
+
el.style.height = 'auto';
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Remove overflow constraints
|
|
126
|
+
el.style.overflow = 'visible';
|
|
127
|
+
el.style.overflowY = 'visible';
|
|
128
|
+
el.style.maxHeight = 'none';
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
// Also handle html and body elements specifically
|
|
133
|
+
var html = document.documentElement;
|
|
134
|
+
var body = document.body;
|
|
135
|
+
|
|
136
|
+
window.__testivaiRootStyles = {
|
|
137
|
+
html: {
|
|
138
|
+
overflow: html.style.overflow,
|
|
139
|
+
height: html.style.height
|
|
140
|
+
},
|
|
141
|
+
body: {
|
|
142
|
+
overflow: body.style.overflow,
|
|
143
|
+
height: body.style.height
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
html.style.overflow = 'visible';
|
|
148
|
+
html.style.height = 'auto';
|
|
149
|
+
body.style.overflow = 'visible';
|
|
150
|
+
body.style.height = 'auto';
|
|
151
|
+
`);
|
|
152
|
+
// Wait for layout to stabilize after style changes
|
|
153
|
+
await page.waitForTimeout(300);
|
|
154
|
+
// Take full-page screenshot
|
|
155
|
+
await page.screenshot({ path: screenshotPath, fullPage: true });
|
|
156
|
+
// Restore original styles
|
|
157
|
+
await page.evaluate(`
|
|
158
|
+
// Restore element styles
|
|
159
|
+
if (window.__testivaiOriginalStyles) {
|
|
160
|
+
window.__testivaiOriginalStyles.forEach(function(item) {
|
|
161
|
+
item.element.style.overflow = item.overflow;
|
|
162
|
+
item.element.style.overflowY = item.overflowY;
|
|
163
|
+
item.element.style.height = item.height;
|
|
164
|
+
item.element.style.maxHeight = item.maxHeight;
|
|
165
|
+
item.element.style.minHeight = item.minHeight;
|
|
166
|
+
});
|
|
167
|
+
delete window.__testivaiOriginalStyles;
|
|
88
168
|
}
|
|
89
|
-
|
|
90
|
-
|
|
169
|
+
|
|
170
|
+
// Restore root element styles
|
|
171
|
+
if (window.__testivaiRootStyles) {
|
|
172
|
+
var html = document.documentElement;
|
|
173
|
+
var body = document.body;
|
|
174
|
+
html.style.overflow = window.__testivaiRootStyles.html.overflow;
|
|
175
|
+
html.style.height = window.__testivaiRootStyles.html.height;
|
|
176
|
+
body.style.overflow = window.__testivaiRootStyles.body.overflow;
|
|
177
|
+
body.style.height = window.__testivaiRootStyles.body.height;
|
|
178
|
+
delete window.__testivaiRootStyles;
|
|
91
179
|
}
|
|
92
|
-
|
|
180
|
+
`);
|
|
93
181
|
// 2. Dump full-page DOM
|
|
94
182
|
const domPath = path.join(outputDir, `${baseFilename}.html`);
|
|
95
183
|
const htmlContent = await page.content();
|
package/package.json
CHANGED
package/progress.md
CHANGED
|
@@ -745,12 +745,47 @@ The Playwright SDK provides two main components that are production-ready:
|
|
|
745
745
|
|
|
746
746
|
---
|
|
747
747
|
|
|
748
|
+
## Full-Page Screenshot Fix (January 10, 2026)
|
|
749
|
+
|
|
750
|
+
### Issue: Windowed Screenshots Instead of Full-Page
|
|
751
|
+
|
|
752
|
+
**Problem**: Screenshots were capturing only the visible viewport instead of the full scrollable page content.
|
|
753
|
+
|
|
754
|
+
**Root Cause**: Modern SPA frameworks (React, Vue, Angular) commonly use fixed viewport layouts like:
|
|
755
|
+
```css
|
|
756
|
+
.container {
|
|
757
|
+
height: 100vh; /* Fixed to viewport height */
|
|
758
|
+
overflow: hidden; /* Prevents document scrolling */
|
|
759
|
+
}
|
|
760
|
+
.content {
|
|
761
|
+
overflow-y: auto; /* Internal scrolling container */
|
|
762
|
+
}
|
|
763
|
+
```
|
|
764
|
+
|
|
765
|
+
This pattern (e.g., TailwindCSS `h-screen overflow-hidden`) creates a scrollable container **inside** a fixed-height viewport. Playwright's `fullPage: true` captures the document height, but when the document is constrained to viewport height with internal scrolling, it only captures what's visible.
|
|
766
|
+
|
|
767
|
+
**Solution**: Enhanced the snapshot function to:
|
|
768
|
+
1. Detect and temporarily remove `overflow: hidden` (not just `auto`/`scroll`)
|
|
769
|
+
2. Detect and remove `height: 100vh` constraints
|
|
770
|
+
3. Remove `maxHeight` constraints that limit container expansion
|
|
771
|
+
4. Explicitly handle `html` and `body` element styles
|
|
772
|
+
5. Wait for layout to stabilize (300ms) before capturing
|
|
773
|
+
6. Restore all original styles after capture
|
|
774
|
+
|
|
775
|
+
**Files Modified**:
|
|
776
|
+
- `src/snapshot.ts` - Enhanced full-page capture logic
|
|
777
|
+
|
|
778
|
+
**Testing**: Run visual tests to verify full-page screenshots are now captured correctly.
|
|
779
|
+
|
|
780
|
+
---
|
|
781
|
+
|
|
748
782
|
**Last Updated**: January 10, 2026
|
|
749
783
|
**Status**: 🎉 PUBLISHED TO NPM ✅ - Publicly available
|
|
750
|
-
**NPM Package**: @testivai/witness-playwright@0.1.
|
|
784
|
+
**NPM Package**: @testivai/witness-playwright@0.1.5
|
|
751
785
|
**Core Features**: Evidence capture, batch upload, and performance monitoring fully functional
|
|
752
786
|
**Configuration**: ✅ COMPLETE - End-to-end flow working
|
|
753
787
|
**Performance Metrics**: ✅ COMPLETE - Basic timing + optional Lighthouse
|
|
788
|
+
**Full-Page Screenshots**: ✅ FIXED - Handles SPA fixed viewport layouts
|
|
754
789
|
**API Key Format**: tstvai-{secure-random-string}
|
|
755
790
|
**Known Issues**: Minor UX improvements (retry logic, progress reporting)
|
|
756
791
|
**Blocker**: None - All critical features implemented
|
package/src/snapshot.ts
CHANGED
|
@@ -56,17 +56,108 @@ export async function snapshot(
|
|
|
56
56
|
// 1. Capture full-page screenshot
|
|
57
57
|
const screenshotPath = path.join(outputDir, `${baseFilename}.png`);
|
|
58
58
|
|
|
59
|
-
//
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
59
|
+
// Temporarily disable overflow constraints and fixed heights to enable full-page capture
|
|
60
|
+
// This handles common SPA patterns like h-screen overflow-hidden with internal scrolling
|
|
61
|
+
/* eslint-disable no-eval */
|
|
62
|
+
await page.evaluate(`
|
|
63
|
+
window.__testivaiOriginalStyles = [];
|
|
64
|
+
|
|
65
|
+
// Process all elements to find and fix viewport-constraining styles
|
|
66
|
+
document.querySelectorAll('*').forEach(function(el) {
|
|
67
|
+
var computed = window.getComputedStyle(el);
|
|
68
|
+
|
|
69
|
+
// Check for overflow constraints
|
|
70
|
+
var hasOverflowConstraint =
|
|
71
|
+
computed.overflow === 'auto' ||
|
|
72
|
+
computed.overflow === 'scroll' ||
|
|
73
|
+
computed.overflow === 'hidden' ||
|
|
74
|
+
computed.overflowY === 'auto' ||
|
|
75
|
+
computed.overflowY === 'scroll' ||
|
|
76
|
+
computed.overflowY === 'hidden';
|
|
77
|
+
|
|
78
|
+
// Check for fixed height constraints (100vh, 100%, or specific pixel values on containers)
|
|
79
|
+
var hasHeightConstraint =
|
|
80
|
+
computed.height === '100vh' ||
|
|
81
|
+
(computed.height.endsWith('px') && el.scrollHeight > el.clientHeight) ||
|
|
82
|
+
(computed.maxHeight && computed.maxHeight !== 'none');
|
|
83
|
+
|
|
84
|
+
if (hasOverflowConstraint || hasHeightConstraint) {
|
|
85
|
+
window.__testivaiOriginalStyles.push({
|
|
86
|
+
element: el,
|
|
87
|
+
overflow: el.style.overflow,
|
|
88
|
+
overflowY: el.style.overflowY,
|
|
89
|
+
height: el.style.height,
|
|
90
|
+
maxHeight: el.style.maxHeight,
|
|
91
|
+
minHeight: el.style.minHeight
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// For scrollable containers, expand to full scroll height
|
|
95
|
+
if (hasOverflowConstraint && el.scrollHeight > el.clientHeight) {
|
|
96
|
+
el.style.height = el.scrollHeight + 'px';
|
|
97
|
+
el.style.minHeight = el.scrollHeight + 'px';
|
|
98
|
+
} else if (hasHeightConstraint) {
|
|
99
|
+
el.style.height = 'auto';
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Remove overflow constraints
|
|
103
|
+
el.style.overflow = 'visible';
|
|
104
|
+
el.style.overflowY = 'visible';
|
|
105
|
+
el.style.maxHeight = 'none';
|
|
106
|
+
}
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
// Also handle html and body elements specifically
|
|
110
|
+
var html = document.documentElement;
|
|
111
|
+
var body = document.body;
|
|
112
|
+
|
|
113
|
+
window.__testivaiRootStyles = {
|
|
114
|
+
html: {
|
|
115
|
+
overflow: html.style.overflow,
|
|
116
|
+
height: html.style.height
|
|
117
|
+
},
|
|
118
|
+
body: {
|
|
119
|
+
overflow: body.style.overflow,
|
|
120
|
+
height: body.style.height
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
html.style.overflow = 'visible';
|
|
125
|
+
html.style.height = 'auto';
|
|
126
|
+
body.style.overflow = 'visible';
|
|
127
|
+
body.style.height = 'auto';
|
|
128
|
+
`);
|
|
129
|
+
|
|
130
|
+
// Wait for layout to stabilize after style changes
|
|
131
|
+
await page.waitForTimeout(300);
|
|
68
132
|
|
|
133
|
+
// Take full-page screenshot
|
|
69
134
|
await page.screenshot({ path: screenshotPath, fullPage: true });
|
|
135
|
+
|
|
136
|
+
// Restore original styles
|
|
137
|
+
await page.evaluate(`
|
|
138
|
+
// Restore element styles
|
|
139
|
+
if (window.__testivaiOriginalStyles) {
|
|
140
|
+
window.__testivaiOriginalStyles.forEach(function(item) {
|
|
141
|
+
item.element.style.overflow = item.overflow;
|
|
142
|
+
item.element.style.overflowY = item.overflowY;
|
|
143
|
+
item.element.style.height = item.height;
|
|
144
|
+
item.element.style.maxHeight = item.maxHeight;
|
|
145
|
+
item.element.style.minHeight = item.minHeight;
|
|
146
|
+
});
|
|
147
|
+
delete window.__testivaiOriginalStyles;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Restore root element styles
|
|
151
|
+
if (window.__testivaiRootStyles) {
|
|
152
|
+
var html = document.documentElement;
|
|
153
|
+
var body = document.body;
|
|
154
|
+
html.style.overflow = window.__testivaiRootStyles.html.overflow;
|
|
155
|
+
html.style.height = window.__testivaiRootStyles.html.height;
|
|
156
|
+
body.style.overflow = window.__testivaiRootStyles.body.overflow;
|
|
157
|
+
body.style.height = window.__testivaiRootStyles.body.height;
|
|
158
|
+
delete window.__testivaiRootStyles;
|
|
159
|
+
}
|
|
160
|
+
`);
|
|
70
161
|
|
|
71
162
|
// 2. Dump full-page DOM
|
|
72
163
|
const domPath = path.join(outputDir, `${baseFilename}.html`);
|