@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 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
- // Ensure lazy-loaded content is loaded by scrolling to bottom and back
83
- try {
84
- await page.evaluate('window.scrollTo(0, Math.max(document.body.scrollHeight, document.documentElement.scrollHeight))');
85
- await page.waitForTimeout(100);
86
- await page.evaluate('window.scrollTo(0, 0)');
87
- await page.waitForTimeout(100);
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
- catch (err) {
90
- // Ignore scroll errors
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
- await page.screenshot({ path: screenshotPath, fullPage: true });
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testivai/witness-playwright",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
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/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.2
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
- // Ensure lazy-loaded content is loaded by scrolling to bottom and back
60
- try {
61
- await page.evaluate('window.scrollTo(0, Math.max(document.body.scrollHeight, document.documentElement.scrollHeight))');
62
- await page.waitForTimeout(100);
63
- await page.evaluate('window.scrollTo(0, 0)');
64
- await page.waitForTimeout(100);
65
- } catch (err) {
66
- // Ignore scroll errors
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`);