Our product is designed to load XML (from EPUB) into an iframe embedded within the shadowDOM, and we need to take partial screenshots of elements for VRT purposes.
At that time, I encountered a strange bug in Firefox, so I’d like to work on resolving the issue by submitting a specific pull request.
Description
When checkElement() is called after browser.switchFrame() to capture an element inside an iframe, Firefox produces a blank (white) screenshot. Chrome works only because of its non-standard takeScreenshot behavior.
<!-- Overview of Elements -->
<customElement>
<iframe>
<div class="target-element">
....
</div>
</iframe>
</customElement>
Steps to Reproduce
it('<title of testcase>', async () => {
// do VRT using switchFrame + browser.checkElement
const iframe = await $('customElement').shadow$('.an-iframe-element');
await browser.switchFrame(iframe);
try {
const el = await $('.target-element');
//
const result = await browser.checkElement(el, 'screenshot-title', {
rawMisMatchPercentage: true
});
expect(result).toBeLessThanOrEqual(0);
} finally {
await browser.switchFrame(null);
}
});
Expected Behavior
checkElement() should produce a correct screenshot of the element inside the iframe on all browsers.
Actual Behavior
- Firefox: Returns a completely white/blank image
- Chrome: Works, but only due to Chrome's non-standard
takeScreenshot behavior
Root Cause Analysis
switchFrame() switches the browsing context to the iframe
checkElement() internally calls browser.execute(getScreenDimensions), which runs in the iframe's window context
- The iframe's viewport is small (e.g., 50×100 CSS px), so
minEdge <= 800 && maxEdge <= 1280 evaluates to true
- On displays with DPR ≥ 2,
isLikelyEmulated becomes true
isEmulated = true forces the fallback flag to true in takeElementScreenshots.ts (line 171):
fallback: (hasResizeDimensions(options.resizeDimensions) || options.isEmulated) || false,
fallback = true skips takeElementScreenshot (W3C §17.2) and falls back to takeScreenshot (§17.1) + crop
- Firefox follows W3C spec:
takeScreenshot captures the top-level browsing context, but crop coordinates are calculated from getBoundingClientRect within the iframe — the coordinates don't match, resulting in a blank image
- Chrome non-standardly returns only the current frame's content from
takeScreenshot, so the crop accidentally succeeds
Verification Data
| API |
Chrome |
Firefox |
element.saveScreenshot() |
✓ Works |
✓ Works |
browser.takeElementScreenshot(elementId) |
✓ Works |
✓ Works |
browser.takeScreenshot() |
Frame only (non-standard) |
Full page (spec-compliant) |
browser.checkElement() |
✓ (relies on Chrome's non-standard behavior) |
✗ Blank image |
getScreenDimensions output inside iframe:
innerWidth=50, innerHeight=100, dpr=3, isLikelyEmulated=true
Proposed Fix
Add an iframe detection check (window.self !== window.top) to the isLikelyEmulated heuristic in getScreenDimensions.ts:
const isInIframe = window.self !== window.top
const isLikelyEmulated =
!isMobile &&
!isInIframe && // Skip emulation detection inside iframes
dpr >= 2 &&
minEdge <= 800 &&
maxEdge <= 1280 &&
width > 0 && height > 0
Rationale: An iframe's viewport size is determined by the parent page's CSS, not by device emulation. window.self !== window.top is safe even for cross-origin iframes (the boolean comparison does not throw a SecurityError).
Environment
- Firefox: 151.0.2 (confirmed)
- Potentially all browsers, but Chrome's non-standard
takeScreenshot behavior masks the issue
- OS: macOS
@wdio/visual-service: 9.x
@wdio/image-comparison-core: 1.x
Our product is designed to load XML (from EPUB) into an iframe embedded within the shadowDOM, and we need to take partial screenshots of elements for VRT purposes.
At that time, I encountered a strange bug in Firefox, so I’d like to work on resolving the issue by submitting a specific pull request.
Description
When
checkElement()is called afterbrowser.switchFrame()to capture an element inside an iframe, Firefox produces a blank (white) screenshot. Chrome works only because of its non-standardtakeScreenshotbehavior.Steps to Reproduce
Expected Behavior
checkElement()should produce a correct screenshot of the element inside the iframe on all browsers.Actual Behavior
takeScreenshotbehaviorRoot Cause Analysis
switchFrame()switches the browsing context to the iframecheckElement()internally callsbrowser.execute(getScreenDimensions), which runs in the iframe'swindowcontextminEdge <= 800 && maxEdge <= 1280evaluates totrueisLikelyEmulatedbecomestrueisEmulated = trueforces thefallbackflag totrueintakeElementScreenshots.ts(line 171):fallback = trueskipstakeElementScreenshot(W3C §17.2) and falls back totakeScreenshot(§17.1) + croptakeScreenshotcaptures the top-level browsing context, but crop coordinates are calculated fromgetBoundingClientRectwithin the iframe — the coordinates don't match, resulting in a blank imagetakeScreenshot, so the crop accidentally succeedsVerification Data
element.saveScreenshot()browser.takeElementScreenshot(elementId)browser.takeScreenshot()browser.checkElement()getScreenDimensionsoutput inside iframe:Proposed Fix
Add an iframe detection check (
window.self !== window.top) to theisLikelyEmulatedheuristic ingetScreenDimensions.ts:Rationale: An iframe's viewport size is determined by the parent page's CSS, not by device emulation.
window.self !== window.topis safe even for cross-origin iframes (the boolean comparison does not throw aSecurityError).Environment
takeScreenshotbehavior masks the issue@wdio/visual-service: 9.x@wdio/image-comparison-core: 1.x