@testivai/witness-playwright 0.1.5 → 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,36 +79,104 @@ 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
- // Temporarily disable overflow constraints to enable full-page capture
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 */
83
85
  await page.evaluate(`
84
86
  window.__testivaiOriginalStyles = [];
85
- document.querySelectorAll('*').forEach((el) => {
86
- const computed = window.getComputedStyle(el);
87
- if (computed.overflow === 'auto' || computed.overflow === 'scroll' ||
88
- computed.overflowY === 'auto' || computed.overflowY === 'scroll') {
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) {
89
108
  window.__testivaiOriginalStyles.push({
90
109
  element: el,
91
110
  overflow: el.style.overflow,
92
- height: el.style.height
111
+ overflowY: el.style.overflowY,
112
+ height: el.style.height,
113
+ maxHeight: el.style.maxHeight,
114
+ minHeight: el.style.minHeight
93
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
94
126
  el.style.overflow = 'visible';
95
- el.style.height = 'auto';
127
+ el.style.overflowY = 'visible';
128
+ el.style.maxHeight = 'none';
96
129
  }
97
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';
98
151
  `);
99
- // Wait for layout to stabilize
100
- await page.waitForTimeout(200);
152
+ // Wait for layout to stabilize after style changes
153
+ await page.waitForTimeout(300);
101
154
  // Take full-page screenshot
102
155
  await page.screenshot({ path: screenshotPath, fullPage: true });
103
156
  // Restore original styles
104
157
  await page.evaluate(`
158
+ // Restore element styles
105
159
  if (window.__testivaiOriginalStyles) {
106
- window.__testivaiOriginalStyles.forEach((item) => {
160
+ window.__testivaiOriginalStyles.forEach(function(item) {
107
161
  item.element.style.overflow = item.overflow;
162
+ item.element.style.overflowY = item.overflowY;
108
163
  item.element.style.height = item.height;
164
+ item.element.style.maxHeight = item.maxHeight;
165
+ item.element.style.minHeight = item.minHeight;
109
166
  });
110
167
  delete window.__testivaiOriginalStyles;
111
168
  }
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;
179
+ }
112
180
  `);
113
181
  // 2. Dump full-page DOM
114
182
  const domPath = path.join(outputDir, `${baseFilename}.html`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@testivai/witness-playwright",
3
- "version": "0.1.5",
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,39 +56,107 @@ export async function snapshot(
56
56
  // 1. Capture full-page screenshot
57
57
  const screenshotPath = path.join(outputDir, `${baseFilename}.png`);
58
58
 
59
- // Temporarily disable overflow constraints to enable full-page capture
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 */
60
62
  await page.evaluate(`
61
63
  window.__testivaiOriginalStyles = [];
62
- document.querySelectorAll('*').forEach((el) => {
63
- const computed = window.getComputedStyle(el);
64
- if (computed.overflow === 'auto' || computed.overflow === 'scroll' ||
65
- computed.overflowY === 'auto' || computed.overflowY === 'scroll') {
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) {
66
85
  window.__testivaiOriginalStyles.push({
67
86
  element: el,
68
87
  overflow: el.style.overflow,
69
- height: el.style.height
88
+ overflowY: el.style.overflowY,
89
+ height: el.style.height,
90
+ maxHeight: el.style.maxHeight,
91
+ minHeight: el.style.minHeight
70
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
71
103
  el.style.overflow = 'visible';
72
- el.style.height = 'auto';
104
+ el.style.overflowY = 'visible';
105
+ el.style.maxHeight = 'none';
73
106
  }
74
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';
75
128
  `);
76
129
 
77
- // Wait for layout to stabilize
78
- await page.waitForTimeout(200);
130
+ // Wait for layout to stabilize after style changes
131
+ await page.waitForTimeout(300);
79
132
 
80
133
  // Take full-page screenshot
81
134
  await page.screenshot({ path: screenshotPath, fullPage: true });
82
135
 
83
136
  // Restore original styles
84
137
  await page.evaluate(`
138
+ // Restore element styles
85
139
  if (window.__testivaiOriginalStyles) {
86
- window.__testivaiOriginalStyles.forEach((item) => {
140
+ window.__testivaiOriginalStyles.forEach(function(item) {
87
141
  item.element.style.overflow = item.overflow;
142
+ item.element.style.overflowY = item.overflowY;
88
143
  item.element.style.height = item.height;
144
+ item.element.style.maxHeight = item.maxHeight;
145
+ item.element.style.minHeight = item.minHeight;
89
146
  });
90
147
  delete window.__testivaiOriginalStyles;
91
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
+ }
92
160
  `);
93
161
 
94
162
  // 2. Dump full-page DOM