Skip to content

bug: checkElement produces blank screenshots inside iframes after switchFrame on Firefox #1156

@n2-freevas

Description

@n2-freevas

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

  1. switchFrame() switches the browsing context to the iframe
  2. checkElement() internally calls browser.execute(getScreenDimensions), which runs in the iframe's window context
  3. The iframe's viewport is small (e.g., 50×100 CSS px), so minEdge <= 800 && maxEdge <= 1280 evaluates to true
  4. On displays with DPR ≥ 2, isLikelyEmulated becomes true
  5. isEmulated = true forces the fallback flag to true in takeElementScreenshots.ts (line 171):
    fallback: (hasResizeDimensions(options.resizeDimensions) || options.isEmulated) || false,
  6. fallback = true skips takeElementScreenshot (W3C §17.2) and falls back to takeScreenshot (§17.1) + crop
  7. 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
  8. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions