Puppeteer: loading=lazy images not loading in attached Chrome tab, img.complete false, visibilityState hidden
Puppeteer screenshots worked, but native loading=lazy images never loaded in an attached Chrome 153.0.0.0 tab. The image endpoint returned HTTP 200 and image/png; img.complete stayed false and naturalWidth stayed zero. scrollIntoView, explicit nonzero image dimensions, and page.bringToFront did not fix the waits (the harness reported 'Error: Waiting failed'). The image rectangles were inside the viewport, while document.visibilityState remained 'hidden' and document.hasFocus() was false. This occurred while automating a real Chrome target through a CDP browser relay.
Check document visibility before diagnosing the image server or rewriting the application. In this attached-background-tab case, bringToFront resolved but did not make the document visible; native lazy image loading remained deferred. CDP focus emulation made visibilityState become 'visible' and hasFocus() become true, after which the same unmodified image URLs loaded and screenshots showed their pixels.
const cdp = await page.createCDPSession();
try {
console.log(await page.evaluate(() => ({
visibility: document.visibilityState,
focused: document.hasFocus(),
})));
await cdp.send('Emulation.setFocusEmulationEnabled', {enabled: true});
if (await page.evaluate(() => document.visibilityState) !== 'visible') {
throw new Error('Focus emulation did not activate this target');
}
// Scroll the images into view when they are genuinely offscreen.
await page.waitForFunction(() => {
const images = [...document.querySelectorAll('img[loading="lazy"]')];
return images.length > 0 && images.every(img => img.complete && img.naturalWidth > 0);
});
await page.screenshot({path: 'review.png'});
} finally {
await cdp.send('Emulation.setFocusEmulationEnabled', {enabled: false});
await cdp.detach();
}Observed in Chrome 153.0.0.0: hidden/false before the command, visible/true afterward, followed by successfully decoded images. Adding width/height is still useful to reserve layout space, but it did not solve this visibility-state failure. Keep this override in automation; do not disable production lazy loading just to accommodate a background test target. Scope the CDP session to the page you actually inspect, and restore the override when done.