<?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: Nico Acosta</title>
    <description>The latest articles on DEV Community by Nico Acosta (@nico_acosta_bc7dceb59f65e).</description>
    <link>https://dev.to/nico_acosta_bc7dceb59f65e</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%2F3978200%2F8f88872d-ec24-49b7-ab54-afee796bed22.png</url>
      <title>DEV Community: Nico Acosta</title>
      <link>https://dev.to/nico_acosta_bc7dceb59f65e</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nico_acosta_bc7dceb59f65e"/>
    <language>en</language>
    <item>
      <title>How to Run Selenium in Docker for Screenshots (and the Ops Tax It Adds)</title>
      <dc:creator>Nico Acosta</dc:creator>
      <pubDate>Mon, 17 Aug 2026 11:43:26 +0000</pubDate>
      <link>https://dev.to/grabbit/how-to-run-selenium-in-docker-for-screenshots-and-the-ops-tax-it-adds-nf4</link>
      <guid>https://dev.to/grabbit/how-to-run-selenium-in-docker-for-screenshots-and-the-ops-tax-it-adds-nf4</guid>
      <description>&lt;p&gt;Running Selenium in Docker to capture screenshots is a well-trodden path, and the setup is short. The reason people still end up searching for it is not the happy path. It is the three failure modes underneath: a blank white image, a Chromium crash halfway through a run, and a &lt;code&gt;session not created&lt;/code&gt; error that traces back to a version mismatch you did not know you had.&lt;/p&gt;

&lt;p&gt;This guide covers the working setup with the official image, a runnable Python example, the errors that actually show up, and the honest boundary where containerizing a browser stops being worth it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use the official standalone-chrome image
&lt;/h2&gt;

&lt;p&gt;Selenium publishes an image that bundles Chrome, chromedriver, and their system libraries, all versioned together:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker run &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="nt"&gt;-p&lt;/span&gt; 4444:4444 &lt;span class="nt"&gt;--shm-size&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;2g selenium/standalone-chrome:latest
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That starts a Selenium Grid standalone with a WebDriver endpoint on port 4444. The &lt;code&gt;--shm-size=2g&lt;/code&gt; is not optional in practice: Chromium leans on shared memory, Docker's default &lt;code&gt;/dev/shm&lt;/code&gt; is 64MB, and the default is exactly enough to make the browser start fine and then die partway through a page. Raise it up front and you skip the most common crash.&lt;/p&gt;

&lt;p&gt;The distinction that matters: the browser runs inside the container, but your automation code does not have to. You connect to it over the network with a Remote WebDriver, so nothing but Docker needs to be installed on the host.&lt;/p&gt;

&lt;h2&gt;
  
  
  A working Python example
&lt;/h2&gt;

&lt;p&gt;With the container running, this connects to it, loads a page, waits for it to paint, and saves a PNG:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;selenium&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;selenium.webdriver.chrome.options&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Options&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;selenium.webdriver.common.by&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;By&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;selenium.webdriver.support.ui&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;WebDriverWait&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;selenium.webdriver.support&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;expected_conditions&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;EC&lt;/span&gt;

&lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Options&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--headless=new&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--window-size=1280,720&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;add_argument&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;--disable-dev-shm-usage&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;driver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;webdriver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Remote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;command_executor&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;http://localhost:4444/wd/hub&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://example.com&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nc"&gt;WebDriverWait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;until&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;EC&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;presence_of_element_located&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;By&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;TAG_NAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;h1&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save_screenshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;example.png&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;finally&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;quit&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The three Chrome options carry their weight. &lt;code&gt;--headless=new&lt;/code&gt; is Chrome's modern headless mode, which renders the same as headed Chrome instead of the old separate code path. &lt;code&gt;--window-size&lt;/code&gt; sets the viewport, because headless defaults to a small window and a small window is a small screenshot. &lt;code&gt;--disable-dev-shm-usage&lt;/code&gt; is the belt to the &lt;code&gt;--shm-size&lt;/code&gt; braces: it moves shared memory to &lt;code&gt;/tmp&lt;/code&gt;, which keeps Chromium alive even when &lt;code&gt;/dev/shm&lt;/code&gt; is tight.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;WebDriverWait&lt;/code&gt; is the part most first attempts skip, and it is why they come back with a blank image.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the screenshot comes back blank
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;save_screenshot&lt;/code&gt; captures whatever is painted at the instant you call it. Call it immediately after &lt;code&gt;driver.get()&lt;/code&gt; and you photograph the page mid-load: no images, no web fonts, no JavaScript-rendered content. On a modern single-page app that means a white rectangle.&lt;/p&gt;

&lt;p&gt;The fix is to wait for a real signal that the page is ready, not a fixed guess. Waiting on a specific element with &lt;code&gt;WebDriverWait&lt;/code&gt; is the precise version. When the content you need is drawn by JavaScript after the initial elements appear, add a short explicit pause before capturing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;time&lt;/span&gt;

&lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;https://example.com/dashboard&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nc"&gt;WebDriverWait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;until&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;EC&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;presence_of_element_located&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;By&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;CSS_SELECTOR&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;[data-loaded]&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;time&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# let late-rendering charts finish painting
&lt;/span&gt;&lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save_screenshot&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;dashboard.png&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A blank image with no error is nearly always this. A blank image where the whole viewport is one flat color can also mean the window never got sized, so confirm &lt;code&gt;--window-size&lt;/code&gt; is set.&lt;/p&gt;

&lt;h2&gt;
  
  
  The errors you will actually hit
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;session not created: This version of ChromeDriver only supports Chrome version N&lt;/code&gt;.&lt;/strong&gt; A mismatch between Chrome and chromedriver. This is the reason the official image exists: it pins the two together. If you built your own image and hit this, you now own the job of upgrading both in lockstep on every Chrome bump.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Chromium crashes mid-run.&lt;/strong&gt; Shared memory. Start the container with &lt;code&gt;--shm-size=2g&lt;/code&gt; and pass &lt;code&gt;--disable-dev-shm-usage&lt;/code&gt; in the options. Use both; they solve the same problem from opposite ends.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;The screenshot only shows the top of the page.&lt;/strong&gt; &lt;code&gt;save_screenshot&lt;/code&gt; captures the viewport, not the full document. Either set the window height to the page's full scroll height before capturing, or use Chrome DevTools Protocol, which renders the whole page in one pass:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;execute_cdp_cmd&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Page.captureScreenshot&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;format&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;png&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;captureBeyondViewport&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;connection refused&lt;/code&gt; on port 4444.&lt;/strong&gt; The container is still booting. Selenium Grid takes a couple of seconds to come up, so poll &lt;code&gt;http://localhost:4444/wd/hub/status&lt;/code&gt; until it reports ready before your first connection.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Works locally, fails in CI.&lt;/strong&gt; Usually the container did not get enough shared memory on the CI runner, or the image tag drifted. Pin the image tag and pass &lt;code&gt;--shm-size&lt;/code&gt; explicitly in the CI job rather than relying on a default.&lt;/p&gt;

&lt;h2&gt;
  
  
  When Docker is the right call, and when it is not
&lt;/h2&gt;

&lt;p&gt;Containerizing Selenium buys you a reproducible browser environment, and that is worth the setup cost when your automation is doing browser work: logging into an app, stepping through a multi-page flow, filling forms, asserting on the result. There you need the whole browser under your control, and a container is how you make "the whole browser" identical on a laptop and a CI runner.&lt;/p&gt;

&lt;p&gt;The calculation flips when the browser is incidental. A large share of Selenium-in-Docker setups exist to produce one thing: an image of a page. A dashboard snapshot, an OG card, a nightly capture of a report, a thumbnail for a listing. There the container is not giving you determinism you need, it is giving you a browser you now have to maintain, so that an HTTP request can come back with a PNG. That maintenance is the real cost, and it is exactly the tax developers describe: self-hosted Chromium at concurrency is where teams lose weekends to memory creep, sticky workers, and restart storms.&lt;/p&gt;

&lt;p&gt;If the job is just "URL in, image out," the render can move off your infrastructure entirely:&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;-sS&lt;/span&gt; https://api.grabbit.live/v1/grabs &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer sk_live_..."&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "url": "https://example.com/dashboard",
    "width": 1280,
    "full_page": true,
    "delay_ms": 1000,
    "format": "webp"
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response carries an &lt;code&gt;image_url&lt;/code&gt; pointing at the stored capture. No container to build, no Chrome and chromedriver versions to keep aligned, no &lt;code&gt;--shm-size&lt;/code&gt; to remember, nothing to rebuild when a base image ships a new glibc.&lt;/p&gt;

&lt;p&gt;The parameters map closely to the Selenium options you would otherwise write: &lt;code&gt;width&lt;/code&gt; (320 to 1920) sets the viewport the way &lt;code&gt;--window-size&lt;/code&gt; does, &lt;code&gt;full_page&lt;/code&gt; renders the whole document like the CDP &lt;code&gt;captureBeyondViewport&lt;/code&gt; call, &lt;code&gt;delay_ms&lt;/code&gt; (0 to 10000) replaces the explicit &lt;code&gt;sleep&lt;/code&gt; after load, &lt;code&gt;selector&lt;/code&gt; captures a single element instead of the page, and &lt;code&gt;format&lt;/code&gt; accepts &lt;code&gt;png&lt;/code&gt;, &lt;code&gt;jpeg&lt;/code&gt;, or &lt;code&gt;webp&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The honest boundary: if the capture has to happen after a login, a click, or a form submission, none of this helps and Selenium in a container is the correct tool. An API call renders a URL. It does not drive a session.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cost, honestly
&lt;/h2&gt;

&lt;p&gt;The comparison is not per-request price against zero, because self-hosting is not free. A containerized browser costs CI minutes on every build, image storage, and the recurring attention of whoever fixes it when a Chrome bump breaks chromedriver or a base image runs the container out of shared memory.&lt;/p&gt;

&lt;p&gt;Grabbit is $0.002 per live grab, prepaid, with credits that do not reset monthly, which fits bursty capture work that runs hard some weeks and not at all in others. Test-environment keys return placeholder images at no cost, so you can wire the whole flow before any real capture runs. Some providers list lower per-grab rates, so if unit price is the only variable you care about, compare directly.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where to go next
&lt;/h2&gt;

&lt;p&gt;If you are keeping the container and just want the capture code to be right, &lt;a href="https://www.grabbit.live/blog/selenium-screenshot" rel="noopener noreferrer"&gt;how to take screenshots in Selenium&lt;/a&gt; covers the full-page, element, and CDP cases in depth. For the browser-agnostic version of the container problem, &lt;a href="https://www.grabbit.live/blog/chrome-headless-docker" rel="noopener noreferrer"&gt;running headless Chrome in Docker&lt;/a&gt; walks the same setup without Selenium in the middle, and &lt;a href="https://www.grabbit.live/blog/playwright-docker" rel="noopener noreferrer"&gt;running Playwright in Docker&lt;/a&gt; covers the equivalent decision from the other major library. The &lt;a href="https://www.grabbit.live/screenshot-api" rel="noopener noreferrer"&gt;screenshot API&lt;/a&gt; page covers the hosted side if you decide the browser is not worth maintaining.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://www.grabbit.live/blog/selenium-docker" rel="noopener noreferrer"&gt;Grabbit blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>node</category>
      <category>programming</category>
    </item>
    <item>
      <title>How Much Time Should You Spend Planning Before Coding With AI?</title>
      <dc:creator>Nico Acosta</dc:creator>
      <pubDate>Mon, 17 Aug 2026 11:15:42 +0000</pubDate>
      <link>https://dev.to/braingrid/how-much-time-should-you-spend-planning-before-coding-with-ai-4dib</link>
      <guid>https://dev.to/braingrid/how-much-time-should-you-spend-planning-before-coding-with-ai-4dib</guid>
      <description>&lt;p&gt;AI made writing code almost free, which should have finally ended the old argument about how much to plan before you start. Why design carefully when regenerating the whole thing takes thirty seconds? Instead the opposite happened. The cheaper the code got, the more the planning started to matter.&lt;/p&gt;

&lt;p&gt;That is not a paradox once you watch where the time actually goes. This week in r/nocode, someone who had burned through Lovable, Replit, and Cursor credits and still had nothing working asked which AI app builder to buy next. An experienced builder replied with a ratio, not a tool: &lt;a href="https://www.reddit.com/r/nocode/comments/1vm2afr/best_ai_app_builder_for_noncoders_in_2026/" rel="noopener noreferrer"&gt;you should spend about 1 hour designing the specs and 20-30 minutes actually building&lt;/a&gt;. Then the line that turns it from advice into a diagnosis: if you find yourself cursing the AI that it is not what I asked for, it is largely because you didn't set the requirements properly.&lt;/p&gt;

&lt;p&gt;Two to one, planning to building. From someone whose building step is an agent that writes the code for him. That is the number worth explaining.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hypothesis: unplanned time doesn't vanish, it moves
&lt;/h2&gt;

&lt;p&gt;Here is the claim this post is built to test. &lt;strong&gt;The hour you skip up front does not disappear. It reappears downstream as debugging, reprompting, and rework, and it costs more there than it would have cost as planning.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The old wisdom was that planning trades slow-now for fast-later, a tax you pay to avoid mistakes. With an AI agent that math gets sharper, because the agent removes almost all of the "building" time but none of the "deciding what to build" time. When the code was the expensive part, a vague plan cost you a few extra hours of typing. Now the code is nearly free and the vague plan costs you something worse: an agent that confidently builds the wrong thing, fast, and reports done.&lt;/p&gt;

&lt;p&gt;You have felt this if you have ever watched an agent produce a beautiful, complete, working feature that solves a problem you did not have. It did exactly what you said. You just had not decided what you meant. The gap between those two is where the skipped hour went.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cursing the AI is a requirements bill coming due
&lt;/h2&gt;

&lt;p&gt;The r/nocode builder's reframe is the whole argument in one sentence. When the output is wrong, the instinct is to blame the tool and go shopping for a better one. Sometimes the tool is genuinely weak. Far more often, the requirements were never set, and no tool can build what you did not specify.&lt;/p&gt;

&lt;p&gt;This shows up as a specific, repeatable feeling. Builders in r/ExperiencedDevs described &lt;a href="https://www.reddit.com/r/ExperiencedDevs/comments/1vmayzf/how_are_people_blowing_through_token_usage/" rel="noopener noreferrer"&gt;feeling illiterate about the codebase&lt;/a&gt; as agents generate faster than anyone can read, and one named the root cause bluntly: people run agentic loops to solve problems they do not even understand themselves. That is not a model problem. An agent handed a fuzzy goal will fill the gaps with its best guess, and its best guess is a coin flip that looks like a decision. You do not discover the mismatch until you are three features deep and the auth logic it invented in feature two fights the state management it invented in feature four. This is the same drift that makes &lt;a href="https://www.braingrid.ai/blog/why-ai-keeps-breaking-things-that-worked" rel="noopener noreferrer"&gt;your agent keep breaking things that already worked&lt;/a&gt;: nothing carried your intent forward, so each feature quietly renegotiated it.&lt;/p&gt;

&lt;p&gt;The planning hour is where you catch that before it's written. Not because planning is virtuous, but because it's the only place the mismatch is cheap to fix. A wrong sentence in a spec costs one edit. The same wrong assumption discovered in a shipped feature costs a debugging session, a reprompt, and the quiet erosion of your trust in the whole app.&lt;/p&gt;

&lt;h2&gt;
  
  
  The ratio is not the point. The exit condition is.
&lt;/h2&gt;

&lt;p&gt;Two-to-one is a useful anchor, but do not turn it into a rule. The honest version is that planning time should scale with how much of your intent the agent would otherwise have to guess. A one-file script needs almost none. A multi-screen app with auth, data, and money needs a lot, because every unstated rule is a decision the agent makes for you.&lt;/p&gt;

&lt;p&gt;What you are actually buying with that hour is a definition of done the agent can be measured against. This is the difference between a loop that converges and a loop that just burns tokens. A builder in r/AI_Agents put the failure mode precisely while defending the idea of agent loops against the skeptics: a bare loop with no verifiable exit is &lt;a href="https://www.reddit.com/r/AI_Agents/comments/1vlanp8/wait_am_i_just_an_idiot_or_is_all_the_talk_about/" rel="noopener noreferrer"&gt;guaranteed token burn with fingers-crossed results&lt;/a&gt;. Planning is how you write the exit condition. Without it, "build until it looks done" is the only stopping rule the agent has, and looks-done is exactly the thing that fails in production.&lt;/p&gt;

&lt;p&gt;Consider the same feature, planned two ways.&lt;/p&gt;

&lt;p&gt;Vague: "Build me a client portal with login and file upload."&lt;/p&gt;

&lt;p&gt;Structured: authenticated users land on a dashboard listing their own files in a table with name, size, and upload date; clicking a row downloads the file; uploads accept PDF and images under 10MB and reject everything else with a visible error; unauthenticated users redirect to /login; a user can never see another user's files. Every clause in the second version is a line the finished build can be checked against. That is the skill underneath the planning hour, and it has a name: &lt;a href="https://www.braingrid.ai/blog/how-to-write-acceptance-criteria-ai-agent-can-verify" rel="noopener noreferrer"&gt;writing acceptance criteria an agent can actually verify&lt;/a&gt;. The agent still moves at agent speed. It just can no longer quietly ship the version where any logged-in user can download everyone's files, which is the kind of gap a demo hides and production reveals.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where BrainGrid fits
&lt;/h2&gt;

&lt;p&gt;That planning hour has a shape, and doing it well is a skill most builders are learning on the fly. This is the gap &lt;a href="https://braingrid.ai" rel="noopener noreferrer"&gt;BrainGrid&lt;/a&gt; was built to close, so the hour is spent on decisions instead of on staring at a blank requirements doc.&lt;/p&gt;

&lt;p&gt;You describe the idea in plain English, and the Planning Agent turns it into a structured requirement with acceptance criteria, data models, and designs. It asks the clarifying questions you did not think to answer and pushes back when the intent is vague, which is precisely the "set the requirements properly" step the r/nocode builder was pointing at. Then the Builder Agent runs the build against that spec, either in a managed cloud sandbox with a live preview or in your own GitHub repo through Claude Code, Cursor, or Codex over MCP. And the part almost nobody does by hand happens automatically: verification checks the finished build against every acceptance criterion, so the feature is not done until the evidence says it matches what you asked for.&lt;/p&gt;

&lt;p&gt;That is the two-to-one ratio turned into a workflow, and it is the whole idea behind &lt;a href="https://www.braingrid.ai/spec-driven-development" rel="noopener noreferrer"&gt;spec-driven development&lt;/a&gt;: the planning is where you decide what you mean, and the verification is where the agent proves it built that and not its best guess. Charity Majors made the underlying point in &lt;a href="https://newsletter.pragmaticengineer.com/p/stop-being-skeptical-about-ai-for" rel="noopener noreferrer"&gt;The Pragmatic Engineer&lt;/a&gt;: when you did not write the code, you have to move your trust to the other end of the process, to validation. The plan is what validation checks against. Skip the plan and there is nothing to verify against except vibes.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changes for you
&lt;/h2&gt;

&lt;p&gt;If you are shipping with an AI coding agent right now, here is the concrete implication. Your instinct is to measure progress by how fast the agent produces code, so a slow planning step feels like the agent is idling. Invert it. Measure progress by how few times you have to say "no, not like that." The builders who spend an hour framing the feature say it once or zero times. The builders who skip straight to building say it five times across three reprompts, and each round quietly rewrites something that already worked.&lt;/p&gt;

&lt;p&gt;The trade-off is real and worth stating. On a genuine throwaway, a scratch script, a one-day prototype nobody will maintain, the planning hour is pure overhead, and you should skip it and vibe. The ratio only pays off when the thing has to keep working after you stop looking at it. That is also exactly the moment most builders wish they had planned, and by then the skipped hour has already been spent, at a worse exchange rate, on figuring out why the app broke.&lt;/p&gt;

&lt;p&gt;So how much time should you spend planning before coding with AI? Enough that the agent is executing a decision instead of making one. The code was never the expensive part. Deciding what it should do always was, and pretending otherwise just moves the bill to the end and adds interest.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How much time should you spend planning versus coding?
&lt;/h3&gt;

&lt;p&gt;There is no universal ratio, but builders working with AI agents converge on planning taking as long as or longer than the build, often around two-to-one for anything non-trivial. The reason is that an AI agent collapses the coding time to near zero while leaving the "decide what to build" time untouched, so planning becomes the larger share by default. Scale it to ambiguity: a small script needs minutes, while a multi-feature app with auth, data, and permissions needs real planning because every unstated rule becomes a decision the agent makes for you. The goal is not a fixed percentage. It is that the agent should be executing decisions you made, not guessing at ones you skipped.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is it better to plan before coding or just start coding?
&lt;/h3&gt;

&lt;p&gt;For a small, well-understood task you have done before, starting directly is fine because you are effectively planning as you type. For anything with multiple moving parts, planning first wins, and it wins harder when an AI agent is doing the building. Unplanned work does not save time, it relocates it: the decisions you skip come back as debugging, reprompting, and rework, which are more expensive than the same decisions made up front. The practical test is whether you can state what "done" looks like before you start. If you cannot, you are not ready to build yet, you are ready to plan.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why does my AI agent keep building the wrong thing?
&lt;/h3&gt;

&lt;p&gt;Almost always because the requirements were underspecified, not because the model is weak. An agent handed a vague goal fills the gaps with its best guess and reports success, so "wrong" usually means "technically what I said, not what I meant." The fix is to write the acceptance criteria before building: the specific behaviors, edge cases, and constraints the result must satisfy. When those exist, the agent has something concrete to build toward and you have something concrete to check the result against. When they do not, you are relying on the agent to read your mind, and it cannot.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the 80/20 rule in coding?
&lt;/h3&gt;

&lt;p&gt;The 80/20 rule (the Pareto principle) observes that roughly 80% of outcomes come from 20% of causes, and in software it shows up several ways: a small fraction of the code causes most of the bugs, a small set of features drives most of the value, and the last 20% of a project often takes 80% of the effort. For AI-assisted building, the useful version is that a small amount of upfront planning prevents a large amount of downstream rework. The 20% you spend deciding exactly what to build is what keeps the other 80% from turning into a debugging spiral.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does planning still matter when AI writes the code so fast?
&lt;/h3&gt;

&lt;p&gt;More than before, not less. When writing code was slow, a vague plan cost you extra typing. Now that an agent writes code in seconds, a vague plan costs you a confident, fast build of the wrong thing, which is worse because it looks finished. Speed makes the mismatch cheaper to produce and more expensive to catch, so the planning that prevents the mismatch is the best-paying time you spend. The bottleneck moved from writing code to deciding what the code should do, and that is exactly the part AI does not do for you. It's the same reason &lt;a href="https://www.braingrid.ai/blog/the-real-cost-of-a-vague-prompt" rel="noopener noreferrer"&gt;a vague prompt costs more than it looks like it should&lt;/a&gt;: the cost is just deferred.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://braingrid.ai" rel="noopener noreferrer"&gt;BrainGrid&lt;/a&gt; is the plan-first app-building platform: it turns your idea into a spec with acceptance criteria, builds against it with your agent, and verifies the result before you call it done. Try it at &lt;a href="https://braingrid.ai" rel="noopener noreferrer"&gt;braingrid.ai&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://www.braingrid.ai/blog/how-much-time-planning-before-coding-ai" rel="noopener noreferrer"&gt;BrainGrid blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Screenshot API Alternatives: An Honest Comparison (2026)</title>
      <dc:creator>Nico Acosta</dc:creator>
      <pubDate>Wed, 12 Aug 2026 11:39:27 +0000</pubDate>
      <link>https://dev.to/grabbit/screenshot-api-alternatives-an-honest-comparison-2026-2a0k</link>
      <guid>https://dev.to/grabbit/screenshot-api-alternatives-an-honest-comparison-2026-2a0k</guid>
      <description>&lt;p&gt;If you are comparing screenshot API alternatives, you have almost certainly hit one of three walls: a monthly quota that resets before you use it, a feature the tool does not have, or an integration path that does not fit how you actually build. This is an honest, side-by-side comparison of the main options, with a clear note on where each one wins.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short answer
&lt;/h2&gt;

&lt;p&gt;Every screenshot API does the same core job: send a URL, get back a rendered image. They differ on three things that actually matter, so pick by whichever is your bottleneck:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Billing model.&lt;/strong&gt; Monthly plans reset unused capacity; a flat annual plan does not. This is the biggest hidden cost for spiky or seasonal workloads.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Per-grab rate.&lt;/strong&gt; Browserless and Thum.io list the lowest rates. Neither is a one-call REST endpoint for every feature, so cheap-per-grab comes with a trade-off.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integration.&lt;/strong&gt; A raw REST endpoint, a first-party SDK for your language, or an agent-callable MCP tool are three different developer experiences.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Grabbit is the alternative when monthly waste and agent onboarding are your pain: &lt;strong&gt;$0.002 per capture, 25,000 prepaid credits per year that never reset or expire&lt;/strong&gt;, plus a built-in MCP server. It is not the cheapest option overall, and this post says so plainly below.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the alternatives compare
&lt;/h2&gt;

&lt;p&gt;Prices verified June 2026 from each provider's public pricing page, normalized to roughly 10,000 grabs per month. "Included" is each provider's entry-plan allowance. The honest caveat up front: &lt;strong&gt;two providers list a lower per-grab rate than Grabbit.&lt;/strong&gt;&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Provider&lt;/th&gt;
&lt;th&gt;Per grab&lt;/th&gt;
&lt;th&gt;Billing&lt;/th&gt;
&lt;th&gt;Included&lt;/th&gt;
&lt;th&gt;Monthly waste&lt;/th&gt;
&lt;th&gt;Agent&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Grabbit&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;$0.002&lt;/td&gt;
&lt;td&gt;Annual flat&lt;/td&gt;
&lt;td&gt;25,000 / yr&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;td&gt;One-line + MCP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ScreenshotOne&lt;/td&gt;
&lt;td&gt;$0.0079&lt;/td&gt;
&lt;td&gt;Monthly&lt;/td&gt;
&lt;td&gt;2,000 / mo&lt;/td&gt;
&lt;td&gt;Resets&lt;/td&gt;
&lt;td&gt;MCP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Urlbox&lt;/td&gt;
&lt;td&gt;$0.0066&lt;/td&gt;
&lt;td&gt;Monthly&lt;/td&gt;
&lt;td&gt;2,000 / mo&lt;/td&gt;
&lt;td&gt;Resets&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ApiFlash&lt;/td&gt;
&lt;td&gt;$0.0035&lt;/td&gt;
&lt;td&gt;Monthly&lt;/td&gt;
&lt;td&gt;1,000 / mo&lt;/td&gt;
&lt;td&gt;Resets&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;ScrapingBee&lt;/td&gt;
&lt;td&gt;$0.0049 †&lt;/td&gt;
&lt;td&gt;Monthly&lt;/td&gt;
&lt;td&gt;~10,000 / mo&lt;/td&gt;
&lt;td&gt;Resets&lt;/td&gt;
&lt;td&gt;MCP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Browserless&lt;/td&gt;
&lt;td&gt;$0.0013 ‡&lt;/td&gt;
&lt;td&gt;Monthly&lt;/td&gt;
&lt;td&gt;20,000 units / mo&lt;/td&gt;
&lt;td&gt;Resets&lt;/td&gt;
&lt;td&gt;MCP&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Thum.io&lt;/td&gt;
&lt;td&gt;$0.0002&lt;/td&gt;
&lt;td&gt;Usage metered&lt;/td&gt;
&lt;td&gt;$1–$20 / mo min.&lt;/td&gt;
&lt;td&gt;Minimum spend&lt;/td&gt;
&lt;td&gt;None&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;† ScrapingBee is credit-based (roughly 25 credits per screenshot grab). ‡ Browserless bills in units (roughly 1 unit per grab) and is a managed browser you drive with your own scripts, not a one-call REST endpoint.&lt;/p&gt;

&lt;p&gt;Browserless and Thum.io are cheaper per grab. The trade-offs are real: Browserless is a headless browser you drive with Puppeteer or Playwright, so you still write and maintain the capture code, and Thum.io has a narrow feature set (no full-page capture, no wait controls, no selector targeting). If raw per-grab price is the only thing you care about, one of those two is the honest recommendation, not Grabbit.&lt;/p&gt;

&lt;h2&gt;
  
  
  Match the alternative to the pain
&lt;/h2&gt;

&lt;h3&gt;
  
  
  If monthly resets are wasting money
&lt;/h3&gt;

&lt;p&gt;On a monthly reset plan, a team that captures 1,500 pages in January and 9,000 in March pays the full monthly allowance both months, and January's unused capacity evaporates. You pad the plan to cover the busy month and forfeit the difference every quiet one. Flat annual prepaid credits remove that pressure: you buy a block once, it covers actual usage, and nothing expires at the end of a cycle. For variable workloads (OG images on publish, monitoring runs, batch research), the billing model often matters more than the headline rate. See &lt;a href="https://www.grabbit.live/blog/screenshot-api-pricing" rel="noopener noreferrer"&gt;why monthly screenshot quotas waste money&lt;/a&gt; for the full math.&lt;/p&gt;

&lt;h3&gt;
  
  
  If you need a first-party SDK
&lt;/h3&gt;

&lt;p&gt;ScreenshotOne maintains client libraries for Ruby, PHP, Go, Java, C#, and more. If you want a supported SDK in your language rather than a raw HTTP call, that is a genuine advantage and a good reason to stay with it. Grabbit is a plain REST endpoint plus an MCP server, not a per-language SDK set.&lt;/p&gt;

&lt;h3&gt;
  
  
  If an AI agent needs to see the page
&lt;/h3&gt;

&lt;p&gt;Most screenshot APIs were designed before agent frameworks existed: you get an API key and call the endpoint from backend code, which means custom glue for every agent that needs visual data. Grabbit and ScreenshotOne both ship an MCP server, so an agent registers capture as a callable tool and gets back a hosted &lt;code&gt;image_url&lt;/code&gt; with no glue code. If agent onboarding is why you are shopping, prefer a provider with a one-line or MCP path over a REST-only endpoint.&lt;/p&gt;

&lt;h3&gt;
  
  
  If you just want the lowest possible rate
&lt;/h3&gt;

&lt;p&gt;Browserless ($0.0013) or Thum.io ($0.0002). Accept that Browserless means writing your own browser scripts and Thum.io means a thin feature set. That is the trade you make for the lowest number in the table.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try an alternative before you pay
&lt;/h2&gt;

&lt;p&gt;You can wire up Grabbit and run it in CI before adding a card. Every account gets a &lt;strong&gt;test key&lt;/strong&gt; (&lt;code&gt;sk_test_...&lt;/code&gt;) that returns a placeholder image at no charge; switch to a &lt;strong&gt;live key&lt;/strong&gt; (&lt;code&gt;sk_live_...&lt;/code&gt;) for real captures. Here is a real request against Grabbit's API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://api.grabbit.live/v1/grabs &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer sk_live_..."&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "url": "https://example.com",
    "width": 1280,
    "height": 720,
    "format": "webp",
    "full_page": true
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response includes &lt;code&gt;image_url&lt;/code&gt; (the hosted screenshot), &lt;code&gt;bytes&lt;/code&gt;, &lt;code&gt;execution_ms&lt;/code&gt;, and &lt;code&gt;id&lt;/code&gt;. Set &lt;code&gt;full_page: false&lt;/code&gt; with a fixed &lt;code&gt;height&lt;/code&gt; (240 to 1080) for a viewport shot, add &lt;code&gt;delay_ms&lt;/code&gt; (up to 10000) to wait for JavaScript-heavy content, or &lt;code&gt;selector&lt;/code&gt; to clip to a single element. Point at a &lt;code&gt;sk_test_&lt;/code&gt; key first, so migrating an existing integration costs nothing to prototype.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to choose
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Monthly reset waste, variable volume:&lt;/strong&gt; Grabbit (flat annual, no reset).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lowest per-grab rate, willing to script:&lt;/strong&gt; Browserless.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simple thumbnails, minimal budget:&lt;/strong&gt; Thum.io.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;First-party SDKs for a non-JS backend:&lt;/strong&gt; ScreenshotOne.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An AI agent needs to call it:&lt;/strong&gt; Grabbit or ScreenshotOne (both ship MCP).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If your comparison is really "managed cloud browser vs. a screenshot endpoint," see the &lt;a href="https://www.grabbit.live/blog/browserbase-alternative" rel="noopener noreferrer"&gt;Browserbase alternative breakdown&lt;/a&gt;. For a wider field than this table, read our &lt;a href="https://www.grabbit.live/blog/best-screenshot-api" rel="noopener noreferrer"&gt;honest comparison of the best screenshot APIs&lt;/a&gt;. For Grabbit's pricing, parameters, and agent onboarding, see &lt;a href="https://www.grabbit.live/screenshots-as-a-service" rel="noopener noreferrer"&gt;screenshots as a service&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://www.grabbit.live/blog/screenshot-api-alternatives" rel="noopener noreferrer"&gt;Grabbit blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>programming</category>
      <category>tools</category>
    </item>
    <item>
      <title>The Best Spec-Driven Development Tools in 2026 (Tested &amp; Compared)</title>
      <dc:creator>Nico Acosta</dc:creator>
      <pubDate>Wed, 12 Aug 2026 11:23:45 +0000</pubDate>
      <link>https://dev.to/braingrid/the-best-spec-driven-development-tools-in-2026-tested-compared-2cah</link>
      <guid>https://dev.to/braingrid/the-best-spec-driven-development-tools-in-2026-tested-compared-2cah</guid>
      <description>&lt;p&gt;Adding a spec-driven development tool does not make your development spec-driven. Most of them just generate a folder of markdown, hand it to the same agent that was already guessing, and call the ceremony a method.&lt;/p&gt;

&lt;p&gt;That gap is exactly what a builder in r/AI_Agents was circling this week when the whole "loop engineering" idea got picked apart. His verdict on the wave of new agent-workflow tooling was blunt: &lt;a href="https://www.reddit.com/r/AI_Agents/comments/1vlanp8/wait_am_i_just_an_idiot_or_is_all_the_talk_about/" rel="noopener noreferrer"&gt;Cron jobs with a shiny new UI are still cron jobs&lt;/a&gt;. He is right about most of them, and the same critique lands on half the "spec-driven" category. A markdown template with a shiny new UI is still a markdown template.&lt;/p&gt;

&lt;p&gt;So which of these tools actually change the outcome, and which just add paperwork? We use spec-driven development to ship our own product every day, so this is the field guide we wish existed: what each tool really does, who it fits, and the one axis that separates the ones that work from the ones that generate homework.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hypothesis: the spec is the easy half
&lt;/h2&gt;

&lt;p&gt;Here is the claim this guide is built to test. &lt;strong&gt;The value of a spec-driven development tool is not in writing the spec. It is in what happens after.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Writing a spec has never been the hard part. In an r/LLMDevs thread this week on where the real bottleneck sits, one builder described his workflow, having the AI write explicit input/output/behavior contracts for every function before implementing anything, and added the line that says everything: &lt;a href="https://www.reddit.com/r/LLMDevs/comments/1vh749h/comments/" rel="noopener noreferrer"&gt;90% of vibe coders still don't do it&lt;/a&gt;. Someone asked what prompt would backfill that discipline into an existing codebase. The reply was two words: it's called spec-driven development.&lt;/p&gt;

&lt;p&gt;That is the tell. People reinvent this practice from scratch, hit the wall, and only then learn it already has a name and a category of tools. The category is real. But most of the tools stop at the easy half. They help you produce a beautiful spec, then hand the spec to the same coding agent that will report "done" the moment it stops running, whether or not the build matches a single line you wrote.&lt;/p&gt;

&lt;p&gt;The tools worth your time are the ones that close the loop: spec, build, and then verify the build against the spec before anyone calls it done. Keep that axis in mind as we go through them.&lt;/p&gt;

&lt;h2&gt;
  
  
  GitHub Spec Kit
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/github/spec-kit" rel="noopener noreferrer"&gt;Spec Kit&lt;/a&gt; is the reference implementation of the category, an open-source CLI toolkit from GitHub that walks your coding agent through a structured pipeline: Constitution, Specify, Clarify, Plan, Tasks, Implement. It writes markdown templates that plug into Claude Code, Copilot, Gemini CLI, and most other agents, so it is agent-agnostic by design.&lt;/p&gt;

&lt;p&gt;Its strength is also its ceiling. Spec Kit is a scaffolding for the workflow, not a product that runs it for you. You get discipline and a shared vocabulary, which is genuinely valuable, but you are still driving the whole thing from a terminal, and the verification step is whatever your agent decides to do on its own. It fits engineers who want structure without a new IDE. If you have a repo and you are comfortable in the CLI, start here. We wrote a full &lt;a href="https://www.braingrid.ai/blog/github-spec-kit-tutorial-existing-project" rel="noopener noreferrer"&gt;Spec Kit walkthrough for an existing project&lt;/a&gt; if you want the hands-on version.&lt;/p&gt;

&lt;h2&gt;
  
  
  OpenSpec
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/Fission-AI/OpenSpec" rel="noopener noreferrer"&gt;OpenSpec&lt;/a&gt; is the lightweight answer to Spec Kit's ceremony. It breaks a high-level prompt into granular, ordered task lists and keeps a set of markdown files in sync as the agent works, so it stays out of your way. If Spec Kit felt like too much process for a solo project, OpenSpec is the trimmed-down version that still gives the agent a plan to follow instead of a vibe.&lt;/p&gt;

&lt;p&gt;The trade-off is that lighter also means less opinionated about what "done" means. You get a cleaner task breakdown and less overhead, and you give up the fuller lifecycle framing. It fits solo builders and small teams who want just enough structure to stop the agent from wandering. We put it head to head with Spec Kit in &lt;a href="https://www.braingrid.ai/blog/openspec-vs-spec-kit-vs-braingrid" rel="noopener noreferrer"&gt;OpenSpec vs Spec Kit vs BrainGrid&lt;/a&gt; if you want the direct comparison.&lt;/p&gt;

&lt;h2&gt;
  
  
  Kiro (and the spec-driven IDE camp)
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://kiro.dev" rel="noopener noreferrer"&gt;Kiro&lt;/a&gt;, Amazon's spec-driven IDE, represents the other end of the spectrum: a full VS Code-style environment built around specs. You describe requirements in natural language, Kiro generates user stories, design docs, and steering files, and you check off implementation against them inside the editor. It is the most "product" of the open toolkits, with interactive UI to track features from requirement to code.&lt;/p&gt;

&lt;p&gt;The cost is lock-in and weight. You are adopting an IDE, not a workflow you can bolt onto the tools you already use. For a developer who wants a native, self-contained spec-driven environment, that is a fair trade. For a non-technical founder, an IDE is still an IDE, with all the friction that implies. We compared its philosophy to ours in &lt;a href="https://www.braingrid.ai/blog/kiro-vs-braingrid-spec-driven-development" rel="noopener noreferrer"&gt;Kiro vs BrainGrid&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  BMAD and the framework crowd
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://github.com/bmad-code-org/BMAD-METHOD" rel="noopener noreferrer"&gt;BMAD-METHOD&lt;/a&gt; and the growing shelf of SDD frameworks (Spec Kitty, Agent OS, Tessl, and more) are for people who want to assemble their own pipeline from parts. They give you agent personas, planning phases, and templates you wire together yourself. Powerful in the right hands, and a rabbit hole in the wrong ones.&lt;/p&gt;

&lt;p&gt;This is where a warning belongs. The moment picking a framework becomes its own project, the tool has failed at its job. If you have spent a weekend comparing steering-doc syntaxes instead of shipping a feature, &lt;a href="https://www.braingrid.ai/blog/stop-shopping-for-spec-driven-development-tools" rel="noopener noreferrer"&gt;you are shopping for tools instead of building&lt;/a&gt;. The framework crowd fits engineers who genuinely enjoy building their own harness. Most builders do not, and should not have to.&lt;/p&gt;

&lt;h2&gt;
  
  
  The contract-and-API-spec category
&lt;/h2&gt;

&lt;p&gt;There is a second, older meaning of "spec-driven" that shows up in the same searches: API-first tools like &lt;a href="https://swagger.io/tools/swaggerhub/" rel="noopener noreferrer"&gt;SwaggerHub&lt;/a&gt;, &lt;a href="https://specmatic.in/" rel="noopener noreferrer"&gt;Specmatic&lt;/a&gt;, and &lt;a href="https://typespec.io" rel="noopener noreferrer"&gt;TypeSpec&lt;/a&gt;. These treat an OpenAPI or contract file as the source of truth and enforce it in CI before code ships. They are excellent, and they solve a different problem: the contract between services, not the requirement behind a feature. If your pain is "my agent keeps breaking the API between the frontend and backend," this is your aisle. If your pain is "my agent built the wrong thing confidently," it is not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where BrainGrid fits
&lt;/h2&gt;

&lt;p&gt;Everything above is strong at producing a spec. The question our own hypothesis forces is what happens next, and that is where we built &lt;a href="https://braingrid.ai" rel="noopener noreferrer"&gt;BrainGrid&lt;/a&gt; to be different.&lt;/p&gt;

&lt;p&gt;You describe an idea in plain English, and the Planning Agent turns it into a structured requirement with acceptance criteria, data models, and designs, asking clarifying questions and pushing back when the intent is vague. That is the part most tools stop at. Then the Builder Agent takes over: it runs the build against that spec, either in a managed cloud sandbox with a live preview or in your own GitHub repo through Claude Code, Cursor, or Codex over MCP. And then the part almost nobody automates happens. Verification checks the build against every acceptance criterion, and the feature is not done until the evidence says it matches what you asked for.&lt;/p&gt;

&lt;p&gt;That closes the loop the r/codex thread was worried about this week, where builders debated whether smarter agents still need review and landed on a hard truth: &lt;a href="https://www.reddit.com/r/codex/comments/1vkscm1/apparently_now_that_codexcc_are_getting_better_we/" rel="noopener noreferrer"&gt;they do not own mistakes&lt;/a&gt;, so someone still has to prove the work is right. A spec you can verify against is how you prove it without reading every line of code yourself. The spec is not paperwork. It is the standard the build gets checked against.&lt;/p&gt;

&lt;h2&gt;
  
  
  Old way vs new way
&lt;/h2&gt;

&lt;p&gt;The difference between using a spec-driven tool and actually doing spec-driven development comes down to one habit.&lt;/p&gt;

&lt;p&gt;Old way: "Build me a CRM with React." The agent generates something, reports success, and you find out at feature four that the auth logic conflicts with the state management it wrote in feature two.&lt;/p&gt;

&lt;p&gt;New way: a requirement that says a contact-management view displays contacts in a sortable table with name, company, and last-contacted date; clicking a row opens a detail panel; only authenticated users can access it; unauthenticated users redirect to /login. Every one of those lines is a criterion the build can be checked against. The agent still moves fast. It just cannot quietly ship the wrong thing.&lt;/p&gt;

&lt;p&gt;The tool that generates the second version for you is doing useful work. The tool that also verifies the build against it is doing the whole job.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changes for you
&lt;/h2&gt;

&lt;p&gt;If you are building a SaaS product with an AI coding agent right now, here is the concrete implication. Adopting any tool on this list will make your third and fourth features less likely to break, because the agent finally has a plan and a shared definition of the feature. That alone is worth it.&lt;/p&gt;

&lt;p&gt;But do not stop at the spec. The failure mode this whole category exists to fix is not "the agent had no plan." It is "the agent said done and it wasn't." Pick your tool on the verification axis. If two tools produce equally good specs, the one that checks the build against the spec is the one that saves you the afternoon you would have spent discovering the gap in production.&lt;/p&gt;

&lt;p&gt;And the honest trade-off: spec-driven development is slower on feature one. You are front-loading the thinking. The payback comes at feature five, when your codebase is still coherent and your agent is still building the thing you asked for instead of the thing it guessed. If you are shipping a one-day throwaway, skip all of it and vibe. If you are building something real, the spec is the cheapest insurance you will buy.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is spec-driven development?
&lt;/h3&gt;

&lt;p&gt;Spec-driven development is a workflow where you write a structured specification, requirements, constraints, and acceptance criteria, before an AI agent writes code, and the spec becomes the source of truth the build is measured against. It replaces "prompt, hope, and patch" with "specify, build, and verify." The point is not the document. It is that a clear spec gives the agent something concrete to build toward and something concrete to be checked against. We cover the full method in &lt;a href="https://www.braingrid.ai/blog/spec-driven-development" rel="noopener noreferrer"&gt;Spec-Driven Development: Ship Reliable Software Faster with AI&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  What are the best spec-driven development tools?
&lt;/h3&gt;

&lt;p&gt;For agent-agnostic structure from the CLI, GitHub Spec Kit is the reference tool. For a lighter task-focused workflow, OpenSpec. For a full spec-driven IDE, Kiro. For assembling your own pipeline, frameworks like BMAD. For API contracts specifically, SwaggerHub or TypeSpec. And for a workflow that plans, builds, and then verifies the build against acceptance criteria in one loop, BrainGrid. The right pick depends on whether you want scaffolding you drive yourself or a system that closes the loop for you.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the difference between spec-driven development tools?
&lt;/h3&gt;

&lt;p&gt;They differ mainly on two axes: how much they do for you, and whether they verify the result. Spec Kit and OpenSpec are scaffolding you drive from a terminal. Kiro is a full IDE. Framework kits like BMAD are parts you assemble. Most of them stop after generating the spec and planning the work. The meaningful divide is verification: does the tool check the finished build against the spec, or does it trust the agent's own "done"?&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the difference between BDD and spec-driven development?
&lt;/h3&gt;

&lt;p&gt;Behavior-driven development (BDD) focuses on describing expected behavior as executable scenarios ("given, when, then") that become automated tests. Spec-driven development is broader: the spec covers requirements, constraints, data models, and acceptance criteria, and drives both the build and its verification, not only the test suite. BDD scenarios can live inside a spec-driven workflow as one form of acceptance criteria. SDD is the wider frame; BDD is one technique for expressing part of it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need a spec-driven tool, or can I just write a good prompt?
&lt;/h3&gt;

&lt;p&gt;A single good prompt works for a single small feature. It stops working once a project has many moving parts, because nothing carries your intent from one session to the next and nothing checks the result. A spec-driven tool exists to make that intent durable and verifiable. If you keep re-explaining your app to the agent every session, or keep discovering broken features you thought were done, you have outgrown prompting and the category is for you.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://braingrid.ai" rel="noopener noreferrer"&gt;BrainGrid&lt;/a&gt; is the AI Product Planner that turns your idea into a spec your coding agent can build against, then verifies the build matches it before you call it done. Try it at &lt;a href="https://braingrid.ai" rel="noopener noreferrer"&gt;braingrid.ai&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://www.braingrid.ai/blog/spec-driven-development-tools" rel="noopener noreferrer"&gt;BrainGrid blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>productivity</category>
    </item>
    <item>
      <title>How to Verify AI-Generated Code (Before It Breaks Production)</title>
      <dc:creator>Nico Acosta</dc:creator>
      <pubDate>Tue, 11 Aug 2026 11:17:15 +0000</pubDate>
      <link>https://dev.to/braingrid/how-to-verify-ai-generated-code-before-it-breaks-production-2h0h</link>
      <guid>https://dev.to/braingrid/how-to-verify-ai-generated-code-before-it-breaks-production-2h0h</guid>
      <description>&lt;p&gt;The agent said it was done. It ran the app, showed you a green result, and wrote a tidy summary of what it built. None of that tells you whether the code does what you asked. Those are two different questions, and the gap between them is where production breaks.&lt;/p&gt;

&lt;p&gt;Here is the hypothesis this guide rests on: verifying AI-generated code is not reading it line by line, it is checking it against a target you defined before the build. The more of your building an agent does, the less "I read it and it looked fine" scales, and the more you need a repeatable way to prove the change matches intent. The good news is that this is a learnable process, not a talent. The bad news is that almost nobody is taught it, so most builders default to eyeballing a demo and hoping.&lt;/p&gt;

&lt;h2&gt;
  
  
  "It runs" is not "it works"
&lt;/h2&gt;

&lt;p&gt;An AI coding agent optimizes for the shortest path to something that looks complete. Ask for a login flow and it will build the happy path, run it once, watch a user log in, and call that done. It is not lying. It genuinely did produce a thing that works, for the one case it tried. What it did not do is check the wrong password, the expired session, the empty email field, or the second user whose data must not leak into the first user's account. Those are the cases that surface in production, at the worst possible time, in front of a real person.&lt;/p&gt;

&lt;p&gt;This is the distinction &lt;a href="https://x.com/stas_sorokin_/status/2083480323476656211" rel="noopener noreferrer"&gt;Stanislav Sorokin named cleanly&lt;/a&gt;, writing about agent runs on X:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Completion is a runtime event. Success is a verified state.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is the whole problem in nine words. The agent stopping is a fact about the loop. Whether it succeeded is a separate fact that has to be established on purpose, and the agent will not establish it for you unless you told it what success means. Verification is how you turn "it stopped" into "it works," and it is the step the demo quietly skips.&lt;/p&gt;

&lt;h2&gt;
  
  
  The five checks that actually catch things
&lt;/h2&gt;

&lt;p&gt;When developers on &lt;a href="https://www.reddit.com/r/ExperiencedDevs/comments/1rzq738/what_tools_and_techniques_are_you_using_to_verify/" rel="noopener noreferrer"&gt;r/ExperiencedDevs compared notes&lt;/a&gt; on verifying AI code, the useful answers were not "read every line." They were specific, repeatable checks. Here is the shortlist that catches the most, in the order that catches it fastest.&lt;/p&gt;

&lt;p&gt;Start with intent, not code. Before you look at a single function, restate what the change was supposed to do and confirm the code is even solving that problem. Agents drift: you asked for a filter and got a sort, or the feature works but silently changed an unrelated screen. Catching a wrong-problem build here saves you from carefully reviewing code that was never going to be right.&lt;/p&gt;

&lt;p&gt;Run the tests, and then read them. A passing suite feels like proof until you notice the agent wrote tests that assert the happy path and nothing else. The test that matters is the one for the case you are worried about, and if it is missing, the green checkmark is meaningless. Read what the tests actually assert before you trust that they passed.&lt;/p&gt;

&lt;p&gt;Exercise the edge cases by hand. Wrong password. Empty input. The second concurrent user. The thing that was working yesterday. Agents are strong on the path they were shown and weak on the paths they were not, so five minutes of deliberately trying to break it finds more than an hour of reading finds.&lt;/p&gt;

&lt;p&gt;Check it in a real environment, not the demo. &lt;a href="https://www.linkedin.com/posts/addyosmani_ai-programming-softwareengineering-activity-7338086913477627905-hO2w" rel="noopener noreferrer"&gt;Addy Osmani put the rule plainly&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Always test AI-generated code in a sandbox before merging.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;A demo runs on the machine that just built it, with the data that happens to be loaded. A sandbox that looks like production surfaces the hardcoded path, the missing environment variable, and the assumption that only holds on the developer's laptop. This is exactly where the "it works on my machine" class of bug lives.&lt;/p&gt;

&lt;p&gt;Diff for collateral damage. AI agents edit more than you asked. Look at the full change, not just the new feature, and ask what else moved. A refactor that "cleaned up" a shared function may have broken three callers you did not think to test.&lt;/p&gt;

&lt;p&gt;Notice what these have in common: not one of them is "understand every line the agent wrote." You verify against behavior and intent, not against a code-reading marathon you do not have time for. That is the shift. You are not auditing the agent's homework, you are proving the result.&lt;/p&gt;

&lt;h2&gt;
  
  
  Verification needs a target, and the target is the receipt
&lt;/h2&gt;

&lt;p&gt;Here is the reframe. Every one of those checks assumes you already know what "correct" looks like. The edge cases you test, the behavior you confirm, the intent you check against, all of it depends on a &lt;a href="https://www.braingrid.ai/blog/definition-of-done-for-ai-builders" rel="noopener noreferrer"&gt;definition of done&lt;/a&gt; that existed before the build started. If that definition lives only in your head, verification becomes a vibe, and a vibe does not scale past the third feature.&lt;/p&gt;

&lt;p&gt;A builder in &lt;a href="https://www.reddit.com/r/ClaudeCode/comments/1vdmg1n/" rel="noopener noreferrer"&gt;r/ClaudeCode described the fix&lt;/a&gt; without naming a product:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You need a receipt for each agent. That receipt, verified, becomes the record.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is the missing piece. A receipt is a checkable claim: "this feature is done because these specific conditions were confirmed." Written before the build, it is the &lt;a href="https://www.braingrid.ai/blog/how-to-write-acceptance-criteria-ai-agent-can-verify" rel="noopener noreferrer"&gt;acceptance criteria the agent builds toward&lt;/a&gt;. Verified after, it is the evidence that the build matches intent. Without receipts, you are left staring at a diff trying to reconstruct what the change was supposed to prove. With them, verification is mechanical: check the change against each criterion, and a criterion either passed or it did not.&lt;/p&gt;

&lt;p&gt;Compare the two ways of asking an agent to be done.&lt;/p&gt;

&lt;p&gt;Vague: "Add login and make sure it works."&lt;/p&gt;

&lt;p&gt;With a receipt: "Add email and password login. A valid pair signs the user in and redirects to /dashboard. A wrong password shows an inline error and does not redirect. An empty field is rejected before submit. A logged-in user hitting /login is redirected to /dashboard. Only the authenticated user's own records are returned."&lt;/p&gt;

&lt;p&gt;The first gives the agent nothing to verify against, so it invents its own bar and clears it. The second is five checkable statements. The agent builds toward them, and you (or a verifier) confirm each one with evidence instead of opinion.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where BrainGrid fits
&lt;/h2&gt;

&lt;p&gt;This is the layer &lt;a href="https://braingrid.ai" rel="noopener noreferrer"&gt;BrainGrid&lt;/a&gt; works in. You describe a feature in plain language and the Planning Agent turns it into structured requirements with acceptance criteria written to be checkable, the "when this, the system shall that" statements that double as the receipt. Then the Builder Agent builds against those criteria, in a BrainGrid-managed cloud sandbox with a live preview, or in your own GitHub repo through Claude Code, Cursor, or Codex over MCP. A feature is not marked done until verification checks the build against every criterion and the evidence says it matches intent.&lt;/p&gt;

&lt;p&gt;That closes &lt;a href="https://www.braingrid.ai/loop" rel="noopener noreferrer"&gt;the loop&lt;/a&gt; the five checks are reaching for. Plan the receipt, build against it, verify with evidence, repeat. The intent is captured as a durable record instead of a sentence in a chat window that scrolls away, so the thing you verify against outlives the session that created it. Everyone else sells speed of generation. The point of a receipt is certainty of outcome.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart LR
  A["Agent says&amp;lt;br/&amp;gt;'done'"] --&amp;gt; B{"Verify against&amp;lt;br/&amp;gt;what?"}
  B --&amp;gt;|"No receipt"| C["Eyeball the demo,&amp;lt;br/&amp;gt;hope"]
  C --&amp;gt; D["Bug surfaces&amp;lt;br/&amp;gt;in production"]
  B --&amp;gt;|"Receipt exists"| E["Check each&amp;lt;br/&amp;gt;acceptance criterion"]
  E --&amp;gt; F["Evidence per&amp;lt;br/&amp;gt;criterion"]
  F --&amp;gt; G["Done means&amp;lt;br/&amp;gt;verified"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The trade-off worth naming
&lt;/h2&gt;

&lt;p&gt;Verification is not free. Writing acceptance criteria before the build and checking them after is real work, and for a throwaway script or a weekend experiment it is overkill. If you are prototyping to learn whether an idea is worth pursuing, skip the receipts and move fast. The cost is justified the moment the code is going to be used by someone other than you, or is going to be built on top of next week. That is the line: verify what has to be trusted, not what you are about to throw away. Applying the full process to a one-off is how you make AI building feel slower than it should.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this changes for you
&lt;/h2&gt;

&lt;p&gt;If you are building a SaaS product with Claude Code or Cursor right now, this means your verification bottleneck is not the reading, it is the missing target. The reason &lt;a href="https://www.braingrid.ai/blog/why-reviewing-ai-code-costs-more-than-writing-it" rel="noopener noreferrer"&gt;reviewing AI code feels endless&lt;/a&gt; is that you are reconstructing intent at review time instead of defining it at plan time. Move that definition earlier. Write down what done means as checkable statements before the agent starts, and the after-the-fact review collapses from "read everything and hope I catch it" into "confirm these specific things passed." You pay the clarity cost once, up front, instead of paying the review tax on every feature forever.&lt;/p&gt;

&lt;h2&gt;
  
  
  Action steps
&lt;/h2&gt;

&lt;p&gt;For your next AI-built feature, run this sequence:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Before the build, write 3 to 6 checkable statements of what "done" means, including the edge cases you are worried about.&lt;/li&gt;
&lt;li&gt;Have the agent build against those statements, not a one-line prompt.&lt;/li&gt;
&lt;li&gt;Run the tests, then read what they assert, and add the missing edge-case test.&lt;/li&gt;
&lt;li&gt;Exercise the failure paths by hand: wrong input, empty input, second user, the thing that worked yesterday.&lt;/li&gt;
&lt;li&gt;Test in a production-like environment, never just the demo that built it.&lt;/li&gt;
&lt;li&gt;Diff the full change and confirm nothing outside the feature moved.&lt;/li&gt;
&lt;li&gt;Check the result against each statement from step one. Anything not confirmed is not done.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  How do you validate AI-generated code?
&lt;/h3&gt;

&lt;p&gt;Validate it against a definition of "done" you wrote before the build, not by reading every line after. Confirm the code solves the intended problem, run and read the tests, exercise the edge and failure cases by hand, test it in a production-like sandbox rather than the demo environment, and diff the full change for edits outside the feature. Each check confirms behavior against intent instead of auditing the agent's code line by line.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is there a way to identify what an AI agent actually changed?
&lt;/h3&gt;

&lt;p&gt;Yes, and you should always look. Review the complete diff, not just the new feature the agent describes. AI coding agents frequently edit shared functions, refactor unrelated code, or adjust configuration as a side effect. Reading the full set of changes is how you catch collateral damage in code the agent did not mention in its summary.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why does AI-generated code pass tests but still break in production?
&lt;/h3&gt;

&lt;p&gt;Because the agent often writes tests that assert only the happy path it already built, so the suite passes without covering the cases that fail. A green checkmark proves the tests that exist passed, not that the right tests exist. Read what the tests actually assert, and add coverage for the edge cases and failure paths you care about before trusting the result.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I need to read AI-generated code to verify it?
&lt;/h3&gt;

&lt;p&gt;Not line by line. Effective verification checks behavior against a defined target: does it do what you asked, does it handle the cases you specified, does it work in a real environment. You will read specific code when a check fails and you need to find out why, but the goal is proving the result against acceptance criteria, not understanding every function the agent wrote.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the fastest way to verify AI code before merging?
&lt;/h3&gt;

&lt;p&gt;Test it in a sandbox that resembles production, then check the change against the acceptance criteria you defined before the build. If those criteria exist, verification is mechanical: each one either passed with evidence or it did not. If they do not exist, you are reconstructing intent at review time, which is exactly why reviewing AI code so often takes longer than writing it.&lt;/p&gt;

&lt;p&gt;The shift is simple to state and hard to skip: define what done means before the agent builds, and verification stops being a guess. It works because it gets more necessary as models get better, not less. More autonomy means more decisions nobody wrote down, and evidence is how you trust what you did not read.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://braingrid.ai" rel="noopener noreferrer"&gt;BrainGrid&lt;/a&gt; is the plan-first app-building platform that turns your idea into checkable requirements, builds against them, and verifies every one with evidence. Try it at &lt;a href="https://braingrid.ai" rel="noopener noreferrer"&gt;braingrid.ai&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://www.braingrid.ai/blog/how-to-verify-ai-generated-code" rel="noopener noreferrer"&gt;BrainGrid blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Browserbase Alternative: When You Need a Screenshot, Not a Whole Cloud Browser</title>
      <dc:creator>Nico Acosta</dc:creator>
      <pubDate>Mon, 10 Aug 2026 11:43:50 +0000</pubDate>
      <link>https://dev.to/grabbit/browserbase-alternative-when-you-need-a-screenshot-not-a-whole-cloud-browser-dc1</link>
      <guid>https://dev.to/grabbit/browserbase-alternative-when-you-need-a-screenshot-not-a-whole-cloud-browser-dc1</guid>
      <description>&lt;p&gt;If you are searching for a Browserbase alternative, the first question worth asking is not "which vendor," it is "do I actually need a cloud browser?" Browserbase gives your code a full, programmable Chrome running in the cloud: persistent sessions, live interaction, stealth, the works. That is the right tool when you are driving a browser through multi-step flows. It is a lot of machinery when all you needed was a picture of a page.&lt;/p&gt;

&lt;p&gt;This is an honest comparison, including an explicit section on when you should stay on Browserbase.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short answer
&lt;/h2&gt;

&lt;p&gt;Browserbase is cloud browser infrastructure for AI agents and automation: you open a session, connect over CDP or an SDK, and operate a real browser remotely. It is built to run whole interaction flows, and it bills accordingly (by session and concurrency).&lt;/p&gt;

&lt;p&gt;If your job is narrower, capture a rendered screenshot of a URL, then a &lt;strong&gt;screenshot API&lt;/strong&gt; is the simpler and usually cheaper alternative. You send one request with a URL and get back a hosted image. There is no session to open, keep alive, or tear down.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.grabbit.live/screenshot-api" rel="noopener noreferrer"&gt;Grabbit&lt;/a&gt; is that alternative when the job is a screenshot: &lt;strong&gt;$0.002 per capture&lt;/strong&gt; on flat prepaid credits that never reset or expire monthly, plus a built-in MCP server so an agent can call it as a tool. Grabbit is not a cloud browser and does not pretend to be one, and this post says plainly below when Browserbase is the correct choice.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two different jobs that look similar
&lt;/h2&gt;

&lt;p&gt;The reason "Browserbase alternative" is a confusing search is that a screenshot and a browser session look related but are different jobs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;A cloud browser&lt;/strong&gt; is a live, stateful thing. You navigate, click, type, wait, read the DOM, maybe hand control to a human. You are &lt;em&gt;operating&lt;/em&gt; the page. Browserbase, Browserless, Hyperbrowser, Anchor Browser, and Steel all sell this.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A screenshot API&lt;/strong&gt; is a stateless request. You name a URL and how you want it rendered, and you get an image. You are &lt;em&gt;observing&lt;/em&gt; the page, not operating it.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most of the "Browserbase alternatives" you will find in a SERP are other cloud browsers, which makes sense if you truly need to drive a browser. But a large share of the traffic to that search is people who reached for a cloud browser because they thought a screenshot required one. It does not.&lt;/p&gt;

&lt;h2&gt;
  
  
  How they compare for the screenshot job
&lt;/h2&gt;

&lt;p&gt;Scoped to "I need a rendered image of a URL," not to full browser automation. Cloud-browser pricing is session or minute based, so a per-grab comparison is only fair for the capture use case. Prices verified August 2026 from each provider's public pricing page.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tool&lt;/th&gt;
&lt;th&gt;What it is&lt;/th&gt;
&lt;th&gt;Billing shape&lt;/th&gt;
&lt;th&gt;Best when&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Grabbit&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Screenshot API&lt;/td&gt;
&lt;td&gt;Per grab ($0.002), flat prepaid&lt;/td&gt;
&lt;td&gt;You need a rendered image of a URL&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Browserbase&lt;/td&gt;
&lt;td&gt;Cloud browser&lt;/td&gt;
&lt;td&gt;Per session / concurrency&lt;/td&gt;
&lt;td&gt;You drive a real browser through steps&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Browserless&lt;/td&gt;
&lt;td&gt;Cloud browser + REST screenshot&lt;/td&gt;
&lt;td&gt;Per unit / monthly&lt;/td&gt;
&lt;td&gt;You want full control and self-host options&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hyperbrowser&lt;/td&gt;
&lt;td&gt;Cloud browser (containers)&lt;/td&gt;
&lt;td&gt;Per session / monthly&lt;/td&gt;
&lt;td&gt;Lightweight isolated sessions, CAPTCHA handling&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The honest read: if you need persistent sessions and live interaction, do not pick a screenshot API, pick a cloud browser. If you need a picture of a page, do not rent a whole browser to take it. Grabbit is also &lt;strong&gt;not the cheapest screenshot API&lt;/strong&gt; in the field; Browserless and Thum.io list lower per-grab rates. Grabbit's edge is the fit and the billing model, prepaid credits that never expire, not a bottom-of-market rate. For the wider field of screenshot APIs specifically, see our &lt;a href="https://www.grabbit.live/blog/best-screenshot-api" rel="noopener noreferrer"&gt;honest comparison of the best screenshot APIs&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the billing shapes differ
&lt;/h2&gt;

&lt;p&gt;This is the part that makes the cost comparison feel unfair until you see it: you are paying for different things.&lt;/p&gt;

&lt;p&gt;A cloud browser holds a real Chrome open for you. You pay for the time that browser is alive and for how many you run at once, because keeping browsers healthy at concurrency is the expensive, hard part (memory creep, restart storms, sticky workers). That cost is real and Browserbase absorbs it so you do not lose weekends to it.&lt;/p&gt;

&lt;p&gt;A screenshot API runs the render, uploads the image, and is done. There is no session to keep warm between calls, so it bills per capture. For a workload that is "render this URL, give me the image," paying for session time is paying for a capability you are not using.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it before you commit
&lt;/h2&gt;

&lt;p&gt;You can wire up Grabbit and run it in CI before adding a card. Every account gets a &lt;strong&gt;test key&lt;/strong&gt; (&lt;code&gt;sk_test_...&lt;/code&gt;) that returns a placeholder image at no charge; switch to a &lt;strong&gt;live key&lt;/strong&gt; (&lt;code&gt;sk_live_...&lt;/code&gt;) when you want real captures. Here is a real request against Grabbit's API:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://api.grabbit.live/v1/grabs &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer sk_live_..."&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "url": "https://www.browserbase.com",
    "width": 1280,
    "height": 720,
    "format": "webp",
    "full_page": true
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response includes &lt;code&gt;image_url&lt;/code&gt; (the hosted screenshot), &lt;code&gt;bytes&lt;/code&gt;, &lt;code&gt;execution_ms&lt;/code&gt;, and &lt;code&gt;id&lt;/code&gt;. Use &lt;code&gt;full_page: true&lt;/code&gt; for a scrolling capture, &lt;code&gt;delay_ms&lt;/code&gt; (up to 10000) to wait for JavaScript-heavy content to settle, or &lt;code&gt;selector&lt;/code&gt; to clip to a single element. Because test captures are free placeholders, prototyping the swap from a cloud-browser screenshot call to Grabbit costs nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  For AI agents
&lt;/h2&gt;

&lt;p&gt;Many people arrive at Browserbase because an agent needs to work with the web, and "the agent needs the web" gets rounded up to "the agent needs a whole browser." Sometimes it does: if the agent clicks, types, and navigates across steps, that is a cloud-browser job and Browserbase (or an agent framework on top of it) is the right call.&lt;/p&gt;

&lt;p&gt;But a large slice of agent-web work is just &lt;em&gt;seeing&lt;/em&gt; a page: feed the rendered view into a vision step, verify your own frontend, or ground the model in what a URL actually looks like. For that, a screenshot API is the lighter path. Grabbit ships an &lt;strong&gt;MCP server&lt;/strong&gt;, so the agent registers capture as a callable tool and gets back a hosted &lt;code&gt;image_url&lt;/code&gt; to pass downstream, with no browser session to manage. For the agent-eyes use case in depth, see &lt;a href="https://www.grabbit.live/blog/screenshots-for-ai-agents" rel="noopener noreferrer"&gt;screenshots for AI agents&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to stay on Browserbase
&lt;/h2&gt;

&lt;p&gt;An honest comparison names when not to switch:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;You need multi-step interaction.&lt;/strong&gt; Logging in, filling forms, clicking through a flow, reading the DOM between steps. A screenshot API captures a page; it cannot operate one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You need persistent, authenticated sessions.&lt;/strong&gt; Holding a logged-in browser open across calls is exactly what a cloud browser is for.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You use an interactive agent framework.&lt;/strong&gt; Tools like Stagehand drive a real browser. A screenshot endpoint is not a substitute.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You need live takeover or stealth.&lt;/strong&gt; Human-in-the-loop control and advanced anti-bot handling are cloud-browser features, not screenshot-API features.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Grabbit's pitch is not "we replace Browserbase." It is narrower and honest: if the job is a screenshot, you do not need a whole cloud browser to take it, and paying per session for a per-image job is waste.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to choose
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;You need a rendered image of a URL:&lt;/strong&gt; a screenshot API (Grabbit, or a cheaper-per-grab option if raw rate is all that matters).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You drive a browser through multi-step flows:&lt;/strong&gt; stay on Browserbase.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You want a cloud browser but with self-host options:&lt;/strong&gt; Browserless.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your agent needs to see, not operate, a page:&lt;/strong&gt; Grabbit (one call + MCP).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your agent needs to click and navigate:&lt;/strong&gt; Browserbase or an interactive framework.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For Grabbit's pricing, parameters, and agent onboarding, see the &lt;a href="https://www.grabbit.live/screenshot-api" rel="noopener noreferrer"&gt;screenshot API&lt;/a&gt; page. To schedule captures without running any browser yourself, see &lt;a href="https://www.grabbit.live/blog/screenshot-automation" rel="noopener noreferrer"&gt;screenshot automation&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://www.grabbit.live/blog/browserbase-alternative" rel="noopener noreferrer"&gt;Grabbit blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>programming</category>
      <category>tools</category>
    </item>
    <item>
      <title>EARS Notation, Explained: The Requirements Syntax Behind Every AI Spec</title>
      <dc:creator>Nico Acosta</dc:creator>
      <pubDate>Mon, 10 Aug 2026 11:14:29 +0000</pubDate>
      <link>https://dev.to/braingrid/ears-notation-explained-the-requirements-syntax-behind-every-ai-spec-28f1</link>
      <guid>https://dev.to/braingrid/ears-notation-explained-the-requirements-syntax-behind-every-ai-spec-28f1</guid>
      <description>&lt;p&gt;A requirements syntax invented in 2009 for jet engine software is quietly becoming the house style for AI coding tools. It was designed for a room full of engineers arguing over a spec. Its best audience turned out to be a machine that never argues.&lt;/p&gt;

&lt;p&gt;EARS notation, short for the Easy Approach to Requirements Syntax, came out of Rolls-Royce when &lt;a href="https://alistairmavin.com/ears/" rel="noopener noreferrer"&gt;Alistair Mavin&lt;/a&gt; and colleagues got tired of aerospace requirements that read like legal fog. The &lt;a href="https://ieeexplore.ieee.org/document/5328509/" rel="noopener noreferrer"&gt;2009 IEEE paper&lt;/a&gt; has been cited over 500 times. For fifteen years it lived in systems engineering, the discipline that writes requirements for things that kill people when they fail. Then the AI tools arrived, and EARS started showing up where nobody in 2009 would have predicted: in the specs that coding agents read before they build.&lt;/p&gt;

&lt;h2&gt;
  
  
  What EARS notation actually is
&lt;/h2&gt;

&lt;p&gt;EARS is a constraint on how you write a requirement, not a new document format or a tool you install. The idea is that most bad requirements are bad in the same few ways, so you give authors a small set of sentence templates that make the common mistakes impossible. Every EARS requirement follows the same clause order:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;While &lt;code&gt;&amp;lt;precondition&amp;gt;&lt;/code&gt;, when &lt;code&gt;&amp;lt;trigger&amp;gt;&lt;/code&gt;, the &lt;code&gt;&amp;lt;system&amp;gt;&lt;/code&gt; shall &lt;code&gt;&amp;lt;response&amp;gt;&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is the whole grammar. The clauses always appear in that order, most are optional, and the word "shall" carries the actual behavior. From that one skeleton, five patterns cover almost everything you need to say.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;ubiquitous&lt;/strong&gt; requirement is always true, no condition attached: "The system shall encrypt all stored passwords." An &lt;strong&gt;event-driven&lt;/strong&gt; requirement fires on a trigger: "When a user submits the signup form, the system shall send a verification email." A &lt;strong&gt;state-driven&lt;/strong&gt; requirement holds while something is true: "While a payment is processing, the system shall disable the submit button." An &lt;strong&gt;unwanted-behavior&lt;/strong&gt; requirement handles the sad path, the one everyone forgets: "If the payment provider returns an error, then the system shall display the failure reason and preserve the cart." And an &lt;strong&gt;optional-feature&lt;/strong&gt; requirement scopes to a configuration: "Where two-factor authentication is enabled, the system shall require a code on login."&lt;/p&gt;

&lt;p&gt;Read those back. Notice what the template forced you to do. You named the exact trigger, the exact state, the exact system, and the exact response. There is no room for "the app should probably handle errors gracefully," because the grammar has no slot for "probably" or "gracefully." That constraint is the entire point.&lt;/p&gt;

&lt;h2&gt;
  
  
  The reframe: it was built for humans, it pays off with agents
&lt;/h2&gt;

&lt;p&gt;Here is the hypothesis this post rests on. EARS was designed to make requirements unambiguous for a human reader, and it did that job for fifteen years. But its value goes up, not down, the moment the reader is an AI agent, because an agent takes the sentence literally in a way a human colleague never quite does.&lt;/p&gt;

&lt;p&gt;When a human reads "the system should handle invalid input," they fill the gap with judgment. They have seen a hundred forms, they know what validation usually means, and they quietly do the reasonable thing. That gap-filling is a feature when the reader is a senior engineer and a liability when the reader is an agent, because the agent also fills the gap, just not necessarily with your version of reasonable. It picks a plausible interpretation, writes code against it, and the demo runs. The mismatch surfaces three weeks later when a real user pastes an emoji into a field you never specified.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/github/spec-kit/issues/1356" rel="noopener noreferrer"&gt;GitHub Spec Kit team is actively debating EARS integration&lt;/a&gt; for exactly this reason. The issue puts it plainly: EARS gives authors sentence templates that help them write specifications AI agents can parse more reliably. Amazon's Kiro already generates its acceptance criteria in EARS by default, &lt;a href="https://www.braingrid.ai/blog/kiro-vs-braingrid-spec-driven-development" rel="noopener noreferrer"&gt;one of two very different takes on spec-driven development&lt;/a&gt;. The tools converging here are not doing it out of nostalgia for a 2009 aerospace standard. They are doing it because a constrained sentence is a better instruction to a literal machine than a fluent paragraph is.&lt;/p&gt;

&lt;p&gt;This is the same lesson the AI building community keeps rediscovering in its own words. Steinberger's line that your job is to &lt;a href="https://www.braingrid.ai/blog/loop-engineering" rel="noopener noreferrer"&gt;design the loops that prompt your agents&lt;/a&gt; is really a statement about inputs: the loop is only as good as the target you hand it. EARS is one disciplined way to write that target so the agent and the verifier read it the same way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vague requirement versus EARS requirement
&lt;/h2&gt;

&lt;p&gt;The difference is easiest to see side by side. Take a single feature, password reset, written the way it usually reaches an agent versus written in EARS.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Vague: "Users should be able to reset their password securely."&lt;/p&gt;

&lt;p&gt;EARS: "When a user requests a password reset, the system shall send a reset link to the account email within one minute. The reset link shall expire 30 minutes after issue. When a user opens a valid reset link, the system shall allow a new password to be set. If a user opens an expired or already-used link, then the system shall display 'This link is no longer valid' and offer to send a new one. When a password reset completes, the system shall invalidate all existing sessions for that account."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The first version is a wish. It has one word, "securely," doing an enormous amount of undefined work. The second version is a set of facts you can check. Each "shall" is either true of the running app or it is not, with no room to argue. An agent can build against it, and just as important, a verifier can measure the result against it without asking anyone what "securely" was supposed to mean.&lt;/p&gt;

&lt;p&gt;That is the connection most explainers miss. A well-formed EARS requirement is already an acceptance criterion. The syntax that makes intent unambiguous is the same syntax that makes "done" checkable. Write the requirement in EARS and you have not just specified the feature, you have specified the test.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flowchart LR
  A["Vague intent:&amp;lt;br/&amp;gt;'handle errors gracefully'"] --&amp;gt; B["Agent fills the gap&amp;lt;br/&amp;gt;with a guess"]
  B --&amp;gt; C["Demo runs,&amp;lt;br/&amp;gt;looks done"]
  C --&amp;gt; D["Bug surfaces&amp;lt;br/&amp;gt;in production"]
  E["EARS requirement:&amp;lt;br/&amp;gt;'If X, then the system&amp;lt;br/&amp;gt;shall do Y'"] --&amp;gt; F["Agent builds against&amp;lt;br/&amp;gt;a literal target"]
  F --&amp;gt; G["Verifier checks&amp;lt;br/&amp;gt;each 'shall'"]
  G --&amp;gt; H["Done means&amp;lt;br/&amp;gt;verified"]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Where BrainGrid fits
&lt;/h2&gt;

&lt;p&gt;This is the layer &lt;a href="https://braingrid.ai" rel="noopener noreferrer"&gt;BrainGrid&lt;/a&gt; works in. You describe a feature in plain language, "let users reset their password," and the Planning Agent turns it into structured requirements with acceptance criteria written to be checkable, the EARS-shaped "when this, the system shall that" statements rather than a paragraph of hope. Then the Builder Agent, working in a cloud sandbox or in your own GitHub repo through Claude Code, Cursor, or Codex, builds against those criteria, and a feature is not done until each one is verified with evidence.&lt;/p&gt;

&lt;p&gt;The point is not that you must memorize five patterns and hand-write "shall" sentences all day. Most builders never learn the acronym, and they should not have to. The point is that the discipline EARS encodes, name the trigger, name the system, name the exact response, cover the unwanted path, is exactly the discipline that separates a spec an agent can build reliably from a prompt it has to guess at. BrainGrid captures that intent as a durable record so the plan outlives the chat window, instead of decaying the moment the context fills up.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trade-off worth naming
&lt;/h2&gt;

&lt;p&gt;EARS is not free, and pretending it is would be dishonest. Constrained syntax is slower to write than a fluent sentence, and it can feel stiff, almost robotic, when you first switch to it. "When a user submits the form, the system shall validate the email field" is less pleasant to read than "the form should check the email." Mavin's own work concedes EARS is a starting discipline, not a universal law: some requirements, particularly complex interacting behaviors, do not fold neatly into one template, and forcing them can make things worse rather than clearer. It is a floor for quality, not a ceiling.&lt;/p&gt;

&lt;p&gt;It also does not verify anything on its own. A perfectly formed EARS requirement that nobody checks is still just a nicely worded wish. The syntax makes "done" definable. Something still has to do the defining-versus-reality comparison, which is the verification step, not the writing step. EARS gets you a target the machine can read. It does not pull the trigger.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this means if you build with agents
&lt;/h2&gt;

&lt;p&gt;If you hand your coding agent one-line prompts and get back plausible code that breaks on the second user, the fix is not a smarter model. It is a less ambiguous instruction. You do not need to adopt EARS formally to get the benefit. You need to write requirements the way EARS forces you to: state the trigger, state the exact behavior, and always write the "if this goes wrong, then the system shall" case, because that is the one your agent will otherwise invent for you.&lt;/p&gt;

&lt;p&gt;A requirements syntax built for jet engines turns out to be the right shape for the age of coding agents, for a reason that has nothing to do with aerospace. The literal reader rewards the precise writer. EARS just happens to be the cleanest way anyone has found to write precisely.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is EARS notation?
&lt;/h3&gt;

&lt;p&gt;EARS notation, the Easy Approach to Requirements Syntax, is a structured way to write natural-language requirements using a small set of keywords and a fixed clause order. Developed by Alistair Mavin and colleagues at Rolls-Royce in 2009, it constrains every requirement to the shape "While &lt;code&gt;&amp;lt;precondition&amp;gt;&lt;/code&gt;, when &lt;code&gt;&amp;lt;trigger&amp;gt;&lt;/code&gt;, the &lt;code&gt;&amp;lt;system&amp;gt;&lt;/code&gt; shall &lt;code&gt;&amp;lt;response&amp;gt;&lt;/code&gt;," which removes the ambiguity that makes ordinary requirements hard to build and impossible to test cleanly.&lt;/p&gt;

&lt;h3&gt;
  
  
  What are the five EARS patterns?
&lt;/h3&gt;

&lt;p&gt;The five patterns are ubiquitous (always true: "the system shall encrypt all stored passwords"), event-driven (triggered: "when a user submits the form, the system shall send an email"), state-driven (conditional on a state: "while a payment is processing, the system shall disable the button"), unwanted behavior (error handling: "if the provider returns an error, then the system shall display the failure"), and optional feature (scoped to a configuration: "where 2FA is enabled, the system shall require a code"). Almost every requirement you need to write fits one of these five shapes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why is EARS used in spec-driven development and AI coding?
&lt;/h3&gt;

&lt;p&gt;Because an AI agent reads a requirement literally, and a constrained sentence is a more reliable instruction than a fluent paragraph. Tools like Amazon's Kiro generate acceptance criteria in EARS, and the GitHub Spec Kit team is debating adding it, precisely because EARS templates produce specs that agents parse consistently. A well-formed EARS requirement is also already an acceptance criterion, so the same syntax that makes intent unambiguous makes "done" checkable, which is the core of &lt;a href="https://www.braingrid.ai/spec-driven-development" rel="noopener noreferrer"&gt;spec-driven development&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the difference between EARS notation and acceptance criteria?
&lt;/h3&gt;

&lt;p&gt;They overlap heavily. EARS is a syntax for writing any requirement clearly; an acceptance criterion is a condition that defines when a feature is done. The connection is that a requirement written in EARS is usually already a good acceptance criterion, because "when X, the system shall Y" is both a statement of intent and a checkable test. For the full craft of writing criteria an agent can verify, see &lt;a href="https://www.braingrid.ai/blog/how-to-write-acceptance-criteria-ai-agent-can-verify" rel="noopener noreferrer"&gt;how to write acceptance criteria an AI agent can actually verify&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Do I have to use EARS to write good requirements for AI agents?
&lt;/h3&gt;

&lt;p&gt;No. EARS is one disciplined way to hit the target, not the only one. What matters is that your requirement names the exact trigger, the exact system, the exact response, and the unwanted-path behavior, which is precisely the discipline EARS enforces. You can get the same benefit by writing that way without ever learning the acronym, which is essentially what BrainGrid's Planning Agent does for you when it turns a plain-language idea into structured, verifiable criteria.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://braingrid.ai" rel="noopener noreferrer"&gt;BrainGrid&lt;/a&gt; is the plan-first platform that turns your idea into requirements and acceptance criteria your agent can build and verify against. Try it at &lt;a href="https://braingrid.ai" rel="noopener noreferrer"&gt;braingrid.ai&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://www.braingrid.ai/blog/ears-notation" rel="noopener noreferrer"&gt;BrainGrid blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Why Monthly Screenshot Quotas Waste Your Money (Screenshot API Pricing, Explained)</title>
      <dc:creator>Nico Acosta</dc:creator>
      <pubDate>Sun, 09 Aug 2026 11:42:15 +0000</pubDate>
      <link>https://dev.to/grabbit/why-monthly-screenshot-quotas-waste-your-money-screenshot-api-pricing-explained-1l04</link>
      <guid>https://dev.to/grabbit/why-monthly-screenshot-quotas-waste-your-money-screenshot-api-pricing-explained-1l04</guid>
      <description>&lt;p&gt;If you are comparing screenshot API pricing, the pricing pages all look roughly the same: a few tiers, a monthly price, a screenshot quota per tier, and overage per extra capture. The number that decides your real cost is not on the card. It is the quota that resets every month, and how much of it you actually use. On a spiky workload, a monthly reset plan makes you pay for a peak you hit occasionally and forfeit the rest. This post explains the mechanic, then shows how to read a pricing page so you buy captures instead of empty allowance.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short answer
&lt;/h2&gt;

&lt;p&gt;Most screenshot APIs bill a &lt;strong&gt;fixed monthly subscription with a reset quota&lt;/strong&gt;: you pay for, say, 2,000 captures a month whether you make 2,000 or 200, and next month the counter goes back to zero. That model is fine when your usage is steady. It quietly wastes money when your usage is &lt;strong&gt;spiky&lt;/strong&gt;, which screenshot workloads almost always are.&lt;/p&gt;

&lt;p&gt;The waste is the gap between what you provision and what you use. To cover an occasional busy month you size up your plan, then forfeit the unused allowance in every quiet month. The sticker price hides that gap.&lt;/p&gt;

&lt;p&gt;Grabbit takes the other approach: &lt;strong&gt;$50 per year for 25,000 prepaid captures&lt;/strong&gt; ($0.002 each) that &lt;strong&gt;never reset or expire monthly&lt;/strong&gt;. Grabbit is not the cheapest screenshot API on headline price, and this post is explicit about that below. The point here is the pricing &lt;em&gt;model&lt;/em&gt;, not a rock-bottom rate.&lt;/p&gt;

&lt;h2&gt;
  
  
  How screenshot API pricing usually works
&lt;/h2&gt;

&lt;p&gt;Walk a typical pricing page and you will see one of three billing models:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Monthly subscription with a reset quota (the common one).&lt;/strong&gt; A tier bundles a fixed number of captures per month. Unused captures do not roll over. Go over and you pay overage per extra grab, or get blocked until next cycle. This is what most listings on a "screenshot api pricing" search use.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Usage metered.&lt;/strong&gt; You pay per request with a small monthly minimum. No quota to waste, but the minimum spend is its own small floor, and metered plans often have a thinner feature set.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Prepaid credits.&lt;/strong&gt; You buy a block of captures once. They draw down as you use them and do not expire on a monthly boundary. Grabbit sells these on a flat annual plan.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The three models can show nearly identical per-grab math on paper and produce very different bills, because the reset quota charges you for allowance, not for work done.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why a reset quota wastes money on spiky workloads
&lt;/h2&gt;

&lt;p&gt;Screenshot volume is rarely a flat line. The real pattern looks like bursts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A batch of Open Graph images generated the day you publish a dozen posts.&lt;/li&gt;
&lt;li&gt;A visual monitoring run that fires on a schedule, then nothing until the next one.&lt;/li&gt;
&lt;li&gt;A one-off research crawl capturing a list of competitor pages, then quiet.&lt;/li&gt;
&lt;li&gt;An agent pipeline that captures heavily during a task and idles between tasks.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Put concrete numbers on it. Say you capture &lt;strong&gt;1,500 pages in January, 9,000 in March, and a few hundred most other months.&lt;/strong&gt; To not get blocked in March, you provision a plan sized for roughly 9,000 captures a month. In January you pay for that whole allowance and use a sixth of it. Every quiet month, you pay for thousands of captures you never make. Over a year, the forfeited allowance can dwarf the cost of the captures you actually took.&lt;/p&gt;

&lt;p&gt;That is the mechanic. &lt;strong&gt;A reset quota bills your peak every month and hands you nothing back for the troughs.&lt;/strong&gt; The lower the ratio of your average month to your busiest month, the more a monthly plan overcharges you.&lt;/p&gt;

&lt;p&gt;Prepaid credits invert it: you buy a block once, draw it down at your real pace, and the busy month and the quiet month both bill only the captures you made. For variable capture volume, the billing model usually matters more than a fraction of a cent on the per-grab rate.&lt;/p&gt;

&lt;h2&gt;
  
  
  The honest caveat: cheapest sticker price is not Grabbit
&lt;/h2&gt;

&lt;p&gt;A "screenshot api pricing" search is full of pages claiming the lowest number, so here is the honest version. Grabbit is &lt;strong&gt;not&lt;/strong&gt; the cheapest screenshot API by headline price. Some providers advertise entry plans from $5 to $9 per month, and on raw per-grab rate &lt;strong&gt;Browserless ($0.0013)&lt;/strong&gt; and &lt;strong&gt;Thum.io ($0.0002)&lt;/strong&gt; both list lower numbers than Grabbit's $0.002.&lt;/p&gt;

&lt;p&gt;The trade-offs behind those lower numbers are real: Browserless is a managed headless browser you drive with your own Puppeteer or Playwright scripts rather than a single REST call, and Thum.io has a limited feature set (no full-page capture, no wait controls, no selector targeting). If the only thing you care about is the lowest per-grab rate and you are willing to script, one of those is the honest pick, not Grabbit.&lt;/p&gt;

&lt;p&gt;Grabbit's claim is narrower: flat annual billing with &lt;strong&gt;zero monthly reset waste&lt;/strong&gt;, and one-line agent onboarding. If your pain is a quota you keep forfeiting, that is the thing that saves you money, not the sticker price. Prices here were verified June 2026 from each provider's public pricing page, normalized to roughly 10,000 grabs per month.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "temporary need" is the category's real pricing problem
&lt;/h2&gt;

&lt;p&gt;There is a deeper reason the reset-quota model fits this category badly. The screenshot API market skews toward &lt;strong&gt;temporary needs&lt;/strong&gt;: a project that ends, a marketing job that ships, a migration that finishes. When the market leader ScreenshotOne was publicly torn down in 2026, the founder said the category's core problem in the open: there is no single ICP it can scale with, and customers churn because the need was temporary. A monthly subscription is the worst possible fit for a temporary need, you keep paying after the work is done until you remember to cancel.&lt;/p&gt;

&lt;p&gt;Prepaid credits that never expire suit a temporary-or-spiky need better: you buy what the job needs, use it when the job runs, and nothing keeps billing you between jobs. (Whether that changes long-run retention is a separate question we are not claiming to have answered, it is a better fit for the buying pattern, full stop.)&lt;/p&gt;

&lt;h2&gt;
  
  
  How to read a screenshot API pricing page
&lt;/h2&gt;

&lt;p&gt;When you land on a pricing page, get past the sticker price and check five things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Reset or roll over?&lt;/strong&gt; Does unused quota carry forward, or evaporate at the cycle boundary? This is the single biggest driver of real cost on spiky usage.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Effective cost per capture &lt;em&gt;you make&lt;/em&gt;.&lt;/strong&gt; Divide the plan price by the captures you realistically make in an average month, not by the quota. That is your true per-grab cost.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Overage behavior.&lt;/strong&gt; Do you pay per extra grab, or get hard-blocked until next cycle? A hard block can break a publish or a monitoring run at the worst moment.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;What the free tier excludes.&lt;/strong&gt; Free plans often gate full-page capture, custom viewports, or output formats. Confirm the free number is a real preview of the paid product, not a stripped-down one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Feature parity across tiers.&lt;/strong&gt; Some providers put the parameters you need (selector targeting, wait delays, WebP) behind a higher tier, so the cheap plan cannot actually do your job.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Test the integration before you pay a cent
&lt;/h2&gt;

&lt;p&gt;You do not have to guess. Grabbit gives every account a &lt;strong&gt;test key&lt;/strong&gt; (&lt;code&gt;sk_test_...&lt;/code&gt;) that returns a placeholder image at no charge, so you can wire up the API and run it in CI before adding a card. Switch to a &lt;strong&gt;live key&lt;/strong&gt; (&lt;code&gt;sk_live_...&lt;/code&gt;) when you want real captures. Here is a real request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://api.grabbit.live/v1/grabs &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer sk_live_..."&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "url": "https://example.com",
    "width": 1280,
    "height": 720,
    "format": "webp",
    "full_page": true
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response includes &lt;code&gt;image_url&lt;/code&gt; (the hosted screenshot), &lt;code&gt;bytes&lt;/code&gt;, &lt;code&gt;execution_ms&lt;/code&gt;, and &lt;code&gt;id&lt;/code&gt;. Add &lt;code&gt;delay_ms&lt;/code&gt; (up to 10000) to wait for JavaScript-heavy content, or &lt;code&gt;selector&lt;/code&gt; to clip to a single element. Point at a &lt;code&gt;sk_test_&lt;/code&gt; key first: test captures are free placeholders, so proving out an integration costs nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to choose
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Spiky or seasonal volume, quota waste is your pain:&lt;/strong&gt; a flat prepaid plan like Grabbit (no monthly reset).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Steady, predictable monthly volume:&lt;/strong&gt; a monthly subscription wastes nothing, and the per-grab math is what it is.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Lowest per-grab rate, willing to write scripts:&lt;/strong&gt; Browserless.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Simple thumbnails, minimal budget:&lt;/strong&gt; Thum.io.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Low, flat volume and you enjoy owning infra:&lt;/strong&gt; self-host Puppeteer or Playwright.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a full field comparison, see our &lt;a href="https://www.grabbit.live/blog/best-screenshot-api" rel="noopener noreferrer"&gt;honest comparison of the best screenshot APIs&lt;/a&gt; and the head-to-head &lt;a href="https://www.grabbit.live/blog/screenshotone-alternative" rel="noopener noreferrer"&gt;ScreenshotOne alternative&lt;/a&gt; breakdown. If an agent is what needs to capture pages, the &lt;a href="https://www.grabbit.live/blog/screenshots-for-ai-agents" rel="noopener noreferrer"&gt;screenshots for AI agents&lt;/a&gt; guide covers the integration path. For Grabbit's pricing, parameters, and onboarding, see &lt;a href="https://www.grabbit.live/screenshots-as-a-service" rel="noopener noreferrer"&gt;screenshots as a service&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://www.grabbit.live/blog/screenshot-api-pricing" rel="noopener noreferrer"&gt;Grabbit blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>programming</category>
      <category>tools</category>
    </item>
    <item>
      <title>What Is a CLAUDE.md File? The One Anthropic's Own Docs Tell You to Keep Short</title>
      <dc:creator>Nico Acosta</dc:creator>
      <pubDate>Sun, 09 Aug 2026 11:13:59 +0000</pubDate>
      <link>https://dev.to/braingrid/what-is-a-claudemd-file-the-one-anthropics-own-docs-tell-you-to-keep-short-3l7o</link>
      <guid>https://dev.to/braingrid/what-is-a-claudemd-file-the-one-anthropics-own-docs-tell-you-to-keep-short-3l7o</guid>
      <description>&lt;p&gt;Everyone tells you to write a CLAUDE.md file, and almost nobody tells you what it is actually for. So you create one, paste in everything you can think of, and watch your agent behave beautifully for a week. Then it starts ignoring the file you spent an afternoon on, and a thread titled &lt;a href="https://www.reddit.com/r/ClaudeAI/comments/1nokaln/what_is_the_point_of_claudemd/" rel="noopener noreferrer"&gt;"What is the point of CLAUDE.md?"&lt;/a&gt; with 180 comments suddenly makes total sense.&lt;/p&gt;

&lt;p&gt;Here is the claim worth sitting with. A CLAUDE.md file is not your project's memory. It is a set of standing instructions the model reads at the start of a session, and the more you treat it like a durable record of what your product is supposed to be, the faster it turns into a liability.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a CLAUDE.md file actually is
&lt;/h2&gt;

&lt;p&gt;A CLAUDE.md file is a plain Markdown file that &lt;a href="https://code.claude.com/docs/en/memory" rel="noopener noreferrer"&gt;Claude Code&lt;/a&gt; reads automatically at the start of every session. It goes in your project's root, you write it by hand, and it holds the things the agent would otherwise have to guess: your tech stack, your build and test commands, your coding conventions, the folders it should not touch. Anthropic's own docs describe it as persistent instructions for a project. The keyword there is instructions, not knowledge.&lt;/p&gt;

&lt;p&gt;That distinction is the whole post. The file is prepended to the conversation, so it costs context tokens on every single turn. It is not a database the agent queries when it needs something. It is a preamble it carries the entire time, whether the current task needs those rules or not. Which is exactly why the advice that actually works, once you get past "just write one," is to keep it short.&lt;/p&gt;

&lt;p&gt;The counterintuitive part is that cutting the file down often makes the agent better, not worse. One builder &lt;a href="https://x.com/kingwilliam_/status/2083596741719261535" rel="noopener noreferrer"&gt;reported cutting roughly 80% of his Claude Code context&lt;/a&gt; and getting sharper results, echoing guidance from Anthropic's own team that less instruction beats more. That reframes the file from a place to hoard context into a budget you spend carefully. A 400-line CLAUDE.md is not a well-documented project. It is a 400-line tax on every request, and past a certain point the model starts skimming it, which is the exact behavior the Reddit thread was complaining about.&lt;/p&gt;

&lt;h2&gt;
  
  
  What goes inside a CLAUDE.md file
&lt;/h2&gt;

&lt;p&gt;The useful contents are narrow and stable. Think of the small set of facts that are true today and will still be true next month: the frameworks you use, the command to run tests, the command to build, the linter you enforce, the one architectural rule you never want violated ("all API calls go through the client in &lt;code&gt;lib/api&lt;/code&gt;, never fetch directly"). These earn their place because they change slowly and apply to nearly every task.&lt;/p&gt;

&lt;p&gt;Here is the difference between a file that helps and one that rots, side by side.&lt;/p&gt;

&lt;p&gt;Rots: "We migrated the auth flow last Tuesday and the new endpoint is &lt;code&gt;/v2/login&lt;/code&gt;, but the old one still works for now, and Sarah is refactoring the session handling this sprint so check with her before touching it."&lt;/p&gt;

&lt;p&gt;Helps: "Auth lives in &lt;code&gt;src/auth&lt;/code&gt;. Run &lt;code&gt;pnpm test:auth&lt;/code&gt; after any change there."&lt;/p&gt;

&lt;p&gt;The first one is a status update. It will be false in a week, and when it is, the agent will confidently act on it anyway. The second is a durable fact about where things live and how to verify them. One is a note to a coworker. The other is a rule for a machine.&lt;/p&gt;

&lt;h2&gt;
  
  
  Should you commit your CLAUDE.md file?
&lt;/h2&gt;

&lt;p&gt;Yes, for the project-level file. The whole value is that everyone on the team, and every remote agent, loads the exact same rules. Commit the &lt;code&gt;CLAUDE.md&lt;/code&gt; in your repo root the way you commit your linter config, because it is the same kind of artifact: a shared standard, not a personal preference. Check it into Git and your agent behaves consistently no matter who runs it or where.&lt;/p&gt;

&lt;p&gt;Your personal, machine-wide file (the one in &lt;code&gt;~/.claude/&lt;/code&gt;) is different. It holds your individual workflow quirks, it lives in your home directory outside any repo, and it should stay there. The rule of thumb: project rules are team property and get committed; personal preferences are yours and do not.&lt;/p&gt;

&lt;h2&gt;
  
  
  The reframe: instructions are not a record
&lt;/h2&gt;

&lt;p&gt;Now the part that most "how to write a great CLAUDE.md" posts miss. Builders keep reaching for CLAUDE.md, or Claude's auto-memory, to solve a problem it was never designed for: remembering what the product is supposed to do. What did we decide about how billing works? Why is this feature built the way it is? What counts as done for the checkout flow? That is not instruction. That is your product's record, and stuffing it into a file the model skims on every turn is how it goes wrong.&lt;/p&gt;

&lt;p&gt;The evidence for this is everywhere once you look. In a recent &lt;a href="https://www.reddit.com/r/ClaudeCode/comments/1vfcmmw/" rel="noopener noreferrer"&gt;r/ClaudeCode thread on whether anyone actually uses Claude's memory feature&lt;/a&gt;, four separate builders landed on the same verdict without coordinating. One put it bluntly:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I made the mistake of trying to use it for project knowledge and now it is bleeding into other projects smh. Don't be like me.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Others in the same thread said it "quickly gets outdated," that it stayed "entirely opaque" for a month before they noticed a wrong note, and that memory "should be reserved for user preferences and nothing more. Not for actual knowledge." Four people, one conclusion: the model's memory is a preference store, not a project record. It bleeds, it goes stale, and you cannot see what is in it.&lt;/p&gt;

&lt;p&gt;This is the capability point that does not go away as models improve. Better models make it more tempting to offload durable knowledge into the model's context, not less, because the agent feels smart enough to trust. But a smarter agent working from a stale note is not more reliable. It is more confidently wrong, and harder to debug, because the mistake traces back to something it "remembered" that stopped being true weeks ago. Models are stateless. Your product isn't. (If you want the deeper version of why auto-memory rots, we wrote it up in &lt;a href="https://www.braingrid.ai/blog/claude-code-memory-state-file-pattern" rel="noopener noreferrer"&gt;the state file pattern&lt;/a&gt;.)&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the product record actually belongs
&lt;/h2&gt;

&lt;p&gt;So if instructions live in CLAUDE.md and durable knowledge does not belong in memory, where does the record of what your product is supposed to do actually live? Outside the model, in something you own and update deliberately.&lt;/p&gt;

&lt;p&gt;This is the gap &lt;a href="https://braingrid.ai" rel="noopener noreferrer"&gt;BrainGrid&lt;/a&gt; fills. You describe a feature, and the Planning Agent turns it into a requirement with explicit acceptance criteria, the specific, checkable statements of what done means for that feature. When the Builder Agent builds it, whether in a BrainGrid Cloud sandbox or in your own GitHub repo through Claude Code, Cursor, or Codex over MCP, verification checks the result against every one of those criteria before the feature counts as done. And all of it, the specs, the decisions, the criteria, the verifications, accumulates per product as a record you can read.&lt;/p&gt;

&lt;p&gt;The difference from a CLAUDE.md file is the point. CLAUDE.md tells the agent how you work: your commands, your conventions, your house rules. The product record holds what you decided and why, and it does not depend on the model remembering anything. One is a preamble the model reads. The other is the source of truth the model is checked against. You still want a lean CLAUDE.md. You just stop asking it to be something it was never built to be. This is the heart of &lt;a href="https://www.braingrid.ai/context-engineering" rel="noopener noreferrer"&gt;context engineering&lt;/a&gt;: deciding what the model should carry in its head, and what belongs in a record outside it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one trade-off worth naming
&lt;/h2&gt;

&lt;p&gt;Keeping durable knowledge out of the model's context has a real cost: it is more work up front. Writing an acceptance criterion is slower than typing a sentence into a chat and hoping the agent remembers it. The lazy path genuinely feels faster on day one. It just stops feeling faster the first time you spend an afternoon debugging behavior that traces back to a note nobody knew was there. The effortless option rots. The deliberate one compounds. That is the trade, and it is worth making on any project you intend to keep.&lt;/p&gt;

&lt;p&gt;If you're building a real product with Claude Code or Cursor right now, this means your CLAUDE.md should get shorter over time, not longer, and the knowledge you were tempted to cram into it needs a home the model doesn't own.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Is a CLAUDE.md file necessary?
&lt;/h3&gt;

&lt;p&gt;Not strictly, but it is worth it for any project you return to. Without one, the agent re-guesses your stack, your commands, and your conventions every session, and guesses wrong often enough to waste real time. A short, accurate CLAUDE.md removes that friction. Skip it only for a throwaway experiment you will never open again.&lt;/p&gt;

&lt;h3&gt;
  
  
  What do you put in a CLAUDE.md file?
&lt;/h3&gt;

&lt;p&gt;The stable facts an agent needs on nearly every task: your tech stack, the commands to run and test and build the project, your core coding conventions, and the hard rules you never want broken. Keep it lean. Leave out anything that changes week to week, like current sprint status or in-progress decisions, because that is exactly what goes stale and misleads the agent later.&lt;/p&gt;

&lt;h3&gt;
  
  
  Should I commit my CLAUDE.md file to Git?
&lt;/h3&gt;

&lt;p&gt;Commit the project-level &lt;code&gt;CLAUDE.md&lt;/code&gt; in your repo root, because its value is that every teammate and every agent loads the same rules. Do not worry about the personal file in your home directory (&lt;code&gt;~/.claude/CLAUDE.md&lt;/code&gt;); it lives outside the repo and holds your individual preferences, so it stays local by design.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why does Claude ignore my CLAUDE.md file?
&lt;/h3&gt;

&lt;p&gt;Usually because the file is too long. Everything in CLAUDE.md is prepended to every request, so a bloated file both costs tokens on every turn and gets skimmed rather than followed. Cut it to the rules that matter most, phrase them as clear directives, and the model follows them far more reliably. If a rule is critical, keeping it near the top and stated plainly helps.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is a CLAUDE.md file the same as agent memory?
&lt;/h3&gt;

&lt;p&gt;No. CLAUDE.md is instructions you write and control, read at the start of each session. Auto-memory is notes the agent writes for itself in the background, in a Claude-specific location. The file you own stays accurate because you edit it; the notes the agent writes tend to go stale and bleed across projects, which is why most builders reserve memory for preferences and keep real project knowledge in files, or in a &lt;a href="https://braingrid.ai" rel="noopener noreferrer"&gt;product record&lt;/a&gt; that lives outside the model entirely.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://braingrid.ai" rel="noopener noreferrer"&gt;BrainGrid&lt;/a&gt; is the plan-first platform that gives your product a record the model can be checked against, not just instructions it reads. Try it at &lt;a href="https://braingrid.ai" rel="noopener noreferrer"&gt;braingrid.ai&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://www.braingrid.ai/blog/what-is-a-claude-md-file" rel="noopener noreferrer"&gt;BrainGrid blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>tutorial</category>
      <category>productivity</category>
    </item>
    <item>
      <title>Open Graph Meta Tags: The Complete Reference (Every Tag, With Examples)</title>
      <dc:creator>Nico Acosta</dc:creator>
      <pubDate>Sat, 08 Aug 2026 11:41:15 +0000</pubDate>
      <link>https://dev.to/grabbit/open-graph-meta-tags-the-complete-reference-every-tag-with-examples-36f6</link>
      <guid>https://dev.to/grabbit/open-graph-meta-tags-the-complete-reference-every-tag-with-examples-36f6</guid>
      <description>&lt;p&gt;Open Graph meta tags are the snippets in your page's &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; that control how a link looks when someone shares it. Get them right and your URL unfurls into a rich card with a title, a summary, and an image. Miss them and the same link shows up as bare text. This is the complete reference: every tag worth setting, what each one does, and copy-paste examples.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short answer: five tags
&lt;/h2&gt;

&lt;p&gt;Most pages need exactly five Open Graph tags. Put these in the &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:title"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"Your page title"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:description"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"A one-line summary of the page."&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:image"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"https://yoursite.com/og/your-page.png"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:url"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"https://yoursite.com/your-page"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:type"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"website"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Facebook, LinkedIn, Slack, Discord, iMessage, and WhatsApp all read these. Set them and your link previews work almost everywhere. The rest of this reference covers the optional tags and the X-specific ones.&lt;/p&gt;

&lt;h2&gt;
  
  
  The required tags, one by one
&lt;/h2&gt;

&lt;p&gt;These five carry the card. Skip any of them and the preview degrades.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Tag&lt;/th&gt;
&lt;th&gt;What it does&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;og:title&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The headline of the card. Independent of the &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt; element.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;og:description&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The summary line under the title. One or two sentences.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;og:image&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The picture in the card. Absolute URL, 1200 by 630 for the large card.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;og:url&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The canonical URL of the page, without tracking parameters.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;og:type&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;The category: &lt;code&gt;website&lt;/code&gt;, &lt;code&gt;article&lt;/code&gt;, &lt;code&gt;video.movie&lt;/code&gt;, and so on.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;A few details that trip people up:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;og:title&lt;/code&gt; is not your &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt;.&lt;/strong&gt; They can match, but &lt;code&gt;og:title&lt;/code&gt; lets you write a version tuned for sharing. Keep it under about 60 characters so it does not truncate.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;og:url&lt;/code&gt; should be the canonical URL.&lt;/strong&gt; Use the clean permanent address, not the one with &lt;code&gt;?utm_source=...&lt;/code&gt; on it, so every share collapses to the same object.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;og:image&lt;/code&gt; must be an absolute &lt;code&gt;https://&lt;/code&gt; URL.&lt;/strong&gt; A relative path like &lt;code&gt;/og.png&lt;/code&gt; will not resolve for the crawler. It also has to be publicly reachable: if the image needs a login, the card shows nothing.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The image tags that make the large card render
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;og:image&lt;/code&gt; alone works, but three companion tags make the difference between a big card and a small thumbnail:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:image"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"https://yoursite.com/og/your-page.png"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:image:width"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"1200"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:image:height"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"630"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:image:alt"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"A short description of the image"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Declaring &lt;code&gt;og:image:width&lt;/code&gt; and &lt;code&gt;og:image:height&lt;/code&gt; lets the platform reserve the right space and render the large card immediately instead of guessing the dimensions on first fetch. Use &lt;strong&gt;1200 by 630 pixels&lt;/strong&gt;, the size every major platform treats as the full-width preview. &lt;code&gt;og:image:alt&lt;/code&gt; is the accessibility description, read by screen readers on platforms that expose it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The optional tags worth setting
&lt;/h2&gt;

&lt;p&gt;Beyond the core five, a handful of tags add polish and matter for specific &lt;code&gt;og:type&lt;/code&gt; values:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:site_name"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"Your Brand"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:locale"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"en_US"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;og:site_name&lt;/code&gt;&lt;/strong&gt; is the name of the overall site, shown as a small label on some cards (LinkedIn, for instance). Set it once to your brand.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;og:locale&lt;/code&gt;&lt;/strong&gt; declares the language and region, like &lt;code&gt;en_US&lt;/code&gt; or &lt;code&gt;fr_FR&lt;/code&gt;. Useful if your pages are localized.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When &lt;code&gt;og:type&lt;/code&gt; is &lt;code&gt;article&lt;/code&gt;, these structured tags become relevant and show up on news and blog cards:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:type"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"article"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"article:published_time"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"2026-08-08T09:00:00Z"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"article:author"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"https://yoursite.com/authors/jane"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"article:section"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"Engineering"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"article:tag"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"open graph"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You only need the &lt;code&gt;article:*&lt;/code&gt; tags on pages that are genuinely articles. For a marketing page or a landing page, &lt;code&gt;og:type&lt;/code&gt; of &lt;code&gt;website&lt;/code&gt; and the core five are enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  X (Twitter) tags: what you actually need
&lt;/h2&gt;

&lt;p&gt;X reads its own &lt;code&gt;twitter:&lt;/code&gt; tags but falls back to your &lt;code&gt;og:&lt;/code&gt; tags when they are absent. The one tag you should always set is &lt;code&gt;twitter:card&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"twitter:card"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"summary_large_image"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Without &lt;code&gt;summary_large_image&lt;/code&gt;, X renders a small square thumbnail beside the text instead of the big image card. Note the attribute difference: &lt;code&gt;og:&lt;/code&gt; tags use &lt;code&gt;property=&lt;/code&gt;, while &lt;code&gt;twitter:&lt;/code&gt; tags use &lt;code&gt;name=&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;You can stop there. X will pull the title, description, and image from &lt;code&gt;og:title&lt;/code&gt;, &lt;code&gt;og:description&lt;/code&gt;, and &lt;code&gt;og:image&lt;/code&gt;. Only add these if you want a &lt;em&gt;different&lt;/em&gt; headline or image on X than everywhere else:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"twitter:title"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"A headline tuned for X"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"twitter:description"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"A summary tuned for X."&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"twitter:image"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"https://yoursite.com/og/your-page.png"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  A complete, copy-paste example
&lt;/h2&gt;

&lt;p&gt;Here is the full &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt; block for a typical article page, combining everything above:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight html"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;head&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;title&amp;gt;&lt;/span&gt;Open Graph Meta Tags: The Complete Reference&lt;span class="nt"&gt;&amp;lt;/title&amp;gt;&lt;/span&gt;

  &lt;span class="c"&gt;&amp;lt;!-- Open Graph --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:title"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"Open Graph Meta Tags: The Complete Reference"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:description"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"Every og: tag worth setting, with examples."&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:image"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"https://yoursite.com/og/reference.png"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:image:width"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"1200"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:image:height"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"630"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:url"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"https://yoursite.com/blog/og-meta-tags"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:type"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"article"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;property=&lt;/span&gt;&lt;span class="s"&gt;"og:site_name"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"Your Brand"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;

  &lt;span class="c"&gt;&amp;lt;!-- X (Twitter) --&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;meta&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"twitter:card"&lt;/span&gt; &lt;span class="na"&gt;content=&lt;/span&gt;&lt;span class="s"&gt;"summary_large_image"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/head&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  How to generate the og:image from a URL
&lt;/h2&gt;

&lt;p&gt;Writing the tags is the easy part. The hard part is producing a unique, on-brand &lt;code&gt;og:image&lt;/code&gt; for every page without designing one by hand. The pattern that scales: build &lt;strong&gt;one HTML template&lt;/strong&gt; that renders the page title and your branding, then &lt;strong&gt;capture that template as an image&lt;/strong&gt; at 1200 by 630.&lt;/p&gt;

&lt;p&gt;A screenshot API turns that into a single request. Point it at your template URL with a fixed viewport, and get back a hosted image you can drop straight into &lt;code&gt;og:image&lt;/code&gt;:&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;-X&lt;/span&gt; POST https://grabbit.live/api/v1/grabs &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer sk_live_your_key"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "url": "https://yoursite.com/og-template?title=Your+Post+Title",
    "width": 1200,
    "height": 630,
    "format": "png"
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response includes the hosted image URL. Set &lt;code&gt;og:image&lt;/code&gt; to it, and every page gets its own preview card generated from real HTML, no design tool required. This is the same approach behind &lt;a href="https://www.grabbit.live/blog/next-js-og-image" rel="noopener noreferrer"&gt;dynamic OG images in Next.js&lt;/a&gt; and any other framework: render a URL, screenshot it, ship the result.&lt;/p&gt;

&lt;p&gt;For the deeper background on why the image matters and how platforms crop it, see &lt;a href="https://www.grabbit.live/blog/what-is-an-og-image" rel="noopener noreferrer"&gt;what an OG image is&lt;/a&gt;. Once your tags are live, run the page through an &lt;a href="https://www.grabbit.live/blog/og-image-checker" rel="noopener noreferrer"&gt;OG image checker&lt;/a&gt; to confirm the crawler sees what you expect, and if a preview looks wrong, an &lt;a href="https://www.grabbit.live/blog/open-graph-debugger" rel="noopener noreferrer"&gt;Open Graph debugger&lt;/a&gt; shows you the exact tags each platform scraped.&lt;/p&gt;

&lt;p&gt;Grabbit is a screenshots-as-a-service API built for exactly this job: point it at a URL, get back a hosted image, no headless browser to run. Live grabs are a flat $0.002 each on prepaid credits that never expire, and a test key renders free placeholders so you can wire up your OG pipeline before adding a card. See the &lt;a href="https://www.grabbit.live/screenshot-api" rel="noopener noreferrer"&gt;screenshot API&lt;/a&gt; for the full reference.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common mistakes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Injecting og: tags with client-side JavaScript.&lt;/strong&gt; Crawlers run little to no JS, so they see an empty &lt;code&gt;&amp;lt;head&amp;gt;&lt;/code&gt;. Server-render your meta tags.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;A relative &lt;code&gt;og:image&lt;/code&gt; path.&lt;/strong&gt; It must be an absolute &lt;code&gt;https://&lt;/code&gt; URL, or the image will not resolve.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Forgetting &lt;code&gt;twitter:card&lt;/code&gt;.&lt;/strong&gt; Without &lt;code&gt;summary_large_image&lt;/code&gt;, X shows a small thumbnail instead of the large card.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Not clearing the cache after a fix.&lt;/strong&gt; Platforms cache the scrape. Use each platform's debugger to force a re-scrape once you have corrected a tag.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://www.grabbit.live/blog/og-meta-tags" rel="noopener noreferrer"&gt;Grabbit blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>seo</category>
      <category>html</category>
    </item>
    <item>
      <title>What Is AI-Native Development? (And Why \"Disposable Software\" Is Only Half True)</title>
      <dc:creator>Nico Acosta</dc:creator>
      <pubDate>Sat, 08 Aug 2026 11:15:16 +0000</pubDate>
      <link>https://dev.to/braingrid/what-is-ai-native-development-and-why-disposable-software-is-only-half-true-30of</link>
      <guid>https://dev.to/braingrid/what-is-ai-native-development-and-why-disposable-software-is-only-half-true-30of</guid>
      <description>&lt;p&gt;Search "AI-native development" today and every definition on the first page was written by a platform vendor. IBM, DevOps.com, a half-dozen consultancies, all describing a paradigm where "intelligent agents act as primary implementers" and "platforms enforce guardrails." It reads like the future arriving on schedule, clean and inevitable.&lt;/p&gt;

&lt;p&gt;Then you open the Reddit threads where builders actually live in this paradigm, and the tone flips. Under a post titled "Is demand for new software decreasing?", the top comment is three words repeated twice: &lt;a href="https://www.reddit.com/r/cscareers/comments/1uc8naj" rel="noopener noreferrer"&gt;software is now disposable&lt;/a&gt;. The reply underneath: "I hate this so much."&lt;/p&gt;

&lt;p&gt;Same paradigm. Two completely different emotions. The vendors sell AI-native as liberation. The people doing it feel something closer to grief. Both are describing something real, and the gap between them is the most useful thing to understand about where software is going.&lt;/p&gt;

&lt;p&gt;Here is the hypothesis this post is going to test: AI-native development is real and worth adopting, "disposable software" is true for a narrower slice of what you build than the grief implies, and the thing that separates the disposable from the durable is not the agent. It's whether a plan and a set of acceptance criteria outlive the chat that produced the code.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI-native development, defined honestly
&lt;/h2&gt;

&lt;p&gt;Strip away the vendor gloss and AI-native development means one thing: you build assuming the agent writes most of the code, and you organize your work around that assumption instead of fighting it.&lt;/p&gt;

&lt;p&gt;That is a real shift, not a marketing one. In the old model, AI was a consultant on your shoulder. You wrote the code and asked ChatGPT when you got stuck. AI-native inverts the default. The agent writes the first draft of almost everything, and your job moves up a level, to deciding what gets built, describing it precisely enough that the agent can execute, and verifying that what came back actually does the job.&lt;/p&gt;

&lt;p&gt;Patrick Debois, who has done more than anyone to name this shift, &lt;a href="https://www.infoq.com/presentations/patterns-ai-native-development/" rel="noopener noreferrer"&gt;frames it as four patterns&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Transitioning from producer to manager, focusing on intent over implementation through spec-driven development, moving from delivery to discovery, and managing agentic knowledge.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Read that list twice and notice what is not on it. None of the four patterns is "write better code." All four are about the work that surrounds the code: intent, management, discovery, memory. The framing reorders the whole job. The thing you used to be paid for, producing code, is now pattern zero, the part the agent handles, and every named pattern lives above it.&lt;/p&gt;

&lt;p&gt;That is the honest core of AI-native development. The coding got cheap. Everything around the coding became the actual job. If you have felt your role quietly change from typing to describing and checking, you already work this way, whether or not you have a word for it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The old way and the AI-native way, side by side
&lt;/h2&gt;

&lt;p&gt;The difference is easiest to see in a single feature.&lt;/p&gt;

&lt;p&gt;Old way: you get a ticket that says "add password reset." You know your codebase, so you open the auth module, write the endpoint, wire up the email, handle the token expiry, and test it as you go. The knowledge of what "done" means lives in your head the entire time, because you are the one holding all the pieces.&lt;/p&gt;

&lt;p&gt;AI-native way: you tell an agent "add password reset" and it produces a working-looking flow in four minutes. Now the knowledge that used to live in your head has to live somewhere the agent can see, or it doesn't get applied at all. Did it expire the token after one use? Rate-limit the request endpoint? Invalidate old sessions on reset? The agent will happily skip every one of those unless someone wrote them down, because "add password reset" is not a specification. It's a wish.&lt;/p&gt;

&lt;p&gt;That is the whole game. In the old model, the spec could stay implicit because the person writing the code was the person who knew the requirements. In the AI-native model, the writer and the knower are different entities, and anything you don't make explicit is anything the agent is free to get wrong. This is the practical difference between &lt;a href="https://www.braingrid.ai/blog/vibe-coding-vs-agentic-coding" rel="noopener noreferrer"&gt;vibe coding and agentic coding&lt;/a&gt;: one accepts whatever the agent produces, the other writes down what "produced correctly" means first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where "disposable software" is actually true
&lt;/h2&gt;

&lt;p&gt;Now the disposability claim, taken seriously.&lt;/p&gt;

&lt;p&gt;The builders saying "software is now disposable" are not wrong. There is a large and growing category of software that genuinely is throwaway now, and pretending otherwise would be dishonest. The internal script that reformats a CSV once a quarter. The landing page for a launch that runs for two weeks. The prototype you build to answer a single question in a meeting, screenshot, and never open again. The one-off tool that scrapes a report so you don't have to.&lt;/p&gt;

&lt;p&gt;For all of that, disposability is not a loss. It's the point. Building these used to cost an afternoon of a developer's time, which meant they mostly didn't get built. Now they cost ten minutes, so they get built, used, and discarded, and the world is slightly better for it. If the software has a lifespan measured in days and a blast radius of exactly one person, spec-driven rigor is overhead you should skip. Vibe your way through it and move on.&lt;/p&gt;

&lt;p&gt;The grief in those threads is real, but a lot of it is misattributed. What feels like "all software is disposable now" is usually "the disposable category got much bigger and much more visible." That is a different and less alarming claim.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it's a bug, not a feature
&lt;/h2&gt;

&lt;p&gt;The line gets crossed the moment other people depend on the thing.&lt;/p&gt;

&lt;p&gt;A demo that only you will ever run can be disposable. The customer-facing app it turns into cannot. The script that formats your own CSV can be disposable. The billing job that formats every customer's invoice cannot. The prototype that proves an idea can be disposable. The product you charge money for, that holds someone's data, that you will still be editing in six months, cannot be. Not because rigor is virtuous, but because "disposable" and "load-bearing" are opposites, and you don't get to be both.&lt;/p&gt;

&lt;p&gt;Here is the trap AI-native development sets, and it's worth stating plainly because almost nobody warns you about it. The disposable version and the durable version look identical when they're born. The agent produces the same clean, confident, working-looking output either way. That is &lt;a href="https://www.braingrid.ai/blog/illusion-of-completeness-ai-demo" rel="noopener noreferrer"&gt;the illusion of completeness&lt;/a&gt;: looking done and being done are different properties, and the agent only guarantees the first one. The demo that will run once and the app that will run for years come out of the same prompt looking equally finished. You cannot tell them apart by looking, which means the moment your throwaway quietly becomes load-bearing, and it will, because the useful ones always do, you inherit a codebase that was never specified, never verified, and never meant to survive.&lt;/p&gt;

&lt;p&gt;Disposable software is fine until the day it stops being disposable. And that day never announces itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  What makes software non-disposable
&lt;/h2&gt;

&lt;p&gt;So what actually converts a disposable build into a durable one? Not more code. The code is the cheap part now, and adding more of it just gives you more to distrust.&lt;/p&gt;

&lt;p&gt;What makes software non-disposable is the layer above the code: a plan that says what this thing is supposed to do, and a set of &lt;a href="https://www.braingrid.ai/blog/how-to-write-acceptance-criteria-ai-agent-can-verify" rel="noopener noreferrer"&gt;acceptance criteria an agent can actually verify&lt;/a&gt; that says how you'll know it does it. Those two artifacts are what let you change the software next month without fear, because they tell the next agent, and the next you, what must stay true. Code without them is a black box you're afraid to touch. Code with them is a system you can keep evolving. That is the entire difference between something you throw away and something you build on.&lt;/p&gt;

&lt;p&gt;This is exactly the gap &lt;a href="https://braingrid.ai" rel="noopener noreferrer"&gt;BrainGrid&lt;/a&gt; is built to close, and it's the whole point of practicing &lt;a href="https://www.braingrid.ai/agentic-engineering" rel="noopener noreferrer"&gt;agentic engineering&lt;/a&gt; instead of just prompting faster. You describe what you want, and the Planning Agent turns it into a real requirement with acceptance criteria, the questions you didn't think to ask, and a spec that survives the conversation. Then the Builder Agent writes the code, in our cloud or in your own repo with Claude Code, Cursor, or Codex. And nothing counts as done until it's been verified against those criteria, with evidence that it does what you intended. The agent forgets everything the moment the session ends. The plan and the criteria don't. That persistence is what makes the output something you can depend on instead of something you'll be scared to open later.&lt;/p&gt;

&lt;p&gt;It runs as a loop, Plan then Build then Verify then Repeat, and the point of the loop is that the durable version costs you almost nothing extra up front. You get the plan for free by describing the idea, and you get to keep it. The tax of AI-native development was never the coding. It was inheriting code nobody wrote down the intent for. The loop pays that tax at the start, once, instead of every time you come back scared to change something.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this means if you're building right now
&lt;/h2&gt;

&lt;p&gt;If you're shipping with agents today, the practical move is not "spec everything" or "spec nothing." It's knowing which side of the line a given build sits on, before you start.&lt;/p&gt;

&lt;p&gt;Ask one question: will anyone other than me depend on this, now or later? If the honest answer is no and will stay no, vibe it and enjoy the speed. That is AI-native development working exactly as advertised, and the disposability is a gift. If the answer is yes, or if there's a real chance a throwaway becomes something people rely on, then the ten minutes you spend writing down what "done" means is the cheapest insurance you will ever buy. You are not adding process. You are refusing to inherit a load-bearing black box.&lt;/p&gt;

&lt;p&gt;The verdict on the hypothesis: AI-native development is real, disposable software is real, and the grief is mostly about not knowing where the line is. The line is dependence. Below it, throwaway is a feature. Above it, the plan and the criteria are what turn the agent's confident output into something you can actually trust. The agent gave you the code for free. The intent is the part you have to keep.&lt;/p&gt;

&lt;h2&gt;
  
  
  FAQ
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What is AI-native development?
&lt;/h3&gt;

&lt;p&gt;AI-native development is an approach to building software where you assume AI agents write most of the code and you organize your work around that assumption. Instead of AI acting as an occasional assistant while you write the code yourself, the agent produces the first draft of almost everything, and your job shifts up a level: deciding what to build, specifying it precisely, and verifying that the result does what you intended. The four recurring patterns are moving from producer to manager, focusing on intent over implementation through spec-driven development, shifting from delivery to discovery, and managing knowledge so context survives across sessions.&lt;/p&gt;

&lt;h3&gt;
  
  
  What are the 4 patterns of AI-native development?
&lt;/h3&gt;

&lt;p&gt;The four patterns, as named by Patrick Debois, are: (1) transitioning from producer to manager, where you operate and review agent output instead of writing every line; (2) focusing on intent over implementation through spec-driven development, where living specifications matter more than granular code; (3) moving from delivery to discovery, where the work shifts from shipping known features to exploring what should exist; and (4) managing agentic knowledge, where feedback from code, tickets, and incidents continuously updates the context your agents work from. All four are about the work surrounding the code, not the code itself.&lt;/p&gt;

&lt;h3&gt;
  
  
  What is the difference between AI-native and AI-first?
&lt;/h3&gt;

&lt;p&gt;AI-first usually means AI is a priority or a default tool in how you work, bolted onto an existing process. AI-native means the process itself was designed from the ground up assuming AI does the core work, the way "cloud-native" meant designing for the cloud rather than lifting an old app into it. In practice, an AI-first team adds an agent to its existing workflow; an AI-native team rebuilds the workflow around the agent, which is why intent, specification, and verification become the load-bearing skills rather than typing speed.&lt;/p&gt;

&lt;h3&gt;
  
  
  Is AI-native development the same as vibe coding?
&lt;/h3&gt;

&lt;p&gt;No, though they overlap. Vibe coding means prompting an agent and accepting what comes back without a written spec or verification, which is a legitimate mode for throwaway work. AI-native development is the broader practice of building around agents, and for anything that needs to last it adds the discipline vibe coding skips: a plan, acceptance criteria, and verification against them. Vibe coding is one style of AI-native work that is fine when the software is disposable and dangerous the moment it becomes something people depend on.&lt;/p&gt;

&lt;h3&gt;
  
  
  Does AI-native development mean software is disposable now?
&lt;/h3&gt;

&lt;p&gt;Only for a specific slice of it. Software that one person uses briefly, a one-off script, a short-lived landing page, a throwaway prototype, genuinely is disposable now, and that category has grown a lot. But the moment other people depend on the software, or a throwaway quietly becomes load-bearing, disposability turns from a feature into a liability. The dividing line is dependence, and what keeps depended-on software from being disposable is a plan and acceptance criteria that outlive the code.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;&lt;a href="https://braingrid.ai" rel="noopener noreferrer"&gt;BrainGrid&lt;/a&gt; is the plan-first app-building platform: it plans before it builds, then verifies every change against acceptance criteria, so the software you depend on never has to be disposable. Try it at &lt;a href="https://braingrid.ai" rel="noopener noreferrer"&gt;braingrid.ai&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://www.braingrid.ai/blog/ai-native-development" rel="noopener noreferrer"&gt;BrainGrid blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>productivity</category>
      <category>career</category>
    </item>
    <item>
      <title>Selenium vs Playwright: Which Should You Use in 2026?</title>
      <dc:creator>Nico Acosta</dc:creator>
      <pubDate>Fri, 07 Aug 2026 11:42:57 +0000</pubDate>
      <link>https://dev.to/grabbit/selenium-vs-playwright-which-should-you-use-in-2026-2idn</link>
      <guid>https://dev.to/grabbit/selenium-vs-playwright-which-should-you-use-in-2026-2idn</guid>
      <description>&lt;p&gt;Playwright and Selenium are the two tools most teams weigh when they automate a browser, and in 2026 the momentum is clearly with Playwright: it just passed Selenium in adoption surveys for the first time. But "which is winning" and "which should you use" are different questions. The right answer depends on your language, your existing suite, and whether you are automating tests or just need an image back from a URL. Here is the honest comparison.&lt;/p&gt;

&lt;h2&gt;
  
  
  The short answer
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;New project, JavaScript or TypeScript or Python:&lt;/strong&gt; Playwright. Auto-waiting, a smaller API, and three bundled browser engines make it the faster path to reliable automation.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Existing enterprise Selenium suite, or a non-JS ecosystem (Java, C#, Ruby):&lt;/strong&gt; Selenium is still a reasonable default. The WebDriver standard, the language breadth, and the grid ecosystem are hard to walk away from.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You need cross-browser tests with the least flakiness:&lt;/strong&gt; Playwright. One API drives Chromium, Firefox, and WebKit, and auto-waiting removes most timing bugs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You just need a screenshot of a URL, not a test framework:&lt;/strong&gt; neither. A &lt;a href="https://www.grabbit.live/screenshot-api" rel="noopener noreferrer"&gt;hosted screenshot API&lt;/a&gt; returns a hosted image from one request, with no browser to run.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The rest of this post shows where the two actually differ so you can decide with the details.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture: WebDriver vs direct control
&lt;/h2&gt;

&lt;p&gt;The core difference is how each tool talks to the browser.&lt;/p&gt;

&lt;p&gt;Selenium drives browsers through the &lt;strong&gt;W3C WebDriver&lt;/strong&gt; protocol. Your script sends commands to a driver (chromedriver, geckodriver), which relays them to the browser over HTTP. That standardization is Selenium's superpower and its tax: it works with almost any browser and language, but every command is a separate round trip.&lt;/p&gt;

&lt;p&gt;Playwright talks to the browser over a &lt;strong&gt;single persistent connection&lt;/strong&gt; using the browser's own debugging protocol. Fewer round trips, and it can observe the page state directly, which is what makes auto-waiting possible.&lt;/p&gt;

&lt;p&gt;This one design choice explains most of the practical differences below: speed, waiting, and reliability all trace back to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Language and browser support
&lt;/h2&gt;

&lt;p&gt;This is where Selenium still leads.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Selenium&lt;/th&gt;
&lt;th&gt;Playwright&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Languages&lt;/td&gt;
&lt;td&gt;Java, Python, C#, Ruby, JavaScript, and more&lt;/td&gt;
&lt;td&gt;JavaScript/TypeScript, Python, Java, C#&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Browsers&lt;/td&gt;
&lt;td&gt;Chrome, Firefox, Edge, Safari (real installs) + huge grid/cloud ecosystem&lt;/td&gt;
&lt;td&gt;Bundled Chromium, Firefox, WebKit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Standard&lt;/td&gt;
&lt;td&gt;W3C WebDriver&lt;/td&gt;
&lt;td&gt;Browser debug protocol (not a standard)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;If your team is on Ruby, or you must drive a specific real browser build on a specific OS through a cloud grid, Selenium's breadth wins. If you want three engines that cover the matrix most apps care about, with zero driver management, Playwright's bundled browsers are simpler.&lt;/p&gt;

&lt;p&gt;Note that "WebKit" in Playwright is the engine behind Safari, not Safari itself, so it is a very close approximation rather than a byte-for-byte Safari render.&lt;/p&gt;

&lt;h2&gt;
  
  
  Auto-waiting: Playwright's biggest day-to-day win
&lt;/h2&gt;

&lt;p&gt;Flaky tests are almost always timing bugs. An element is not clickable yet, or the network has not settled, and a fixed &lt;code&gt;sleep&lt;/code&gt; either wastes time or fails intermittently.&lt;/p&gt;

&lt;p&gt;Selenium makes you manage this. The recommended pattern is an explicit wait:&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;By&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;until&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;selenium-webdriver&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;driver&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Builder&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;forBrowser&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;chrome&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;build&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;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://example.com/login&lt;/span&gt;&lt;span class="dl"&gt;'&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;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;wait&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;until&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;elementLocated&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;By&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#submit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)),&lt;/span&gt; &lt;span class="mi"&gt;5000&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;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findElement&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;By&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;css&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#submit&lt;/span&gt;&lt;span class="dl"&gt;'&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;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;quit&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Playwright waits for you. Its locators auto-wait for the element to be attached, visible, and actionable before acting, so the same flow needs no explicit wait:&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="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;chromium&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;playwright&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;browser&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;chromium&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&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;page&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;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;newPage&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;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://example.com/login&lt;/span&gt;&lt;span class="dl"&gt;'&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;locator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#submit&lt;/span&gt;&lt;span class="dl"&gt;'&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;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Across a full suite, deleting hundreds of &lt;code&gt;wait&lt;/code&gt; and &lt;code&gt;sleep&lt;/code&gt; calls is the single biggest reliability and maintenance improvement teams report after migrating.&lt;/p&gt;

&lt;h2&gt;
  
  
  Speed
&lt;/h2&gt;

&lt;p&gt;Playwright is generally faster, and the architecture explains why. Selenium's per-command HTTP round trips add up over a long test, and explicit waits are often padded to be safe. Playwright's single connection and auto-waiting mean it acts as soon as the page is ready, not after a fixed delay.&lt;/p&gt;

&lt;p&gt;The gap is real on multi-step flows. On a single screenshot it is mostly noise, because page load time dominates and both tools spend that time the same way.&lt;/p&gt;

&lt;h2&gt;
  
  
  Locators: role and text over XPath
&lt;/h2&gt;

&lt;p&gt;Selenium's world is CSS and XPath selectors tied to the DOM structure. That works, but a renamed class or an extra wrapper &lt;code&gt;div&lt;/code&gt; breaks the locator.&lt;/p&gt;

&lt;p&gt;Playwright still supports CSS and XPath, but pushes you toward user-facing locators that read the accessibility tree:&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="c1"&gt;// Playwright: resilient, user-facing locators&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;getByRole&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Submit&lt;/span&gt;&lt;span class="dl"&gt;'&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;getByLabel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Email&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;fill&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user@example.com&lt;/span&gt;&lt;span class="dl"&gt;'&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;getByText&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Welcome back&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;waitFor&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These survive DOM refactors because they find the button labelled "Submit" rather than its position in the markup. It is a small API difference with a big effect on how often tests break when the frontend changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Migrating from Selenium to Playwright
&lt;/h2&gt;

&lt;p&gt;Migration is worth it when flaky tests and explicit-wait upkeep are costing you real time, and your app is JavaScript-heavy. The wait-code deletion alone often pays for the move.&lt;/p&gt;

&lt;p&gt;It is not worth it when your Selenium suite is stable, your team lives in a language Playwright serves poorly, or your grid, reporting, and CI are deeply wired into WebDriver. A working suite is an asset; do not rewrite it for fashion.&lt;/p&gt;

&lt;p&gt;If you do migrate, do it incrementally: run new tests in Playwright while the Selenium suite keeps guarding old ground, and port flaky tests first, because those are exactly the ones auto-waiting fixes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Screenshots: where both tools are overkill
&lt;/h2&gt;

&lt;p&gt;Both frameworks take screenshots, and the code is nearly identical. In Selenium:&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;image&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;driver&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;takeScreenshot&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;page.png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64&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;In Playwright:&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;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="s1"&gt;page.png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;fullPage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But if a screenshot is the &lt;em&gt;only&lt;/em&gt; thing you need, either tool is a lot of moving parts. You are installing a driver or a bundled browser, running headless Chromium in your own infrastructure, and owning the parts nobody enjoys:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Provisioning.&lt;/strong&gt; Headless browsers need a long list of system libraries. Slim containers and serverless functions hit missing-dependency errors before the first pixel.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory and zombie processes.&lt;/strong&gt; A browser not closed on every error path leaks memory and orphans processes until the box falls over.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Patching.&lt;/strong&gt; Browser engines ship security updates constantly, so a long-lived capture service becomes a browser fleet you keep current.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Concurrency.&lt;/strong&gt; One browser does one job at a time well. Thousands of captures a day means a pool, a queue, and back-pressure to build and operate.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of that is test code, and it is the same whether you picked Selenium or Playwright.&lt;/p&gt;

&lt;h2&gt;
  
  
  The same capture as one API call
&lt;/h2&gt;

&lt;p&gt;When screenshots are a feature you ship rather than a step inside a test, a hosted &lt;a href="https://www.grabbit.live/screenshot-api" rel="noopener noreferrer"&gt;screenshot API&lt;/a&gt; skips the browser entirely. Here is a full-page capture as a single request to Grabbit:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;curl https://api.grabbit.live/v1/grabs &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Authorization: Bearer sk_live_..."&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-H&lt;/span&gt; &lt;span class="s2"&gt;"Content-Type: application/json"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;'{
    "url": "https://example.com",
    "width": 1280,
    "full_page": true,
    "format": "webp"
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The response includes a hosted &lt;code&gt;image_url&lt;/code&gt; you can use directly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"grb_01jx..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"status"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"done"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"image_url"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://cdn.grabbit.live/grabs/grb_01jx....webp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"width"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1280&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"format"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"webp"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"bytes"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;48210&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"execution_ms"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1180&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The options you reach for in either framework map onto request parameters: &lt;code&gt;fullPage&lt;/code&gt; becomes &lt;code&gt;full_page&lt;/code&gt;, a locator or selector becomes the &lt;code&gt;selector&lt;/code&gt; field, and a manual wait becomes &lt;code&gt;delay_ms&lt;/code&gt; (0 to 10000). Width accepts 320 to 1920, height 240 to 1080, and &lt;code&gt;format&lt;/code&gt; is &lt;code&gt;png&lt;/code&gt;, &lt;code&gt;jpeg&lt;/code&gt;, or &lt;code&gt;webp&lt;/code&gt;. Pricing is a flat $0.002 per capture on prepaid credits that never reset, so a screenshot you run once a week costs the same per image as one you run a thousand times a day.&lt;/p&gt;

&lt;h2&gt;
  
  
  Which to choose
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Greenfield project, want the least flakiness:&lt;/strong&gt; Playwright.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Existing enterprise suite, or a non-JS stack, or a specific real-browser grid:&lt;/strong&gt; Selenium.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Learning automation from scratch in 2026:&lt;/strong&gt; start with Playwright, add Selenium for enterprise roles.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You just need an image from a URL, not a test framework:&lt;/strong&gt; skip the browser and call an API.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the framework-specific deep dives, see &lt;a href="https://www.grabbit.live/blog/playwright-screenshot" rel="noopener noreferrer"&gt;taking screenshots in Playwright&lt;/a&gt; and &lt;a href="https://www.grabbit.live/blog/selenium-screenshot" rel="noopener noreferrer"&gt;taking screenshots in Selenium&lt;/a&gt;. If you are weighing hosted options, the &lt;a href="https://www.grabbit.live/blog/best-screenshot-api" rel="noopener noreferrer"&gt;honest comparison of screenshot APIs&lt;/a&gt; covers the trade-offs without the marketing, and &lt;a href="https://www.grabbit.live/blog/puppeteer-vs-playwright" rel="noopener noreferrer"&gt;Puppeteer vs Playwright&lt;/a&gt; compares the two Chromium-first libraries head to head.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Originally published on the &lt;a href="https://www.grabbit.live/blog/selenium-vs-playwright" rel="noopener noreferrer"&gt;Grabbit blog&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>node</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
