Playwright with plugins to be a ghost.
npm install playwright-ghost[![npm][img-npm]][link-npm] [![build][img-build]][link-build]
[![coverage][img-coverage]][link-coverage] [![semver][img-semver]][link-semver]
Playwright-ghost is an overlay on Playwright, adding
plugins to conceal the differences between a browser used by a human being and a
headless browser controlled by
a program.
The Playwright-ghost API is identical to that of Playwright, except for the
addition of the plugins option to the
[BrowserType.launch([options])](https://playwright.dev/docs/api/class-browsertype#browser-type-launch)
and
[BrowserType.launchPersistentContext(userDataDir, [options])](https://playwright.dev/docs/api/class-browsertype#browser-type-launch-persistent-context)
methods.
The plugins property is an array containing the plugins to be added.
This project is not officially commissioned or supported by Microsoft and
Playwright.
playwright-ghost doesn't
provide playwright, so you need to
add it to your dependencies.
``shell`
npm install playwright playwright-ghost
playwright-ghost can also be used withpatchright orrebrowser-playwright.
`shell`
npm install patchright playwright-ghost
npm install rebrowser-playwright playwright-ghost
Here's an example with the recommended plugins.
`javascript
import { chromium } from "playwright-ghost";
// Or to use patchright or rebrowser-playwright:
// import { chromium } from "playwright-ghost/patchright";
// import { chromium } from "playwright-ghost/rebrowser";
import plugins from "playwright-ghost/plugins";
const browser = await chromium.launch({
plugins: plugins.recommended(),
});
const context = await browser.newContext();
const page = await context.newPage();
await page.goto("https://example.com/");
const title = await page.locator("h1").textContent();
console.log(title);
await context.close();
await browser.close();
`
In this other example, three plugins are added:
- polyfill.headless has no options;polyfill.screen
- sets other values for screen size;utils.adblocker
- uses default options.
`javascript
import { chromium } from "playwright-ghost";
import plugins from "playwright-ghost/plugins";
const browser = await chromium.launch({
plugins: [
plugins.polyfill.headless(),
plugins.polyfill.screen({ width: 2560, height: 1440 }),
plugins.utils.adblocker(),
],
});
// ...
`
And for this example, the recommended plugins and the utils.locale plugin are
added.
`javascript
import { chromium } from "playwright-ghost";
import plugins from "playwright-ghost/plugins";
const browser = await chromium.launch({
plugins: [...plugins.recommended(), plugins.utils.locale()],
});
// ...
`
⭐️ is in recommended / ⚙️ has options
| ⭐️ | ⚙️ | Name | Description |
|---|---|---|---|
| ⭐️ | polyfill.automation | Disable --enable-automation in Chromium. | |
| ⭐️ | polyfill.headless | Correct many differences in JavaScript APIs between the headful and headless versions of Chromium. | |
| ⭐️ | ⚙️ | polyfill.screen | Set a realistic value for screen size: 1920x1080. |
| ️ | ⚙️ | polyfill.userAgent | Change the browser's user agent. |
| ⭐️ | ⚙️ | polyfill.viewport | Vary viewport size with random values between 1000x500 and 1800x800. |
| ⭐️ | polyfill.webdriver | Set navigator.webdriver to false. | |
polyfill.webGL | Modify WebGL parameter values. |
| ⭐️ | ⚙️ | Name | Description |
|---|---|---|---|
| ⭐️ | ⚙️ | humanize.click | Add delay between mousedown and mouseup forclicks and double-clicks. |
| ⭐️ | ⚙️ | humanize.cursor | Move the cursor with human-like movements. |
| ⭐️ | ⚙️ | humanize.dialog | Close <dialog> within a humanly possible time (between1 and 5 seconds). |
| ⭐️ | ⚙️ | Name | Description |
|---|---|---|---|
| ️⚙️ | utils.adblocker | Add Ghostery adblocker. | |
| ️⚙️ | utils.camoufox | Replace Firefox by Camoufox. | |
| ⚙️ | utils.fingerprint | Change the browser fingerprint. | |
| ⚙️ | utils.locale | Use the locally installed browser. | |
| ⚙️ | utils.weston | Run browser in weston (a Wayland compositor). | |
| ⚙️ | utils.xvfb | Run browser in Xvfb (X Virtual Frame Buffer). |
| ⭐️ | ⚙️ | Name | Description |
|---|---|---|---|
| ️⚙️ | debug.console | Display console messages and error from the browser in the program console. | |
debug.cursor | Show cursor in page. | ||
debug.sniffer | Monitor all JavaScript properties used in a page. |
This 24 anti-bots don't detect Playwright-ghost:
Anubis,
Bing,
bounty-nodejs,
Brotector,
BrowserScan,
Chromedriver Detector,
Detect CDP,
detectIncognito,
Deviceandbrowserinfo,
Device Info
Disable-devtool,
Fingerprint,
Fingerprint Pro Playground,
Fingerprint-Scan,
HeadlessDetectJS,
infosimples,
Chrome Headless Detection (Intoli),
Check browser fingerprints (iphey),
OverpoweredJS Fingerprinting Demo,
Pixelscan,
rebrowser-bot-detector,
Antibot (Sannysoft),
Simple Service Workers Fingerprinting Leaks Test
and
Cloudflare turnstile demo.
To find out which plugins are used, see the
anti-bots integration tests.
This 2 anti-bots detect Playwright-ghost:
- CreepJS: _44% like headless_
- Score detector (reCAPTCHA v3): _0.3_
Contributions are welcome to fix these defects.
You can write your own plugins. A plugin is a function that returns an object
containing the hooks. The keys of this object are made up of the class, method
and hook type. For example:
- "BrowserType.launch:before": modify the input arguments of the launch()BrowserType
method of the class."BrowserContext.newPage:after"
- : modify the return parameter of thenewPage()
method of the BrowserContext class.
The values of the object are functions applying the modifications.
- For "before" types, the function receives an array containing the arguments"after"
of the hooked method. And it must return a new array containing the modified
arguments.
- For types, the function receives the return value of the hooked
method. And it must return the modified return value.
`javascript
/// rickrollPlugin.js
export default function rickrollPlugin() {
return {
"BrowserType.launch:before": (args) => {
return [
{
...args[0],
args: ["--disable-volume-adjust-sound"],
},
];
},
"BrowserContext.newPage:after": (page) => {
page.addInitScript(() => {
// Execute script only in main frame.
if (window !== top) {
return;
}
addEventListener("load", () => {
const iframe = document.createElement("iframe");
iframe.src = "https://www.youtube-nocookie.com/embed/dQw4w9WgXcQ";
document.body.replaceChildren(iframe);
});
});
return page;
},
};
}
`
To use your plugin, add it to the plugins option.
`javascript
import { chromium } from "playwright-ghost";
import plugins from "playwright-ghost/plugins";
import rickrollPlugin from "./rickrollPlugin.js";
const browser = await chromium.launch({
plugins: [...plugins.recommended(), rickrollPlugin()],
});
// ...
``
This plugin isn't perfect, so
let's see how we can improve it (and also discover other
features).
[img-npm]:
https://img.shields.io/npm/dm/playwright-ghost?label=npm&logo=npm&logoColor=whitesmoke
[img-build]:
https://img.shields.io/github/actions/workflow/status/regseb/playwright-ghost/ci.yml?branch=main&logo=github&logoColor=whitesmoke
[img-coverage]:
https://img.shields.io/endpoint?label=coverage&url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.com%2Fregseb%2Fplaywright-ghost%2Fmain
[img-semver]:
https://img.shields.io/badge/semver-2.0.0-blue?logo=semver&logoColor=whitesmoke
[link-npm]: https://www.npmjs.com/package/playwright-ghost
[link-build]:
https://github.com/regseb/playwright-ghost/actions/workflows/ci.yml?query=branch%3Amain
[link-coverage]:
https://dashboard.stryker-mutator.io/reports/github.com/regseb/playwright-ghost/main
[link-semver]: https://semver.org/spec/v2.0.0.html "Semantic Versioning 2.0.0"