# How the Playwright Browser Probe Works Across Chrome, Edge, and Chromium Without Node.js

> Discover how the Playwright browser probe works with Chrome Edge and Chromium using pure Python without Nodejs. Explore the handsomestWei patent-disclosure-skill repository for a seamless automation experience.

- Repository: [handsomestWei/patent-disclosure-skill](https://github.com/handsomestWei/patent-disclosure-skill)
- Tags: internals
- Published: 2026-09-02

---

**The patent-disclosure-skill repository provides a pure-Python Playwright probe that auto-detects and launches system Chrome, system Edge, or bundled Chromium using only `playwright.sync_api`—no Node.js runtime required.**

This article examines the implementation in `handsomestWei/patent-disclosure-skill`, specifically how [`tools/shared/browser.py`](https://github.com/handsomestWei/patent-disclosure-skill/blob/main/tools/shared/browser.py) implements a self-contained browser discovery mechanism. The probe eliminates the traditional Node.js dependency by leveraging Playwright's Python bindings and embedded driver binaries, making it ideal for lightweight Python environments.

## Environment-Driven Configuration

The probe respects two environment variables that control its behavior without code changes.

### `PLAYWRIGHT_HEADED` — Display Mode Control

The `headed()` function parses this flag to determine whether browsers launch in headed or headless mode.

```python
import os

def headed() -> bool:
    return os.environ.get("PLAYWRIGHT_HEADED", "").lower() in ("1", "true", "yes", "on")

```

Source: [`tools/shared/browser.py`](https://github.com/handsomestWei/patent-disclosure-skill/blob/main/tools/shared/browser.py), lines 35-39

### `PATENT_BROWSER_CHANNEL` — Force a Specific Browser

The `_forced_channel()` function reads this optional override, allowing users to bypass auto-detection entirely.

```python
def _forced_channel() -> Optional[str]:
    ch = os.environ.get("PATENT_BROWSER_CHANNEL", "").strip().lower()
    if ch in ("chrome", "msedge", "chromium"):
        return ch
    return None

```

Source: [`tools/shared/browser.py`](https://github.com/handsomestWei/patent-disclosure-skill/blob/main/tools/shared/browser.py), lines 61-64

## Auto-Detection Priority Order

When no channel is forced, the probe follows a fixed fallback sequence defined in `_AUTO_CHANNELS`:

```python
_AUTO_CHANNELS: Tuple[str, ...] = ("chrome", "msedge")  # None = bundled Chromium

```

This tuple drives the loop in `launch_chromium()`, which attempts each candidate in order:

1. **"chrome"** — System-installed Google Chrome
2. **"msedge"** — System-installed Microsoft Edge
3. **`None`** — Playwright's bundled Chromium (downloaded via `playwright install chromium`)

Source: [`tools/shared/browser.py`](https://github.com/handsomestWei/patent-disclosure-skill/blob/main/tools/shared/browser.py), lines 31-34

## Installation Verification Before Launch

The probe validates that Playwright's Python package is importable before attempting any browser launches. The `playwright_installed()` function guards against missing dependencies with a clean boolean check.

```python
def playwright_installed() -> bool:
    try:
        import playwright
        return True
    except ImportError:
        return False

```

Source: [`tools/shared/browser.py`](https://github.com/handsomestWei/patent-disclosure-skill/blob/main/tools/shared/browser.py), lines 43-49

## Launch Mechanics: How Node.js Is Bypassed

The core launch logic resides in `launch_chromium()`. For each candidate channel, the function constructs arguments from `LAUNCH_ARGS` and calls `playwright.chromium.launch()` with the `channel` parameter when applicable.

### Launch Arguments Configuration

```python
LAUNCH_ARGS = [
    "--disable-blink-features=AutomationControlled",
    "--no-sandbox",           # Required for Docker/root environments

    "--disable-setuid-sandbox",
]

```

These flags disable automation detection (reducing bot-blocking) and relax sandboxing for portability.

### The Launch Loop

```python
def launch_chromium(channel: Optional[str] = None):
    from playwright.sync_api import sync_playwright
    
    with sync_playwright() as p:
        browser = p.chromium.launch(
            headless=not headed(),
            args=LAUNCH_ARGS,
            channel=channel,  # None → bundled Chromium; "chrome"/"msedge" → system install

        )
        # ... success handling

```

The `channel` parameter is the critical mechanism: when set to `"chrome"` or `"msedge"`, Playwright locates and uses the system browser executable; when `None`, it falls back to its own Chromium binary.

Source: [`tools/shared/browser.py`](https://github.com/handsomestWei/patent-disclosure-skill/blob/main/tools/shared/browser.py), lines 94-106

## Success and Failure Handling

### Success Path

On first successful launch, the browser closes immediately and returns:

```python
browser.close()
return browser, label  # label ∈ {"chrome", "msedge", "chromium"}

```

Source: [`tools/shared/browser.py`](https://github.com/handsomestWei/patent-disclosure-skill/blob/main/tools/shared/browser.py), lines 99-104

### Failure Path with Installation Hints

If all candidates fail, `install_chromium_hint()` generates platform-specific guidance:

```python
def install_chromium_hint() -> str:
    return "Playwright Chromium not found. Run: playwright install chromium"

```

The probe raises `RuntimeError` with this hint, directing users to resolve the issue without manual troubleshooting.

Source: [`tools/shared/browser.py`](https://github.com/handsomestWei/patent-disclosure-skill/blob/main/tools/shared/browser.py), lines 108-110 and `install_chromium_hint()` implementation

## JSON Report Structure

The `probe_launch()` wrapper returns a machine-parseable dictionary suitable for CI/CD pipelines and health checks:

| Key | Type | Description |
|-----|------|-------------|
| `playwright` | `bool` | Whether the Python package is importable |
| `channel` | `str \| None` | Which browser channel was detected/specified |
| `ok` | `bool` | Whether launch succeeded |
| `error` | `str \| None` | Exception message if failed |
| `hint` | `str \| None` | Installation or remediation guidance |

Source: [`tools/shared/browser.py`](https://github.com/handsomestWei/patent-disclosure-skill/blob/main/tools/shared/browser.py), lines 124-140

## Command-Line and Programmatic Usage

### CLI Probe Mode

```bash

# Quick system check — exits 0 on success

python -m tools.shared.browser --probe

```

**stderr output (human-readable):**

```

PROBE: ok=true channel=chrome error=

```

**stdout output (JSON):**

```json
{
  "playwright": true,
  "channel": "chrome",
  "ok": true,
  "error": null,
  "hint": null
}

```

Source: [`tools/shared/browser.py`](https://github.com/handsomestWei/patent-disclosure-skill/blob/main/tools/shared/browser.py), lines 168-176 (`main()` function)

### Programmatic Integration

```python
from tools.shared.browser import probe_launch

result = probe_launch()
if result["ok"]:
    print(f"Ready: {result['channel']}")
else:
    print(f"Failed: {result['error']}")
    print(f"Fix: {result['hint']}")

```

### Force Specific Channel

```bash
export PATENT_BROWSER_CHANNEL=msedge
python -m tools.shared.browser --probe

```

Valid values: `chrome`, `msedge`, `chromium`

## Why No Node.js Is Required

Traditional Playwright setups require Node.js for the driver process. This probe avoids that dependency through three design choices:

1. **Python-native bindings** — Uses `playwright.sync_api` exclusively
2. **Embedded driver binaries** — The `playwright` PyPI package includes pre-compiled browser drivers
3. **Direct executable discovery** — System Chrome/Edge paths are resolved via Playwright's built-in locators, not Node.js path resolution

The bundled Chromium fallback works because `playwright install chromium` downloads a **standalone browser build** that communicates via the Python binding's WebSocket protocol—no separate Node.js runtime orchestrates the process.

## Summary

- The probe in [`tools/shared/browser.py`](https://github.com/handsomestWei/patent-disclosure-skill/blob/main/tools/shared/browser.py) implements **pure-Python browser discovery** using Playwright's sync API
- **Auto-detection order**: system Chrome → system Edge → bundled Chromium, controlled by `_AUTO_CHANNELS`
- **Environment variables** `PLAYWRIGHT_HEADED` and `PATENT_BROWSER_CHANNEL` configure behavior without code changes
- **Launch arguments** in `LAUNCH_ARGS` disable automation flags and sandboxing for reliability
- **Node.js elimination** relies on Python bindings plus embedded driver binaries, not external runtime
- **JSON output** from `probe_launch()` enables programmatic health checks and CI integration

## Frequently Asked Questions

### Does this probe work in Docker containers without a display?

Yes. By default, `headed()` returns `False` unless `PLAYWRIGHT_HEADED` is explicitly set, so browsers launch in **headless mode**. The `--no-sandbox` flag in `LAUNCH_ARGS` handles root-privileged containers where standard Chromium sandboxing fails.

### What happens if I have both Chrome and Edge installed?

The probe selects **Chrome first** based on `_AUTO_CHANNELS = ("chrome", "msedge")`. To override, set `PATENT_BROWSER_CHANNEL=msedge` before running.

### How do I install the bundled Chromium if no system browser exists?

Run `playwright install chromium` in your environment. The probe's `install_chromium_hint()` generates this exact command in its error output when detection fails.

### Can I extend this to support Firefox or WebKit?

The current implementation is Chromium-specific. Playwright's Python bindings do support Firefox and WebKit via `playwright.firefox` and `playwright.webkit`, but [`browser.py`](https://github.com/handsomestWei/patent-disclosure-skill/blob/main/browser.py) would require modifications to `_AUTO_CHANNELS` and `launch_chromium()` to iterate over additional browser types.