---
slug: "playwright-bot-bypass"
source_type: "readme"
source_url: "https://cdn.jsdelivr.net/gh/greekr4/playwright-bot-bypass@main/README.md"
repo: "https://github.com/greekr4/playwright-bot-bypass"
source_file: "README.md"
branch: "main"
---
# playwright-bot-bypass v2.2

[![Claude Code plugin](https://img.shields.io/badge/Claude%20Code-plugin-d97757)](https://github.com/greekr4/playwright-bot-bypass)
[![version](https://img.shields.io/badge/version-2.2.0-blue)](https://github.com/greekr4/playwright-bot-bypass/releases)
[![License: MIT](https://img.shields.io/badge/License-MIT-green)](LICENSE)
[![detectors](https://img.shields.io/badge/bot%20detectors-8%2F8%20pass-brightgreen)](skills/playwright-bot-bypass/SKILL.md)

> A **Claude Code plugin** (bundling a single skill) that wires existing stealth tools — [`rebrowser-playwright`](https://github.com/rebrowser/rebrowser-patches), real headed Chrome, and [`undetected-chromedriver`](https://github.com/ultrafunkamsterdam/undetected-chromedriver) — into a single, agent-ready bundle.

> **Authorized use only.** For QA, accessibility testing, and research on sites you own or are permitted to test. Respect each site's Terms of Service, `robots.txt`, and applicable law.

## What this skill is (and isn't)

**It is** a *packaging + integration* layer. The hard stealth work lives in upstream projects (see [Credits](#credits)). What this skill adds:

- 🧩 **A one-import factory** — `createStealthBrowser()` returns a ready `{ browser, page }` so an agent doesn't have to rediscover the right combination of `channel:'chrome'` + headed + rebrowser env var + init script.
- 🎯 **A verified recipe** — *which* knobs to set and, just as important, **which fakes NOT to add**. Measured against 8 detectors: the artifact-strip + Runtime-fix pass; the old hand-rolled `navigator` fakes actively *hurt* (see [How It Works](#how-it-works-3-layers)).
- 📊 **Reproducible proof** — A/B screenshots + a measured detection-coverage matrix, with honest limits.
- 🤖 **Agent docs** — [`SKILL.md`](https://github.com/greekr4/playwright-bot-bypass/blob/HEAD/skills/playwright-bot-bypass/SKILL.md) so Claude Code can use and reason about it directly.

**It is NOT** a new stealth engine. It does not author rebrowser-playwright, undetected-chromedriver, or the underlying detection research — it stitches them together and tunes the result.

## Installation

### As a Claude Code plugin (recommended)

```
/plugin marketplace add greekr4/playwright-bot-bypass
/plugin install playwright-bot-bypass@playwright-bot-bypass
```

The first command registers this repo as a marketplace; the second installs the plugin (the `playwright-bot-bypass` skill is auto-discovered). Update later with `/plugin marketplace update playwright-bot-bypass`.

### As a standalone skill

```
npx skills add greekr4/playwright-bot-bypass
```

## What you get

- ✅ Passes fingerprint + automation-framework detectors (sannysoft, rebrowser-detector, deviceandbrowserinfo, browserscan, iphey, creepjs) — measured, 9/9 reproducible
- ✅ Google search without triggering CAPTCHA; public Reddit/YouTube/TikTok/X loads with no bot challenge
- ✅ Real GPU fingerprint, real Chrome UA, genuine PluginArray — from real headed Chrome, **not** faked in JS
- ✅ One-import factory + human-like helpers (mouse movement, typing delays), cookie persistence, proxy support
- ✅ Node.js (`rebrowser-playwright`) and Python (`undetected-chromedriver`) paths

## A/B: plain Playwright vs this skill

Measured **2026-06-10** (macOS, Apple Silicon). **A** = default `playwright` (headless bundled Chromium, no stealth). **B** = this skill via `createStealthBrowser()` (headed real Chrome + rebrowser Runtime-fix + Playwright-artifact strip). Same three detectors, same machine.

**deviceandbrowserinfo.com — "Are you a bot?"**

| A · plain Playwright | B · this skill |
|:---:|:---:|
| ![bot](https://github.com/greekr4/playwright-bot-bypass/raw/HEAD/img/ab-dabi-before.png) | ![human](https://github.com/greekr4/playwright-bot-bypass/raw/HEAD/img/ab-dabi-after.png) |
| ❌ **"You are a bot!"** (`isBot: true`) | ✅ **"You are human!"** (`isBot: false`) |

**bot-detector.rebrowser.net — CDP/automation tests** (rebrowser's own detector)

| A · plain Playwright | B · this skill |
|:---:|:---:|
| ![reds](https://github.com/greekr4/playwright-bot-bypass/raw/HEAD/img/ab-rebrowser-before.png) | ![green](https://github.com/greekr4/playwright-bot-bypass/raw/HEAD/img/ab-rebrowser-after.png) |
| 🔴 `navigatorWebdriver` (`= true`) | 🟢 all green — `webdriver false`, no `__pwInitScripts`, no Runtime leak |

**bot.sannysoft.com — fingerprint suite**

| A · plain Playwright | B · this skill |
|:---:|:---:|
| ![detected](https://github.com/greekr4/playwright-bot-bypass/raw/HEAD/img/ab-sannysoft-before.png) | ![passed](https://github.com/greekr4/playwright-bot-bypass/raw/HEAD/img/ab-sannysoft-after.png) |
| Red rows: WebDriver / Chrome / UA = HeadlessChrome / WebGL SwiftShader | All green — real Chrome UA, Apple M2 WebGL, `webdriver false` |

> **Honest scope:** these are *fingerprint + automation-framework* detectors, and B passes them cleanly and repeatably (9/9 runs). This does **not** mean every site is bypassed — IP reputation, behavioral analysis, and login walls (Instagram/Facebook/LinkedIn) are untouched. See **[SKILL.md → Detection Coverage](https://github.com/greekr4/playwright-bot-bypass/blob/HEAD/skills/playwright-bot-bypass/SKILL.md)** for the full measured matrix and the one residual leak (`__playwright_builtins__`) that no current rebrowser version can strip.

## How It Works (3 layers)

The skill's job is to pick the right layers and let them do the work — most evasion is the real browser and the upstream library, not hand-written JS. The skill's own contribution is the **integration**: the artifact-strip init script, auto-setting the rebrowser env var, and *removing* the fakes that backfire.

| Layer | Handles |
|-------|---------|
| **rebrowser-playwright + Runtime-fix** (`REBROWSER_PATCHES_RUNTIME_FIX_MODE`, auto-set) | CDP `Runtime.enable` headless leak; reports `webdriver: false` |
| **`channel:'chrome'` + headed** | real UA, WebGL/GPU, canvas fingerprint, PluginArray, hardware/permission consistency, native `navigator.languages` via `locale` |
| **artifact strip** (one init script) | deletes `window.__pwInitScripts` / `__playwright*` (the `isPlaywright` signature) every navigation |

v2.2 touches **nothing** on `navigator`. **Removed across v2.1/v2.2** (each faked values inconsistently → net-negative): fake `navigator.plugins`, canvas noise, hardcoded `hardwareConcurrency`/`deviceMemory`, `outerWidth/Height` offset, the permissions override (`Illegal invocation` crash), the `webdriver` delete (made it `undefined` — a tell), and the `navigator.languages` getter (own-property + worker-mismatch tells).

Launch arg: `--disable-blink-features=AutomationControlled`. `--no-sandbox` is **opt-in** (`{ noSandbox: true }`) — a security risk and a bot signal, off by default. Real Chrome via `channel: 'chrome'`.

### Real community sites (single logged-out load, residential IP)

| Site | Result |
|------|--------|
| Reddit, YouTube, Pinterest, Threads, X, Quora, TikTok | 🟢 content fully loaded — no bot challenge / CAPTCHA |
| Instagram, Facebook, LinkedIn | 🟡 content behind the **normal logged-out login modal** — not a bot block / checkpoint / 999 |

None returned a bot challenge — even IG/FB/LinkedIn served the standard human logged-out experience. **Caveat:** one load each on a clean residential IP; at volume those three enforce `datr`/999/rate-limits (IP & account problems this skill does not solve).

## Quick Start

### Node.js (Recommended)

```bash
npm init -y && npm install rebrowser-playwright
```

#### Using the template (recommended)

```javascript
import { createStealthBrowser, humanDelay, humanType, simulateMouseMovement } from './scripts/stealth-template.mjs';

const { browser, page } = await createStealthBrowser();

try {
  await page.goto('https://example.com');
  await simulateMouseMovement(page);  // Natural mouse movement
  await humanType(page, 'input', 'query');  // Human-like typing
  await humanDelay(300, 800);
} finally {
  await browser.close();
}
```

#### Template options

```javascript
createStealthBrowser({
  headless: false,              // Required for stealth (default)
  viewport: { width: 1280, height: 800 },
  locale: 'ko-KR',             // Browser locale (sets navigator.languages + Accept-Language)
  storageState: './session.json',  // Cookie persistence
  proxy: { server: 'http://proxy:8080' },  // Proxy support
  noSandbox: false             // Opt-in --no-sandbox (Linux root/CI only)
});
```

#### Manual setup

```javascript
// Runtime-fix must be set before importing rebrowser-playwright.
process.env.REBROWSER_PATCHES_RUNTIME_FIX_MODE ??= 'addBinding';
const { chromium } = await import('rebrowser-playwright');

const browser = await chromium.launch({
  headless: false,
  channel: 'chrome',
  args: ['--disable-blink-features=AutomationControlled']  // --no-sandbox is opt-in; it's a bot signal
});

const context = await browser.newContext({ locale: 'ko-KR' }); // sets languages + Accept-Language natively

await context.addInitScript(() => {
  // strip Playwright's main-world signature; touch nothing on navigator
  for (const k of Object.getOwnPropertyNames(window)) {
    if (/^__pw|pwInitScripts|playwright/i.test(k)) { try { delete window[k]; } catch {} }
  }
});

const page = await context.newPage();
try {
  await page.goto('https://google.com');
} finally {
  await browser.close();
}
```

### Python

```bash
pip install undetected-chromedriver
```

```python
import undetected_chromedriver as uc

driver = uc.Chrome()  # auto-detects Chrome version
driver.get('https://google.com')
```

> Python `playwright-stealth` only patches at JS level — WebGL still shows SwiftShader. Use `undetected-chromedriver` instead.

## Test Results

| Environment | bot.sannysoft.com | Google Search | bluer.co.kr |
|-------------|-------------------|---------------|-------------|
| Standard Playwright | Detected | CAPTCHA | **403** |
| **This skill** (rebrowser + headed Chrome) | **Pass** | **Works** | **200** |
| playwright-stealth (Python) | Pass | CAPTCHA | - |
| **undetected-chromedriver** (Python path) | **Pass** | **Works** | - |

## Scripts Included

```
skills/playwright-bot-bypass/
  scripts/
    stealth-template.mjs      # Reusable stealth factory (all examples import this)
    bot-detection-test.mjs     # Verify bypass at bot.sannysoft.com
  examples/
    stealth-google-search.mjs  # Google search without CAPTCHA
    ab-test.mjs                # Side-by-side detected vs stealth
    stealth-twitter-scrape.mjs # Twitter/X profile scraping
  package.json                 # Dependencies (type: module)
  marketplace.json
  SKILL.md                     # Full documentation for Claude Code agents
```

## Requirements

- Node.js 18+ (ESM / `.mjs`)
- Google Chrome installed (not just Chromium)
- Headed mode (display required — `headless: false`)

## Troubleshooting

| Problem | Fix |
|---------|-----|
| `ERR_MODULE_NOT_FOUND` | Run `npm install rebrowser-playwright` in your script directory |
| Browser not opening | Verify Chrome: `/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome --version` |
| WebGL shows SwiftShader | You're effectively headless / GPU-less. Run **headed** (`headless: false`) with `channel: 'chrome'` on a machine with a real GPU — SwiftShader is the software fallback, not an import issue |
| Still getting detected | Add `simulateMouseMovement()` and `humanDelay()` between actions |
| Process hangs | Ensure `browser.close()` is in a `finally` block |

## Credits

This skill is **glue + tuning** on top of other people's work. All the heavy lifting belongs to:

- **[rebrowser-playwright / rebrowser-patches](https://github.com/rebrowser/rebrowser-patches)** — the patched Playwright that hides the CDP `Runtime.enable` leak and reports `webdriver: false`. The core of the Node.js path.
- **[undetected-chromedriver](https://github.com/ultrafunkamsterdam/undetected-chromedriver)** (ultrafunkamsterdam) — the Python path.
- **Real Google Chrome** via Playwright's `channel: 'chrome'` — provides the genuine UA / WebGL / canvas / PluginArray that no JS fake can match.
- **Detection research & test pages** used to verify this skill: [rebrowser-bot-detector](https://github.com/rebrowser/rebrowser-bot-detector), [bot.sannysoft.com](https://bot.sannysoft.com), [deviceandbrowserinfo.com](https://deviceandbrowserinfo.com), [browserscan.net](https://www.browserscan.net), [CreepJS](https://abrahamjuliot.github.io/creepjs/), and Antoine Vastel's bot pages.

What's original here is the **integration**: the `createStealthBrowser()` factory, the verified v2.2 recipe (the `__pwInitScripts` strip + auto Runtime-fix, and the decision to *remove* counter-productive `navigator` fakes), the measured A/B + coverage matrix, and the agent-facing `SKILL.md`.

## License

MIT
