cdpfleet Docs GitHub Dashboard

Cases / #3 · 2026-09-30 · Easy

Stable, beta, dev and an older major: four Chromes side by side

Pick a channel or pin a previous major, read the live catalog instead of hard-coding versions, and see what actually differs on the wire.

Google Chrome

Run on production on 2026-09-30: ✓ Node.js ✓ Python ✓ Java ✓ C# ✓ Go

The problem

A site breaks on the new Chrome, or you need to test the next release before your users get it, or a target only trusts the current version. cdpfleet runs Chrome stable, beta and dev, plus previous majors. Which options select which build — and does the version change the fingerprint a site sees?

What we used, and why

WhatWhy
GET /api/public/browsersThe live catalog: which channels and previous majors exist right now. Majors rotate, so read it instead of hard-coding numbers.
channel: "beta" / "dev"Selects a pre-release build. Omit it (or send "stable") for stable.
version: "152"Pins a retained previous major. A major the fleet doesn't have returns 503 version_unavailable instead of silently running another. Don't combine it with a pre-release channel (that's a 400).
headless: falseA real (virtual) display, so the user agent reads Chrome/…, not HeadlessChrome/….

How it works

  1. Read the catalog and build one variant per listed Chrome build.
  2. Launch all variants at once — each is its own session.
  3. In each, compare browser.version() with the catalog, and read the user agent, sec-ch-ua and fingerprints from tls.peet.ws.

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, PROXY_URL
import { chromium } from 'playwright';

const KEY = process.env.CDPFLEET_API_KEY;

// The live catalog says which channels and previous majors exist right now.
const catalog = await (await fetch('https://cdpfleet.com/api/public/browsers')).json();
const versions = catalog.engines.find((e) => e.key === 'chrome').versions;
const variants = versions.map((v) => (v.label === 'pinned'
  ? { label: `version ${v.version.split('.')[0]}`, options: { version: v.version.split('.')[0] }, catalog: v.version }
  : { label: `channel ${v.label}`, options: v.label === 'stable' ? {} : { channel: v.label }, catalog: v.version }));

async function probe({ label, options, catalog: expected }) {
  const res = await fetch('https://starter.cdpfleet.com/chrome/session', {
    method: 'POST',
    headers: { 'x-api-key': KEY, 'content-type': 'application/json' },
    // headless: false (a real display) so the user agent doesn't say "HeadlessChrome".
    body: JSON.stringify({ proxy: process.env.PROXY_URL, headless: false, ...options }),
  });
  if (!res.ok) return { variant: label, error: `${res.status} ${await res.text()}` };
  const { wsUrl } = await res.json();
  const browser = await chromium.connect(wsUrl, { headers: { 'x-api-key': KEY } });
  try {
    // A residential exit occasionally times out: one retry, in a fresh tab.
    const load = async () => (await browser.newPage()).goto('https://tls.peet.ws/api/all', { timeout: 30000 });
    const fp = await (await load().catch(load)).json();
    const headers = fp.http2.sent_frames.find((f) => f.frame_type === 'HEADERS').headers;
    return {
      variant: label,
      catalog_version: expected,
      browser_version: browser.version(),
      user_agent: fp.user_agent,
      sec_ch_ua: headers.find((h) => h.startsWith('sec-ch-ua: '))?.slice(11) ?? null,
      ja4: fp.tls.ja4,
      akamai_h2_hash: fp.http2.akamai_fingerprint_hash,
    };
  } finally {
    await browser.close();
  }
}

// All variants at once: each is its own session.
console.log(JSON.stringify(await Promise.all(variants.map(probe)), null, 2));

What we got

Variantbrowser.version()User agentsec-ch-uaJA4
channel stable153.0.8010.36Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/203.0.113.1 Safari/537.36"Google Chrome";v="153", "Not_A Brand";v="8", "Chromium";v="153"t13d1517h2_8daaf6152771_cb7bf5808d99
channel beta155.0.8059.12Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/203.0.113.2 Safari/537.36"Google Chrome";v="155", "Chromium";v="155", "Not(A:Brand";v="24"t13d1517h2_8daaf6152771_cb7bf5808d99
channel dev156.0.8072.0Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/203.0.113.3 Safari/537.36"Not:A-Brand";v="8", "Chromium";v="156", "Google Chrome";v="156"t13d1517h2_8daaf6152771_cb7bf5808d99
version 152152.0.7977.82Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/203.0.113.4 Safari/537.36"Chromium";v="152", "Not?A_Brand";v="24", "Google Chrome";v="152"t13d1517h2_8daaf6152771_cb7bf5808d99

From the Node.js run on 2026-09-30. 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)
[
  {
    "variant": "channel stable",
    "catalog_version": "153.0.8010.36",
    "browser_version": "153.0.8010.36",
    "user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/203.0.113.1 Safari/537.36",
    "sec_ch_ua": "\"Google Chrome\";v=\"153\", \"Not_A Brand\";v=\"8\", \"Chromium\";v=\"153\"",
    "ja4": "t13d1517h2_8daaf6152771_cb7bf5808d99",
    "akamai_h2_hash": "52d84b11737d980aef856699f885ca86"
  },
  {
    "variant": "channel beta",
    "catalog_version": "155.0.8059.12",
    "browser_version": "155.0.8059.12",
    "user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/203.0.113.2 Safari/537.36",
    "sec_ch_ua": "\"Google Chrome\";v=\"155\", \"Chromium\";v=\"155\", \"Not(A:Brand\";v=\"24\"",
    "ja4": "t13d1517h2_8daaf6152771_cb7bf5808d99",
    "akamai_h2_hash": "52d84b11737d980aef856699f885ca86"
  },
  {
    "variant": "channel dev",
    "catalog_version": "156.0.8072.0",
    "browser_version": "156.0.8072.0",
    "user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/203.0.113.3 Safari/537.36",
    "sec_ch_ua": "\"Not:A-Brand\";v=\"8\", \"Chromium\";v=\"156\", \"Google Chrome\";v=\"156\"",
    "ja4": "t13d1517h2_8daaf6152771_cb7bf5808d99",
    "akamai_h2_hash": "52d84b11737d980aef856699f885ca86"
  },
  {
    "variant": "version 152",
    "catalog_version": "152.0.7977.82",
    "browser_version": "152.0.7977.82",
    "user_agent": "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/203.0.113.4 Safari/537.36",
    "sec_ch_ua": "\"Chromium\";v=\"152\", \"Not?A_Brand\";v=\"24\", \"Google Chrome\";v=\"152\"",
    "ja4": "t13d1517h2_8daaf6152771_cb7bf5808d99",
    "akamai_h2_hash": "52d84b11737d980aef856699f885ca86"
  }
]

Takeaways