Migrating from puppeteer-extra-plugin-stealth
Keep Puppeteer, drop the plugin. Clearcote starts the browser with the fingerprint already set in the engine, and Puppeteer connects to it. Your page code stays the same.
The change
import puppeteer from "puppeteer-extra";
import StealthPlugin from "puppeteer-extra-plugin-stealth";
import puppeteer from "puppeteer-core";
import { serve } from "clearcote";
puppeteer.use(StealthPlugin());
const browser = await puppeteer.launch({ args: ["--proxy-server=http://proxy.example:8080"] });
const srv = await serve({
fingerprint: "acct-1", // one seed per account: the same device every run
proxy: { server: "http://proxy.example:8080", username: "user", password: "pass" },
geoip: true, // timezone, language and WebRTC follow the exit IP
});
const browser = await puppeteer.connect({ browserURL: srv.cdpUrl, defaultViewport: null });
const page = await browser.newPage();
await page.authenticate({ username: "user", password: "pass" });
await page.setUserAgent("Mozilla/5.0 ...");
await page.goto("https://example.com");
await browser.close();
await browser.disconnect();
await srv.close(); // stops the browser and removes its temporary profileInstall with npm install clearcote puppeteer-core. serve() downloads a verified build on first use and starts it with a CDP endpoint on loopback. Keep defaultViewport: null: without it, Puppeteer resizes every page to 800×600, which no longer matches the window the persona gave the browser.
Everything else
| With the plugin | With Clearcote |
|---|---|
page.emulateTimezone(), --lang | geoip: true with a proxy, or timezone and acceptLanguage |
page.setViewport() | Remove it |
evaluateOnNewDocument evasion snippets | Remove them. Patches of navigator or WebGL from a script are what pages look for |
userDataDir | userDataDir on serve() (default: a temporary profile, removed on close) |
other puppeteer-extra plugins, such as an ad blocker | Keep them: puppeteer-extra wraps connect() as it wraps launch(). Only the stealth plugin goes |
Or move to Playwright
The SDK's launch() returns a Playwright Browser directly, and adds humanized mouse, keyboard and scrolling (humanize: true), which a client connected over CDP does not get. page.goto, page.evaluate and page.screenshot stay the same; page.$(sel) becomes page.locator(sel), and Playwright waits for elements by itself.
import { launch } from "clearcote";
const browser = await launch({ fingerprint: "acct-1", humanize: true });
const page = await browser.newPage();
await page.goto("https://example.com");
await page.locator("text=More information").click(); // moves and clicks like a person
await browser.close();Check the switch
npx clearcote info prints the build the SDK resolves and a launch test. Without a licence key that is the open build (BSD-3, no account); a proxy with a username and password needs the latest build, free with a GitHub sign-in (npx clearcote login). Then open the audit in the old setup and the new one. Comparison: Clearcote vs puppeteer-extra-plugin-stealth.