DEV Community

Cover image for Cleaning up web pages for screenshots — a practical Puppeteer guide
PetrDev
PetrDev

Posted on

Cleaning up web pages for screenshots — a practical Puppeteer guide

If you've ever taken a screenshot of a real-world webpage with Puppeteer, you've seen the ugly truth: cookie banners, sticky headers frozen at weird positions, collapsed FAQ accordions, and "Subscribe!" popups.

Here are four small Puppeteer helpers I use to get clean, presentable screenshots. Each is one function, drop-in ready.

1. Kill cookie banners

async function hideCookieBanners(page, customSelectors = []) {
    await page.evaluate((selectors) => {
        // Known cookie-banner elements
        ['CookieReportsPanel', 'CookieReportsOverlay', 'CookieReportsBannerAZ']
            .forEach(id => document.getElementById(id)?.remove());

        document.querySelectorAll('[class*="wscr"],[id*="CookieReport"]')
            .forEach(el => el.remove());

        // Reset body scroll locks the banner often sets
        document.body.style.overflow = '';
        document.documentElement.style.overflow = '';

        // User-defined selectors (if they know their site)
        selectors.forEach(sel => {
            try {
                document.querySelectorAll(sel).forEach(el => {
                    el.style.setProperty('display', 'none', 'important');
                });
            } catch {}
        });
    }, customSelectors);
}
Enter fullscreen mode Exit fullscreen mode

2. Hide arbitrary elements

async function hideElements(page, selectors) {
    if (!selectors.length) return;
    await page.evaluate(sels => {
        sels.forEach(sel => {
            try {
                document.querySelectorAll(sel).forEach(el => {
                    el.style.setProperty('display', 'none', 'important');
                });
            } catch {}
        });
    }, selectors);
}
Enter fullscreen mode Exit fullscreen mode

Example: hide chat widgets, "cookies" bars, floating "Get started" buttons:

await hideElements(page, [
    '#intercom-container',
    '.crisp-client',
    '[class*="newsletter-popup"]'
]);
Enter fullscreen mode Exit fullscreen mode

3. Unfix sticky headers

Sticky headers repeat on every "screen" of a fullPage screenshot — ugly. Convert them to position: relative:

async function unfixSticky(page, selectors) {
    await page.evaluate(sels => {
        sels.forEach(sel => {
            try {
                document.querySelectorAll(sel).forEach(el => {
                    el.style.setProperty('position', 'relative', 'important');
                    ['top', 'bottom', 'left', 'right', 'z-index']
                        .forEach(p => el.style.setProperty(p, 'auto', 'important'));
                    el.style.setProperty('width', '100%', 'important');
                });
            } catch {}
        });
    }, selectors);
}
Enter fullscreen mode Exit fullscreen mode

4. Expand FAQs / accordions

Most FAQs use classes like _active or attributes like open. Give the function a CSS selector + action + value:

async function expandAccordions(page, pairs) {
    if (!pairs.length) return;
    await page.evaluate(rules => {
        rules.forEach(({ selector, action, value }) => {
            try {
                document.querySelectorAll(selector).forEach(el => {
                    if (action === 'class') {
                        el.classList.add(value || '_active');
                        // Also unhide next sibling (common FAQ pattern)
                        const next = el.nextElementSibling;
                        if (next) {
                            next.style.display = 'block';
                            next.style.maxHeight = 'none';
                            next.removeAttribute('hidden');
                        }
                    } else if (action === 'attribute') {
                        const [name, val = ''] = value.split('=');
                        el.setAttribute(name, val);
                    } else if (action === 'style') {
                        value.split(';').forEach(rule => {
                            const [p, v] = rule.split(':');
                            if (p && v) {
                                el.style.setProperty(p.trim(), v.trim(), 'important');
                            }
                        });
                    }
                });
            } catch {}
        });
    }, pairs);

    // Wait for CSS transitions
    await new Promise(r => setTimeout(r, 400));
}
Enter fullscreen mode Exit fullscreen mode

Example usage:

await expandAccordions(page, [
    { selector: '.faq__question', action: 'class', value: '_active' },
    { selector: 'details', action: 'attribute', value: 'open=true' },
    { selector: '.accordion-body', action: 'style', value: 'display: block' }
]);
Enter fullscreen mode Exit fullscreen mode

Putting it together

await page.goto(url, { waitUntil: 'domcontentloaded' });
await hideCookieBanners(page);
await hideElements(page, ['.chat-widget']);
await unfixSticky(page, ['.sticky-nav']);
await expandAccordions(page, [{ selector: '.faq__q', action: 'class', value: '_open' }]);
await page.screenshot({ path: 'clean.png', fullPage: true });
Enter fullscreen mode Exit fullscreen mode

Where I use this

All four helpers ship in production on Site2PDF — a website-to-PDF tool I'm building. Users paste a URL, tick what to hide/expand, and get a clean archive.

If you're doing any kind of web scraping or automated screenshots, grab these — they'll save you a weekend of CSS archaeology.

Top comments (0)