<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: FrameJet</title>
    <description>The latest articles on DEV Community by FrameJet (@framejet).</description>
    <link>https://dev.to/framejet</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4129338%2F94fe6787-9763-40de-8587-135002694289.png</url>
      <title>DEV Community: FrameJet</title>
      <link>https://dev.to/framejet</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/framejet"/>
    <language>en</language>
    <item>
      <title>Why your headless screenshots have a cookie banner over them (and how to get rid of it)</title>
      <dc:creator>FrameJet</dc:creator>
      <pubDate>Thu, 17 Sep 2026 10:35:58 +0000</pubDate>
      <link>https://dev.to/framejet/why-your-headless-screenshots-have-a-cookie-banner-over-them-and-how-to-get-rid-of-it-4gg0</link>
      <guid>https://dev.to/framejet/why-your-headless-screenshots-have-a-cookie-banner-over-them-and-how-to-get-rid-of-it-4gg0</guid>
      <description>&lt;p&gt;If you have ever automated screenshots with Puppeteer or Playwright, you've seen it: the page renders perfectly, and then a consent wall covers half of it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why it happens
&lt;/h2&gt;

&lt;p&gt;A headless browser starts with an empty profile. No cookies, no stored consent. To every website it is a brand-new visitor, and brand-new visitors in the EU (and increasingly everywhere) get a consent banner. Your screenshot is just faithfully recording what that visitor sees.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 1: accept the banner first
&lt;/h2&gt;

&lt;p&gt;Click "Accept" before capturing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;waitUntil&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;networkidle2&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;btn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#onetrust-accept-btn-handler&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;btn&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;screenshot&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;shot.png&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It works for one site. It breaks the moment you capture a site using a different consent platform, a different button id, or a banner inside an iframe.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 2: hide the overlays
&lt;/h2&gt;

&lt;p&gt;Instead of interacting, remove the elements:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;addStyleTag&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;content&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;`
  #onetrust-consent-sdk, #CybotCookiebotDialog, .fc-consent-root,
  [id^="sp_message_container"], #didomi-host { display: none !important; }
  html, body { overflow: auto !important; }
`&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two details people miss: many banners lock scrolling on &lt;code&gt;&amp;lt;body&amp;gt;&lt;/code&gt;, so you must restore &lt;code&gt;overflow&lt;/code&gt;, and some leave a dark full-screen backdrop that is a separate element from the dialog. A selector list gets you most of the big platforms; for the long tail you need a heuristic, e.g. fixed-position elements covering most of the viewport with a high z-index.&lt;/p&gt;

&lt;p&gt;Chat widgets (Intercom, Drift, HubSpot...) and sticky headers are the same problem in a different place. The sticky header is especially bad on full-page captures, where it can repeat down the image.&lt;/p&gt;

&lt;h2&gt;
  
  
  Option 3: use a service that does it for you
&lt;/h2&gt;

&lt;p&gt;This is the part where I mention that I got tired of maintaining that selector list and built &lt;a href="https://framejet.dev" rel="noopener noreferrer"&gt;Framejet&lt;/a&gt;, a screenshot API with this on by default:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"X-Api-Key: &lt;/span&gt;&lt;span class="nv"&gt;$KEY&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;-o&lt;/span&gt; shot.png &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s2"&gt;"https://framejet.dev/v1/take?url=https://example.com&amp;amp;full_page=true"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It removes consent overlays, sticky headers and chat bubbles before the capture, doesn't charge for failed captures or cached repeats, and has an MCP server if you want an AI agent to take screenshots. There's a free tier of 200 screenshots a month. Other APIs (ScreenshotOne, Urlbox, ApiFlash) solve this too, so pick whatever fits your volume and budget.&lt;/p&gt;

&lt;p&gt;Whatever you choose, the underlying trick is the same: treat overlays as a rendering problem, not a clicking problem.&lt;/p&gt;

</description>
      <category>puppeteer</category>
      <category>webdev</category>
      <category>javascript</category>
      <category>api</category>
    </item>
  </channel>
</rss>
