Skip to content

Add job-level stealth toggle; scope StealthPlugin to Chromium#7

Merged
jrpool merged 2 commits into
jrpool:mainfrom
wittjeff:job-stealth-toggle
May 25, 2026
Merged

Add job-level stealth toggle; scope StealthPlugin to Chromium#7
jrpool merged 2 commits into
jrpool:mainfrom
wittjeff:job-stealth-toggle

Conversation

@wittjeff
Copy link
Copy Markdown
Contributor

Problem

puppeteer-extra-plugin-stealth is Chromium-specific. Its evasions patch Blink-only DOM globals and inject Chromium-only launch flags — notably --disable-blink-features=AutomationControlled.

Today, run.js registers the plugin on all three browser types unconditionally:

chromium.use(StealthPlugin());
webkit.use(StealthPlugin());
firefox.use(StealthPlugin());

playwright-extra then passes the Chromium-only arg through to WebKit and Firefox during launch, and both browsers reject unknown CLI args at startup:

[pid=141014][err] Cannot parse arguments: Unknown option --disable-blink-features=AutomationControlled
[pid=141014] <process did exit: exitCode=1, signal=null>

The browser process exits immediately, the launch raises browserType.launch: Target page, context or browser has been closed, and every test act fails the same way. This makes any job with browserID: 'webkit' or 'firefox' unrunnable — and by extension, any device emulation whose defaultBrowserType is webkit (every iPhone/iPad descriptor Playwright ships).

I hit this on a fan-out run from a project where four Chromium/Firefox configurations completed 8/8 engines and the one WebKit (iPhone 15 Pro Max) configuration reported 0/8 engines, all with the same root error in the worker logs.

Changes

  1. run.js: register StealthPlugin only on playwright-extra.chromium. WebKit and Firefox no longer have the plugin attached, so the incompatible Chromium-only args stop leaking into their launches.

  2. procs/launch.js: pick playwright-extra vs plain playwright per launch based on a new useStealth derivation. Chromium with stealth → playwright-extra (plugin fires). Chromium without stealth, or any WebKit/Firefox launch → plain playwright. Also gate --disable-blink-features=AutomationControlled on useStealth since it's a stealth-only arg (hides the automation flag that stealth's other evasions assume is hidden).

  3. procs/job.js: validate optional stealth field — must be boolean when present, omitted means "use the default" (which is true on Chromium, false everywhere else).

  4. README.md: document the new field in the sample-job block.

Backwards compatibility

  • Jobs that don't set stealth get the historical behavior on Chromium (stealth on).
  • WebKit/Firefox jobs that previously failed unconditionally now launch cleanly. Setting stealth: true on a WebKit job is silently ignored (the plugin is never registered there).
  • No API breakage. stealth is a new optional field.

Why expose stealth as a job field at all

Two reasons:

  1. Stealth's evasions are visible to careful sites — some anti-bot heuristics actually react more aggressively when they detect the patches. Integrators occasionally need to opt out for specific targets.
  2. When reproducing what a real user agent sees, the stealth patches alter the JS environment in observable ways (navigator.webdriver, navigator.plugins, etc.). Tests that care about the unmodified environment benefit from opting out.

If you'd prefer to keep stealth always-on for Chromium and only land change (1), I'm happy to split this into two PRs — but the second change is small and the validation/docs touch the same surface, so I bundled them.

Test notes

Loaded run.js and procs/launch.js standalone with node --check for syntax. End-to-end verification was via the production worker run that surfaced the bug: with the patched testaro, the WebKit (iPhone 15 Pro Max) configuration now reports 8/8 engines like the Chromium/Firefox configurations.

puppeteer-extra-plugin-stealth is Chromium-specific. Its evasions
patch Blink-only DOM globals and inject Chromium-only launch flags
(notably `--disable-blink-features=AutomationControlled`). When the
plugin is registered on WebKit or Firefox via `playwright-extra`, the
launch arg is still passed through to those browsers, which reject
unknown args at startup with:

  Cannot parse arguments: Unknown option --disable-blink-features=AutomationControlled

This makes every WebKit/Firefox scan fail unconditionally on
Testaro 70.2.0 — including any job with `browserID: 'webkit'` or any
device whose `defaultBrowserType` is webkit (e.g. all iPhone presets
from Playwright's device descriptors).

Two changes:

1. Register StealthPlugin only on `playwright-extra.chromium`. WebKit
   and Firefox now always use plain `playwright` and launch cleanly.

2. Add an optional job-level `stealth` field (boolean, default true).
   When true, Chromium launches go through `playwright-extra` so the
   stealth evasions fire — same behavior as before for Chromium. When
   false, Chromium uses plain `playwright` with no evasions and no
   `--disable-blink-features=AutomationControlled` arg, giving
   integrators a way to opt out for sites whose anti-bot heuristics
   react badly to stealth's patches.

`procs/job.js` validates `stealth` is a boolean when present. README
documents the new field in the sample job block.

Backwards-compatible: jobs that don't set `stealth` get the historical
Chromium-with-stealth behavior. WebKit/Firefox jobs that used to fail
silently (or noisily) now succeed.
Copy link
Copy Markdown
Owner

@jrpool jrpool left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you very much for discovering this bug, which is evidence for the recently weak validation hygiene of this repository.

@jrpool jrpool merged commit bfde22d into jrpool:main May 25, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants