Your Test Runs 40 Times a Day and Logging In Isn't Free

Picture a Puppeteer script that checks a dashboard every fifteen minutes, or a Selenium suite that runs on every pull request. Each run that starts from a login form burns time on a password field, maybe an OTP prompt, maybe a CAPTCHA that only shows up for "suspicious" automated traffic. Multiply that by dozens of runs a day and the login step becomes the slowest, flakiest part of the whole pipeline.

The fix most working automation engineers land on is session reuse: log in once by hand in a real browser, export the cookies that prove you're authenticated, and hand those cookies to the automation tool so it starts already logged in. Puppeteer and Selenium both support this, but they don't accept cookies in the form your browser gives them. That mismatch, and how to close it, is what this post walks through.

Two Formats, One Cookie

When your browser makes a request, it sends cookies as a single flat string in the Cookie header:

Cookie: session_id=abc123xyz; theme=dark; ref=newsletter

That string is all a server ever sees. It has no idea about expiry dates, which domain the cookie belongs to, or whether it was marked httpOnly. Those extra properties exist only in the browser's internal cookie store, and that store is exactly what Puppeteer's page.setCookie() and Selenium's driver.add_cookie() expect to be handed back: not a header string, but an array of objects carrying the full metadata.

A single converted cookie looks like this:

{
  "name": "session_id",
  "value": "abc123xyz",
  "domain": ".example.com",
  "path": "/",
  "expires": 1790000000,
  "httpOnly": true,
  "secure": true,
  "sameSite": "Lax"
}

Going from the header string to this object means supplying the fields the header never carried. Going the other direction, from JSON back to a header string, is simpler: join each name=value pair with "; " and drop everything else. Our cookie converter handles both directions locally in your browser, but understanding each field pays off the first time an injected cookie silently fails to work.

What Each Field Actually Controls

  • name and value: the payload itself. This is the only part a raw Cookie header preserves, so if you're starting from a copied header string, everything else below has to be filled in manually or with sensible defaults.
  • domain: which hosts the cookie is sent to. This is the field that causes the most confusion. A value of example.com matches only that exact host, while .example.com with a leading dot matches example.com and every subdomain like app.example.com or www.example.com. Copy the domain wrong (add or drop that leading dot) and the cookie sits in the store doing nothing, with no error message anywhere.
  • path: restricts the cookie to a URL prefix. Almost always / for session cookies, meaning "send on every path," but some sites scope cookies to /admin or /api specifically.
  • expires: a Unix timestamp (seconds since epoch) for when the cookie should stop being valid. Leave it unset for a session cookie that should die when the browser closes. A timestamp already in the past gets the cookie thrown out immediately by both Puppeteer and Selenium, which is a common cause of "it just doesn't show up."
  • httpOnly: true means client-side JavaScript can't read the cookie via document.cookie. It has no bearing on whether automation tools can set it; it only affects in-page scripts.
  • secure: true means the cookie is only sent over HTTPS. If your automation navigates to a plain http:// URL first, a secure cookie won't be honored.
  • sameSite: one of Strict, Lax, or None. Controls whether the cookie is sent on cross-site requests. Setting None requires secure: true in modern browsers, and getting this pairing wrong is a frequent silent-failure source.

Pulling the Real Values Out of DevTools

Guessing at these fields is how sessions break. The reliable path is copying them straight from the browser that's already logged in:

  1. Log in normally in Chrome or Firefox.
  2. Open DevTools (F12), go to the Application tab (Chrome) or Storage tab (Firefox), then Cookies, then select your domain.
  3. You'll see a table with every cookie's name, value, domain, path, expiry, and flag columns. This table is the source of truth. Don't reconstruct domain or expiry from memory; read them off this list.
  4. If you only need the flat string, open the Network tab, click any request to the site, and look at the Cookie request header under Headers.
  5. Feed either form into our cookie converter to get a clean JSON array, or type the objects out by hand using the DevTools values.

Loading the Cookies Into Puppeteer

Puppeteer's page.setCookie() takes one or more cookie objects as separate arguments, so a JSON array needs to be spread:

const fs = require('fs');
const puppeteer = require('puppeteer');

(async () => {
  const cookies = JSON.parse(fs.readFileSync('./cookies.json', 'utf8'));
  const browser = await puppeteer.launch();
  const page = await browser.newPage();

  // Navigate first so the page context matches the cookie domain
  await page.goto('https://example.com');
  await page.setCookie(...cookies);

  // Now load the page that requires authentication
  await page.goto('https://example.com/dashboard');
  await page.screenshot({ path: 'dashboard.png' });
  await browser.close();
})();

Newer Puppeteer versions will accept cookies before any navigation has happened, but older versions and some edge cases are pickier about an initial goto() existing first. Doing the navigate-then-set-then-navigate-again sequence above works reliably across versions and costs nothing extra.

Loading the Cookies Into Selenium

Selenium's add_cookie() takes one dictionary at a time, so a JSON array needs a loop:

# Python
import json
from selenium import webdriver

driver = webdriver.Chrome()

# You MUST be on the target domain before add_cookie will accept anything
driver.get("https://example.com")

with open("cookies.json") as f:
    cookies = json.load(f)

for cookie in cookies:
    # Selenium rejects a "sameSite" or "expiry" key it doesn't recognize
    # in some driver versions, so strip anything unexpected first
    cookie.pop("sameSite", None)
    driver.add_cookie(cookie)

driver.get("https://example.com/dashboard")

The line that trips up almost everyone the first time is the initial driver.get("https://example.com"). Selenium's WebDriver protocol only allows adding a cookie for the domain the browser is currently sitting on; call add_cookie() before any navigation and it throws an InvalidCookieDomainException. Land on the domain first, even a blank or 404 page on that host is enough, then add the cookies, then navigate to wherever you actually need to be.

When an injected session "just doesn't work," check these three things in order: is the domain field exactly right including the leading dot, has the expires timestamp already passed, and did you navigate to the target domain before calling add_cookie or setCookie. Those three cover the overwhelming majority of failures.

Why This Matters More Than It Looks Like It Should

A session cookie is not a convenience token, it is the login itself. Whoever holds a valid, unexpired session cookie is authenticated as that account with no password and no second factor required, which is the exact same mechanism behind session hijacking attacks, just used here on purpose against your own account. A few practical consequences follow directly from that fact:

  • A cookies.json file is a live credential, not a config file. Add it to .gitignore immediately, never commit it, and never paste it into a shared chat or ticket. Anyone who gets that file gets your session for as long as it lasts.
  • Sessions expire and rotate on purpose. Logging out, changing a password, or the site's own timeout schedule can invalidate the cookie set you saved. Treat exported cookies as short-lived by design, and re-export before a long-running automation job rather than assuming an old file still works.
  • Only reuse sessions for accounts you control. Injecting someone else's cookies to access their account, even ones you found or were casually handed, crosses from automation convenience into unauthorized access, and that line is both a terms-of-service violation and, in most jurisdictions, a legal one.
  • For CI pipelines, cookie injection is a stopgap, not a foundation. A real login flow against a dedicated test account, or an authenticated API call that returns a token, is more durable than a hand-exported cookie file that expires without warning. Save cookie injection for local scripts and short-lived tasks where the convenience outweighs the fragility.

Frequently Asked Questions

Why did my cookie import successfully but the page still shows me as logged out?

The cookie object was accepted without error but the session still isn't recognized, which almost always means the domain doesn't match what the server expects. Recheck whether the original had a leading dot, and confirm you're navigating to the same host (including subdomain) the cookie was captured from.

Do I need every field, or just name and value?

Name and value are the only fields that actually carry data, but Puppeteer and Selenium both use domain and path to decide where to store the cookie internally. Omit them and the tool falls back to defaults based on the current page, which sometimes works and sometimes silently doesn't. Copying the real values from DevTools removes the guesswork.

Can I go from JSON back to a plain Cookie header string?

Yes. Join each object's name=value pair with "; " between them, dropping domain, path, and flags entirely, since the header format never carried them anyway. This is the format tools like curl expect for a -b flag. Our converter does this conversion in both directions.

My cookies work for the first few requests, then the session drops. What's happening?

That's usually a short expires value, or the site rotating the session token after a specific action (many sites reissue a fresh session cookie on any state change, like changing a setting). A cookie captured minutes ago can be stale by the time a longer automation run gets to it, so re-export immediately before a run rather than relying on a file from an earlier session.

Is it risky to paste live cookies into an online converter tool?

It's risky if the tool sends what you paste to a server. Only use converters that process everything client-side in your own browser, with nothing uploaded, which is how ours works. Even then, treat a live session cookie the same way you'd treat a live password: prefer converting cookies from a disposable test account when one is available, and clear anything you don't need to keep around afterward.

Shoyeb Akter

Written by

Security Tools Developer and creator of 2FA Fast, a privacy-first browser-based authenticator and security tools platform.