cdpfleet Docs Sign up free

Examples & benchmarks / #75 · 2026-10-29 · Benchmark · Medium · for web scraping

HUMAN on real sites: twelve browsers, three sites, one exit each

Three real sites behind HUMAN (formerly PerimeterX), chosen from our sweep as easy, medium and hard, loaded once by each of twelve browsers and by a returning Camoufox profile, through one sticky residential exit per site, with the HUMAN cookies each load got. Dated 2026-10-29.

Google Chrome Microsoft Edge Brave Opera Yandex Browser Naver Whale Chromium Patchright Firefox WebKit Camoufox

Run on production on 2026-10-29: ✓ Node.js ✓ Python ✓ Java ✓ C# ✓ Go

A benchmark: the results below are what this setup measured on the dates shown, on our own test runs. Sites, networks and browsers change, so rerun the code before you rely on the numbers.

The problem

HUMAN's own site (case 67) shows its Press & Hold challenge, but every real site sets its own rules. This case takes three real sites behind HUMAN from our sweep of production sites, one that let headless Chrome in (easy), one that stopped headless Chrome but not Camoufox (medium) and one that stopped Camoufox too (hard), and loads each once with every browser the fleet offers, plus a returning Camoufox profile, through one sticky residential exit per site, as case 70, case 71 and case 73 did for Cloudflare, DataDome and Akamai. It also records which HUMAN cookies (_px3, _pxvid, _pxhd, pxcts …) each load got, by name only. The sites aren't named: they stand for a kind of site, and their rules can change any day. It is a dated measurement, not an evasion guide; for the signals a page can read, see What a page can read about your browser, and for the whole picture /bot-protection.

What we used, and why

WhatWhy
Twelve browsersChrome headful and headless, Edge, Brave, Opera, Yandex, Whale, Chromium headless and Patchright (all Chromium), Firefox, WebKit, and Camoufox (based on Firefox source; not affiliated with Mozilla). Rebrowser needs a Playwright 1.52 client, so it's left out of a program written for 1.60.
proxy: cdpfleet-resi-country-us-session-<id>One sticky residential exit per site, shared by every setup on that site, so a difference between rows is the browser.
One session at a time per siteEach site's setups run one after another; the three sites run side by side.
page.evaluate() after 8 sHUMAN's Press & Hold widget (#px-captcha), or a short page asking to verify you are human, is challenge — usually on a page titled "Access to this page has been denied", but interactive, so not a block; an "access denied" or "has been blocked" page without it, or an error status with a short page, is blocked; anything else content. Nothing is clicked.
context.cookies()The names of the cookies HUMAN set (_px3, _pxvid, _pxhd, _pxde, pxcts …) 8 s after the load. Values are never read or set.
profileA returning visitor: Camoufox loads the site once with a new profile, the profile is saved, and a second session with it loads the site again.
LAB_SITES_HUMANThe three URLs come from the environment, so the published programs don't name the sites; set your own to repeat the measurement.

How it works

  1. For each site, take one sticky residential exit and, one session at a time, open each of the twelve setups, load the page once, wait 8 s, and read the page and HUMAN's cookies.
  2. Classify what the page shows and record the HTTP status of its first response; a network error gets one retry.
  3. Then load the site with Camoufox and a new profile, wait for the profile to be saved, load it again with the same profile and record the second visit. Delete the profile.

The code

The same program in five languages (also on GitHub, with the raw output). Set these environment variables first:

// npm install [email protected]
// env: CDPFLEET_API_KEY, LAB_SITES_HUMAN (comma-separated URLs of the pages to test; one load per page per setup, nothing clicked)
import { randomBytes } from 'node:crypto';
import { chromium, firefox, webkit } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;
const RUN = randomBytes(3).toString('hex');
const SITES = process.env.LAB_SITES_HUMAN.split(',').map((url, i) => ({ site: String.fromCharCode(65 + i), url }));
// Every browser the fleet offers that a Playwright 1.60 client can drive (Rebrowser needs 1.52, so it's left out).
const SETUPS = [
  ['chrome_headful', 'Chrome, headful', 'chrome', chromium, false],
  ['chrome_headless', 'Chrome, headless', 'chrome', chromium, 'new'],
  ['edge', 'Edge, headful', 'edge', chromium, false],
  ['brave', 'Brave, headful', 'brave', chromium, false],
  ['opera', 'Opera, headful', 'opera', chromium, false],
  ['yandex', 'Yandex, headful', 'yandex', chromium, false],
  ['whale', 'Whale, headful', 'whale', chromium, false],
  ['chromium_headless', 'Chromium, headless', 'chromium', chromium, true],
  ['patchright', 'Patchright, headful', 'patchright', chromium, false],
  ['firefox', 'Firefox, headful', 'firefox', firefox, false],
  ['webkit', 'WebKit, headful', 'webkit', webkit, false],
  ['camoufox', 'Camoufox, headful', 'camoufox', firefox, false],
];

// What the page shows after it loaded and had 8 s to run its checks, in the lab's words. HUMAN's
// challenge is its "Press & Hold" widget (#px-captcha), usually on a page titled "Access to this page
// has been denied" — interactive, so it counts as a challenge, not a block.
const STATE = `(() => {
  const t = (document.title + ' ' + (document.body ? document.body.innerText : '').slice(0, 600)).replace(/\\s+/g, ' ');
  const len = document.body ? document.body.innerText.length : 0;
  if (document.querySelector('#px-captcha') || (/press (&|and) hold|human touch|verify you are (a )?human|are you a robot|before we continue/i.test(t) && len < 4000)) return 'challenge';
  if (/access (to this page has been )?denied|has been blocked|you don.t have permission to access|request unsuccessful|reference #/i.test(t) && len < 4000) return 'blocked';
  return 'content';
})()`;

// HUMAN's cookies (_px3, _pxhd, _pxvid, pxcts …): which ones the site set, by name only — values are never read.
const pxCookies = (cookies) => cookies.map((c) => c.name).filter((n) => /^_?px/i.test(n)).sort();

async function launch(engine, headless, proxy, profile) {
  for (let attempt = 1; ; attempt++) {
    const res = await fetch(`https://starter.cdpfleet.com/${engine}/session`, {
      method: 'POST',
      headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
      body: JSON.stringify({ proxy, headless, record: false, ...(profile ? { profile } : {}) }),
    });
    if (res.ok) return (await res.json()).wsUrl;
    if (![429, 503].includes(res.status) || attempt === 5) throw new Error(`launch ${res.status} ${await res.text()}`);
    await new Promise((r) => setTimeout(r, (Number(res.headers.get('retry-after')) || 5) * 1000));
  }
}

// One session, one load: the status of the page's own response, then the page's state and HUMAN's
// cookies 8 s later.
async function visit(setup, url, proxy, profile) {
  const [, , engine, type, headless] = setup;
  const browser = await type.connect(await launch(engine, headless, proxy, profile), { headers: { 'x-api-key': KEY } });
  try {
    const context = profile ? browser.contexts()[0] : await browser.newContext();
    const tab = await context.newPage();
    let status = 0;
    try {
      status = (await tab.goto(url, { waitUntil: 'domcontentloaded', timeout: 45000 }))?.status() ?? 0;
    } catch (err) {
      // A reset HTTP/2 connection is a refusal, not a network failure.
      return { outcome: /ERR_HTTP2_PROTOCOL_ERROR/.test(err.message) ? 'blocked' : 'error', status: 0, px_cookies: [] };
    }
    await tab.waitForTimeout(8000);
    let outcome = await tab.evaluate(STATE).catch(() => 'error');
    if (outcome === 'content' && status >= 400) outcome = 'blocked';
    return { outcome, status, px_cookies: pxCookies(await context.cookies()) };
  } finally {
    await browser.close();
  }
}

async function api(method, path) {
  const res = await fetch(`https://cdpfleet.com${path}`, { method, headers: { 'x-api-key': KEY } });
  return res.json().catch(() => ({}));
}
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));

// One sticky residential exit per site; its setups run one after another (one session at a time per
// site), and the sites run side by side. A network error gets one more try.
async function site({ site, url }) {
  const proxy = `cdpfleet-resi-country-us-session-c75${RUN}${site}-lifetime-30`;
  const results = {};
  for (const setup of SETUPS) {
    let r = await visit(setup, url, proxy).catch(() => ({ outcome: 'error', status: 0, px_cookies: [] }));
    if (r.outcome === 'error') r = await visit(setup, url, proxy).catch(() => ({ outcome: 'error', status: 0, px_cookies: [] }));
    results[setup[0]] = r;
  }
  // A returning visitor: Camoufox with a profile, a first visit and then a second session.
  const name = `case75-${RUN}-${site}`;
  const camoufox = SETUPS.find((s) => s[0] === 'camoufox');
  await visit(camoufox, url, proxy, name).catch(() => null);
  for (let j = 0; j < 60 && (await api('GET', '/v1/me/profiles')).profiles?.find((p) => p.name === name)?.in_use !== false; j++) await sleep(2000);
  results.camoufox_returning = await visit(camoufox, url, proxy, name).catch(() => ({ outcome: 'error', status: 0, px_cookies: [] }));
  await api('DELETE', `/v1/me/profiles/${name}`);
  return { site, results };
}

const sites = await Promise.all(SITES.map(site));
console.log(JSON.stringify({ measured_at: new Date().toISOString().slice(0, 10), setups: Object.fromEntries([...SETUPS.map((s) => [s[0], s[1]]), ['camoufox_returning', 'Camoufox, returning profile (2nd visit)']]), sites }, null, 2));

What we got

SetupSite A, a big-box retailerSite B, a real-estate marketplaceSite C, a freelance marketplaceMeasured
Chrome, headfulcontent (200)errorchallenge (403)2026-10-29
Chrome, headlesscontent (200)challenge (403)challenge (403)2026-10-29
Edge, headfulcontent (200)errorchallenge (403)2026-10-29
Brave, headfulcontent (200)content (200)challenge (403)2026-10-29
Opera, headfulcontent (200)content (200)challenge (403)2026-10-29
Yandex, headfulcontent (200)content (200)content (200)2026-10-29
Whale, headfulcontent (200)content (200)content (200)2026-10-29
Chromium, headlesscontent (200)challenge (403)challenge (403)2026-10-29
Patchright, headfulcontent (200)content (200)challenge (403)2026-10-29
Firefox, headfulchallenge (200)content (200)content (200)2026-10-29
WebKit, headfulcontent (200)content (200)content (200)2026-10-29
Camoufox, headfulcontent (200)content (200)content (200)2026-10-29
Camoufox, returning profile (2nd visit)challenge (200)content (200)challenge (403)2026-10-29

From the Node.js run on 2026-10-29. IP addresses are replaced with placeholders (203.0.113.x); equal addresses stay equal. The other languages produced the same findings.

Raw output (Node.js)
{
  "measured_at": "2026-10-29",
  "setups": {
    "chrome_headful": "Chrome, headful",
    "chrome_headless": "Chrome, headless",
    "edge": "Edge, headful",
    "brave": "Brave, headful",
    "opera": "Opera, headful",
    "yandex": "Yandex, headful",
    "whale": "Whale, headful",
    "chromium_headless": "Chromium, headless",
    "patchright": "Patchright, headful",
    "firefox": "Firefox, headful",
    "webkit": "WebKit, headful",
    "camoufox": "Camoufox, headful",
    "camoufox_returning": "Camoufox, returning profile (2nd visit)"
  },
  "sites": [
    {
      "site": "A",
      "results": {
        "chrome_headful": {
          "outcome": "content",
          "status": 200,
          "px_cookies": [
            "_pxhd"
          ]
        },
        "chrome_headless": {
          "outcome": "content",
          "status": 200,
          "px_cookies": [
            "_px3",
            "_pxde",
            "_pxvid",
            "pxcts"
          ]
        },
        "edge": {
          "outcome": "content",
          "status": 200,
          "px_cookies": [
            "_px3",
            "_pxde",
            "_pxhd",
            "_pxvid",
            "pxcts"
          ]
        },
        "brave": {
          "outcome": "content",
          "status": 200,
          "px_cookies": [
            "_px3",
            "_pxde",
            "_pxhd",
            "_pxvid",
            "pxcts"
          ]
        },
        "opera": {
          "outcome": "content",
          "status": 200,
          "px_cookies": [
            "_px3",
            "_pxde",
            "_pxhd",
            "_pxvid",
            "pxcts"
          ]
        },
        "yandex": {
          "outcome": "content",
          "status": 200,
          "px_cookies": [
            "_px3",
            "_pxde",
            "_pxhd",
            "_pxvid",
            "pxcts"
          ]
        },
        "whale": {
          "outcome": "content",
          "status": 200,
          "px_cookies": [
            "_px3",
            "_pxde",
            "_pxhd",
            "_pxvid",
            "pxcts"
          ]
        },
        "chromium_headless": {
          "outcome": "content",
          "status": 200,
          "px_cookies": [
            "_px3",
            "_pxde",
            "_pxvid",
            "pxcts"
          ]
        },
        "patchright": {
          "outcome": "content",
          "status": 200,
          "px_cookies": [
            "_px3",
            "_pxde",
            "_pxhd",
            "_pxvid",
            "pxcts"
          ]
        },
        "firefox": {
          "outcome": "challenge",
          "status": 200,
          "px_cookies": [
            "_px3",
            "_pxde",
            "_pxhd",
            "_pxvid",
            "pxcts"
          ]
        },
        "webkit": {
          "outcome": "content",
          "status": 200,
          "px_cookies": [
            "_px3",
            "_pxde",
            "_pxhd",
            "_pxvid",
            "pxcts"
          ]
        },
        "camoufox": {
          "outcome": "content",
          "status": 200,
          "px_cookies": [
            "_px3",
            "_pxde",
            "_pxhd",
            "_pxvid",
            "pxcts"
          ]
        },
        "camoufox_returning": {
          "outcome": "challenge",
          "status": 200,
          "px_cookies": [
            "_px3",
            "_pxde",
            "_pxhd",
            "_pxvid",
            "pxcts"
          ]
        }
      }
    },
    {
      "site": "B",
      "results": {
        "chrome_headful": {
          "outcome": "error",
          "status": 0,
          "px_cookies": []
        },
        "chrome_headless": {
          "outcome": "challenge",
          "status": 403,
          "px_cookies": []
        },
        "edge": {
          "outcome": "error",
          "status": 0,
          "px_cookies": []
        },
        "brave": {
          "outcome": "content",
          "status": 200,
          "px_cookies": []
        },
        "opera": {
          "outcome": "content",
          "status": 200,
          "px_cookies": [
            "_px3",
            "_pxvid",
            "pxcts"
          ]
        },
        "yandex": {
          "outcome": "content",
          "status": 200,
          "px_cookies": []
        },
        "whale": {
          "outcome": "content",
          "status": 200,
          "px_cookies": [
            "_px3",
            "_pxvid",
            "pxcts"
          ]
        },
        "chromium_headless": {
          "outcome": "challenge",
          "status": 403,
          "px_cookies": []
        },
        "patchright": {
          "outcome": "content",
          "status": 200,
          "px_cookies": [
            "_px3",
            "_pxvid",
            "pxcts"
          ]
        },
        "firefox": {
          "outcome": "content",
          "status": 200,
          "px_cookies": []
        },
        "webkit": {
          "outcome": "content",
          "status": 200,
          "px_cookies": []
        },
        "camoufox": {
          "outcome": "content",
          "status": 200,
          "px_cookies": [
            "_px3",
            "_pxvid",
            "pxcts"
          ]
        },
        "camoufox_returning": {
          "outcome": "content",
          "status": 200,
          "px_cookies": [
            "_px3",
            "_pxvid",
            "pxcts"
          ]
        }
      }
    },
    {
      "site": "C",
      "results": {
        "chrome_headful": {
          "outcome": "challenge",
          "status": 403,
          "px_cookies": [
            "_px3",
            "_pxde",
            "_pxhd",
            "_pxvid",
            "pxcts"
          ]
        },
        "chrome_headless": {
          "outcome": "challenge",
          "status": 403,
          "px_cookies": [
            "_px3",
            "_pxde",
            "_pxhd",
            "_pxvid",
            "pxcts"
          ]
        },
        "edge": {
          "outcome": "challenge",
          "status": 403,
          "px_cookies": [
            "_px3",
            "_pxde",
            "_pxhd",
            "_pxvid",
            "pxcts"
          ]
        },
        "brave": {
          "outcome": "challenge",
          "status": 403,
          "px_cookies": [
            "_px3",
            "_pxde",
            "_pxhd",
            "_pxvid",
            "pxcts"
          ]
        },
        "opera": {
          "outcome": "challenge",
          "status": 403,
          "px_cookies": [
            "_px3",
            "_pxde",
            "_pxhd",
            "_pxvid",
            "pxcts"
          ]
        },
        "yandex": {
          "outcome": "content",
          "status": 200,
          "px_cookies": [
            "_px3",
            "_pxde",
            "_pxhd",
            "_pxvid",
            "pxcts"
          ]
        },
        "whale": {
          "outcome": "content",
          "status": 200,
          "px_cookies": [
            "_px3",
            "_pxde",
            "_pxhd",
            "_pxvid",
            "pxcts"
          ]
        },
        "chromium_headless": {
          "outcome": "challenge",
          "status": 403,
          "px_cookies": [
            "_px3",
            "_pxde",
            "_pxhd",
            "_pxvid",
            "pxcts"
          ]
        },
        "patchright": {
          "outcome": "challenge",
          "status": 403,
          "px_cookies": [
            "_px3",
            "_pxde",
            "_pxhd",
            "_pxvid",
            "pxcts"
          ]
        },
        "firefox": {
          "outcome": "content",
          "status": 200,
          "px_cookies": [
            "_px3",
            "_pxde",
            "_pxhd",
            "_pxvid",
            "pxcts"
          ]
        },
        "webkit": {
          "outcome": "content",
          "status": 200,
          "px_cookies": [
            "_px3",
            "_pxde",
            "_pxhd",
            "_pxvid",
            "pxcts"
          ]
        },
        "camoufox": {
          "outcome": "content",
          "status": 200,
          "px_cookies": [
            "_px3",
            "_pxde",
            "_pxhd",
            "_pxvid",
            "pxcts"
          ]
        },
        "camoufox_returning": {
          "outcome": "challenge",
          "status": 403,
          "px_cookies": [
            "_px3",
            "_pxde",
            "_pxhd",
            "_pxvid",
            "pxcts"
          ]
        }
      }
    }
  ]
}

Takeaways