{"id":15151841,"url":"https://github.com/regseb/playwright-ghost","last_synced_at":"2025-10-24T08:31:14.931Z","repository":{"id":252054406,"uuid":"484176009","full_name":"regseb/playwright-ghost","owner":"regseb","description":"Playwright overlay with plugins.","archived":false,"fork":false,"pushed_at":"2024-10-30T20:43:23.000Z","size":1746,"stargazers_count":3,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-01-31T00:32:54.422Z","etag":null,"topics":["ghost","headless","playwright","polyfill"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/regseb.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":".github/CODE_OF_CONDUCT.md","threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":".github/SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"buy_me_a_coffee":"regseb","custom":"https://www.paypal.me/sebastienregne"}},"created_at":"2022-04-21T19:15:01.000Z","updated_at":"2024-11-02T18:43:52.000Z","dependencies_parsed_at":"2024-08-07T13:03:08.728Z","dependency_job_id":"222f420e-cafa-44bc-a29e-20a2a9a4f726","html_url":"https://github.com/regseb/playwright-ghost","commit_stats":null,"previous_names":["regseb/playwright-ghost"],"tags_count":21,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/regseb%2Fplaywright-ghost","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/regseb%2Fplaywright-ghost/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/regseb%2Fplaywright-ghost/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/regseb%2Fplaywright-ghost/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/regseb","download_url":"https://codeload.github.com/regseb/playwright-ghost/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":237937777,"owners_count":19390543,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ghost","headless","playwright","polyfill"],"created_at":"2024-09-26T15:22:30.899Z","updated_at":"2025-10-24T08:31:14.925Z","avatar_url":"https://github.com/regseb.png","language":"JavaScript","funding_links":["https://buymeacoffee.com/regseb","https://www.paypal.me/sebastienregne"],"categories":[],"sub_categories":[],"readme":"# Playwright-ghost\n\n\u003c!-- Utiliser du HTML (avec l'attribut \"align\" obsolète) pour faire flotter\n     l'image à droite. --\u003e\n\u003c!-- markdownlint-disable-next-line no-inline-html--\u003e\n\u003cimg src=\"asset/logo.svg\" align=\"right\" width=\"100\" height=\"100\" alt=\"\"\u003e\n\n[![npm][img-npm]][link-npm] [![build][img-build]][link-build]\n[![coverage][img-coverage]][link-coverage] [![semver][img-semver]][link-semver]\n\nPlaywright-ghost is an overlay on [Playwright](https://playwright.dev/), adding\nplugins to conceal the differences between a browser used by a human being and a\n[headless browser](https://en.wikipedia.org/wiki/Headless_browser) controlled by\na program.\n\nThe Playwright-ghost API is identical to that of Playwright, except for the\naddition of the `plugins` option to the\n[`BrowserType.launch([options])`](https://playwright.dev/docs/api/class-browsertype#browser-type-launch)\nand\n[`BrowserType.launchPersistentContext(userDataDir, [options])`](https://playwright.dev/docs/api/class-browsertype#browser-type-launch-persistent-context)\nmethods.\n\nThe `plugins` property is an array containing the plugins to be added.\n\n## Disclaimer\n\nThis project is not officially commissioned or supported by Microsoft and\nPlaywright.\n\n## Install\n\n[`playwright-ghost`](https://www.npmjs.com/package/playwright-ghost) doesn't\nprovide [`playwright`](https://www.npmjs.com/package/playwright), so you need to\nadd it to your dependencies.\n\n```shell\nnpm install playwright playwright-ghost\n```\n\n`playwright-ghost` can also be used with\n[`patchright`](https://www.npmjs.com/package/patchright) or\n[`rebrowser-playwright`](https://www.npmjs.com/package/rebrowser-playwright).\n\n```shell\nnpm install patchright playwright-ghost\nnpm install rebrowser-playwright playwright-ghost\n```\n\n## Use\n\nHere's an example with the recommended plugins.\n\n```javascript\nimport { chromium } from \"playwright-ghost\";\n// Or to use patchright or rebrowser-playwright:\n// import { chromium } from \"playwright-ghost/patchright\";\n// import { chromium } from \"playwright-ghost/rebrowser\";\nimport plugins from \"playwright-ghost/plugins\";\n\nconst browser = await chromium.launch({\n  plugins: plugins.recommended(),\n});\nconst context = await browser.newContext();\nconst page = await context.newPage();\n\nawait page.goto(\"https://example.com/\");\nconst title = await page.locator(\"h1\").textContent();\nconsole.log(title);\n\nawait context.close();\nawait browser.close();\n```\n\nIn this other example, three plugins are added:\n\n- `polyfill.headless` has no options;\n- `polyfill.screen` sets other values for screen size;\n- `utils.adblocker` uses default options.\n\n```javascript\nimport { chromium } from \"playwright-ghost\";\nimport plugins from \"playwright-ghost/plugins\";\n\nconst browser = await chromium.launch({\n  plugins: [\n    plugins.polyfill.headless(),\n    plugins.polyfill.screen({ width: 2560, height: 1440 }),\n    plugins.utils.adblocker(),\n  ],\n});\n// ...\n```\n\nAnd for this example, the recommended plugins and the `utils.locale` plugin are\nadded.\n\n```javascript\nimport { chromium } from \"playwright-ghost\";\nimport plugins from \"playwright-ghost/plugins\";\n\nconst browser = await chromium.launch({\n  plugins: [...plugins.recommended(), plugins.utils.locale()],\n});\n// ...\n```\n\n## Plugins\n\n⭐️ is in [`recommended`](docs/plugins/recommended.md) / ⚙️ has options\n\n### Polyfill\n\n\u003c!-- markdownlint-disable no-inline-html--\u003e\n\u003ctable\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e⭐️\u003c/td\u003e\n    \u003ctd\u003e⚙️\u003c/td\u003e\n    \u003cth\u003eName\u003c/th\u003e\n    \u003cth\u003eDescription\u003c/th\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e⭐️\u003c/td\u003e\n    \u003ctd\u003e\u003c/td\u003e\n    \u003ctd\u003e\n      \u003ca href=\"docs/plugins/polyfill/automation.md\"\u003e\n        \u003ccode\u003epolyfill.automation\u003c/code\u003e\n      \u003c/a\u003e\n    \u003c/td\u003e\n    \u003ctd\u003eDisable \u003ccode\u003e--enable-automation\u003c/code\u003e in Chromium.\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e⭐️\u003c/td\u003e\n    \u003ctd\u003e\u003c/td\u003e\n    \u003ctd\u003e\n      \u003ca href=\"docs/plugins/polyfill/headless.md\"\u003e\n        \u003ccode\u003epolyfill.headless\u003c/code\u003e\n      \u003c/a\u003e\n    \u003c/td\u003e\n    \u003ctd\u003e\n      Correct many differences in JavaScript APIs between the headful and\n      headless versions of Chromium.\n    \u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e⭐️\u003c/td\u003e\n    \u003ctd\u003e⚙️\u003c/td\u003e\n    \u003ctd\u003e\n      \u003ca href=\"docs/plugins/polyfill/screen.md\"\u003e\u003ccode\u003epolyfill.screen\u003c/code\u003e\u003c/a\u003e\n    \u003c/td\u003e\n    \u003ctd\u003eSet a realistic value for screen size: 1920x1080.\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e️\u003c/td\u003e\n    \u003ctd\u003e⚙️\u003c/td\u003e\n    \u003ctd\u003e\n      \u003ca href=\"docs/plugins/polyfill/useragent.md\"\u003e\n        \u003ccode\u003epolyfill.userAgent\u003c/code\u003e\n      \u003c/a\u003e\n    \u003c/td\u003e\n    \u003ctd\u003eChange the browser's user agent.\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e⭐️\u003c/td\u003e\n    \u003ctd\u003e⚙️\u003c/td\u003e\n    \u003ctd\u003e\n      \u003ca href=\"docs/plugins/polyfill/viewport.md\"\u003e\n        \u003ccode\u003epolyfill.viewport\u003c/code\u003e\n      \u003c/a\u003e\n    \u003c/td\u003e\n    \u003ctd\u003e\n      Vary viewport size with random values between 1000x500 and 1800x800.\n    \u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e⭐️\u003c/td\u003e\n    \u003ctd\u003e\u003c/td\u003e\n    \u003ctd\u003e\n      \u003ca href=\"docs/plugins/polyfill/webdriver.md\"\u003e\n        \u003ccode\u003epolyfill.webdriver\u003c/code\u003e\n      \u003c/a\u003e\n    \u003c/td\u003e\n    \u003ctd\u003eSet \u003ccode\u003enavigator.webdriver\u003c/code\u003e to \u003ccode\u003efalse\u003c/code\u003e.\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003c/td\u003e\n    \u003ctd\u003e\u003c/td\u003e\n    \u003ctd\u003e\n      \u003ca href=\"docs/plugins/polyfill/webgl.md\"\u003e\u003ccode\u003epolyfill.webGL\u003c/code\u003e\u003c/a\u003e\n    \u003c/td\u003e\n    \u003ctd\u003eModify WebGL parameter values.\u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\n### Humanize\n\n\u003c!-- markdownlint-disable no-inline-html--\u003e\n\u003ctable\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e⭐️\u003c/td\u003e\n    \u003ctd\u003e⚙️\u003c/td\u003e\n    \u003cth\u003eName\u003c/th\u003e\n    \u003cth\u003eDescription\u003c/th\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e⭐️\u003c/td\u003e\n    \u003ctd\u003e⚙️\u003c/td\u003e\n    \u003ctd\u003e\n      \u003ca href=\"docs/plugins/humanize/click.md\"\u003e\u003ccode\u003ehumanize.click\u003c/code\u003e\u003c/a\u003e\n    \u003c/td\u003e\n    \u003ctd\u003e\n      Add delay between \u003ccode\u003emousedown\u003c/code\u003e and \u003ccode\u003emouseup\u003c/code\u003e for\n      clicks and double-clicks.\n    \u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e⭐️\u003c/td\u003e\n    \u003ctd\u003e⚙️\u003c/td\u003e\n    \u003ctd\u003e\n      \u003ca href=\"docs/plugins/humanize/cursor.md\"\u003e\u003ccode\u003ehumanize.cursor\u003c/code\u003e\u003c/a\u003e\n    \u003c/td\u003e\n    \u003ctd\u003eMove the cursor with human-like movements.\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e⭐️\u003c/td\u003e\n    \u003ctd\u003e⚙️\u003c/td\u003e\n    \u003ctd\u003e\n      \u003ca href=\"docs/plugins/humanize/dialog.md\"\u003e\u003ccode\u003ehumanize.dialog\u003c/code\u003e\u003c/a\u003e\n    \u003c/td\u003e\n    \u003ctd\u003e\n      Close \u003ccode\u003e\u0026lt;dialog\u0026gt;\u003c/code\u003e within a humanly possible time (between\n      1 and 5 seconds).\n    \u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\n### Utils\n\n\u003c!-- markdownlint-disable no-inline-html--\u003e\n\u003ctable\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e⭐️\u003c/td\u003e\n    \u003ctd\u003e⚙️\u003c/td\u003e\n    \u003cth\u003eName\u003c/th\u003e\n    \u003cth\u003eDescription\u003c/th\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003c/td\u003e\n    \u003ctd\u003e️⚙️\u003c/td\u003e\n    \u003ctd\u003e\n      \u003ca href=\"docs/plugins/utils/adblocker.md\"\u003e\u003ccode\u003eutils.adblocker\u003c/code\u003e\u003c/a\u003e\n    \u003c/td\u003e\n    \u003ctd\u003eAdd Ghostery adblocker.\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003c/td\u003e\n    \u003ctd\u003e️⚙️\u003c/td\u003e\n    \u003ctd\u003e\n      \u003ca href=\"docs/plugins/utils/camoufox.md\"\u003e\u003ccode\u003eutils.camoufox\u003c/code\u003e\u003c/a\u003e\n    \u003c/td\u003e\n    \u003ctd\u003eReplace Firefox by Camoufox.\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003c/td\u003e\n    \u003ctd\u003e️⚙️\u003c/td\u003e\n    \u003ctd\u003e\n      \u003ca href=\"docs/plugins/utils/debug.md\"\u003e\u003ccode\u003eutils.debug\u003c/code\u003e\u003c/a\u003e\n    \u003c/td\u003e\n    \u003ctd\u003eAdd debugging to a page (transfer error; display cursor).\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003c/td\u003e\n    \u003ctd\u003e⚙️\u003c/td\u003e\n    \u003ctd\u003e\n      \u003ca href=\"docs/plugins/utils/fingerprint.md\"\u003e\n        \u003ccode\u003eutils.fingerprint\u003c/code\u003e\n      \u003c/a\u003e\n    \u003c/td\u003e\n    \u003ctd\u003eChange the browser fingerprint.\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003c/td\u003e\n    \u003ctd\u003e⚙️\u003c/td\u003e\n    \u003ctd\u003e\n      \u003ca href=\"docs/plugins/utils/locale.md\"\u003e\u003ccode\u003eutils.locale\u003c/code\u003e\u003c/a\u003e\n    \u003c/td\u003e\n    \u003ctd\u003eUse the locally installed browser.\u003c/td\u003e\n  \u003c/tr\u003e\n  \u003ctr\u003e\n    \u003ctd\u003e\u003c/td\u003e\n    \u003ctd\u003e⚙️\u003c/td\u003e\n    \u003ctd\u003e\n      \u003ca href=\"docs/plugins/utils/xvfb.md\"\u003e\u003ccode\u003eutils.xvfb\u003c/code\u003e\u003c/a\u003e\n    \u003c/td\u003e\n    \u003ctd\u003eRun browser in \u003ccode\u003eXvfb\u003c/code\u003e (\u003cem\u003eX Virtual Frame Buffer\u003c/em\u003e).\u003c/td\u003e\n  \u003c/tr\u003e\n\u003c/table\u003e\n\n## Anti-bots\n\n### Pass\n\nThis 20 anti-bots don't detect Playwright-ghost:\n[Anubis](https://anubis.techaro.lol),\n[Brotector](https://kaliiiiiiiiii.github.io/brotector/),\n[BrowserScan](https://www.browserscan.net/bot-detection),\n[Chromedriver Detector](https://hmaker.github.io/selenium-detector/),\n[Detect CDP](https://bypassantibot.github.io/detectCDP/),\n[Deviceandbrowserinfo](https://deviceandbrowserinfo.com/are_you_a_bot),\n[Device Info](https://www.deviceinfo.me/)\n[Disable-devtool](https://theajack.github.io/disable-devtool/),\n[Fingerprint](https://fingerprint.com/products/bot-detection/),\n[Fingerprint Pro Playground](https://demo.fingerprint.com/playground),\n[Fingerprint-Scan](https://fingerprint-scan.com/),\n[HeadlessDetectJS](https://github.com/LouisKlimek/HeadlessDetectJS),\n[infosimples](https://infosimples.github.io/detect-headless/),\n[Chrome Headless Detection (Intoli)](https://intoli.com/blog/not-possible-to-block-chrome-headless/chrome-headless-test.html),\n[Check browser fingerprints (iphey)](https://iphey.com/),\n[OverpoweredJS Fingerprinting Demo](https://overpoweredjs.com/demo.html),\n[Pixelscan](https://pixelscan.net/fingerprint-check),\n[Antibot (Sannysoft)](https://bot.sannysoft.com/),\n[Simple Service Workers Fingerprinting Leaks Test](https://mihneamanolache.github.io/simple-sw-test/)\nand\n[Cloudflare turnstile demo](https://peet.ws/turnstile-test/non-interactive.html).\n\nTo find out which plugins are used, see the\n[anti-bots integration tests](test/integration/antibots).\n\n### Fail\n\nThis 3 anti-bots detect Playwright-ghost:\n\n- [CreepJS](https://abrahamjuliot.github.io/creepjs/): _F_\n- [rebrowser-bot-detector](https://bot-detector.rebrowser.net/):\n  _mainWorldExecution_, _pwInitScripts_ and _useragent_\n- [Score detector (reCAPTCHA v3)](https://antcpt.com/score_detector/): _0.3_\n\nContributions are welcome to fix these defects.\n\n## Customize\n\nYou can write your own plugins. A plugin is a function that returns an object\ncontaining the hooks. The keys of this object are made up of the class, method\nand hook type. For example:\n\n- `\"BrowserType.launch:before\"`: modify the input arguments of the `launch()`\n  method of the `BrowserType` class.\n- `\"BrowserContext.newPage:after\"`: modify the return parameter of the\n  `newPage()` method of the `BrowserContext` class.\n\nThe values of the object are functions applying the modifications.\n\n- For `\"before\"` types, the function receives an array containing the arguments\n  of the hooked method. And it must return a new array containing the modified\n  arguments.\n- For `\"after\"` types, the function receives the return value of the hooked\n  method. And it must return the modified return value.\n\n```javascript\n/// rickrollPlugin.js\nexport default function rickrollPlugin() {\n  return {\n    \"BrowserType.launch:before\": (args) =\u003e {\n      return [\n        {\n          ...args[0],\n          args: [\"--disable-volume-adjust-sound\"],\n        },\n      ];\n    },\n\n    \"BrowserContext.newPage:after\": (page) =\u003e {\n      page.addInitScript(() =\u003e {\n        // Execute script only in main frame.\n        if (window !== top) {\n          return;\n        }\n        addEventListener(\"load\", () =\u003e {\n          const iframe = document.createElement(\"iframe\");\n          iframe.src = \"https://www.youtube-nocookie.com/embed/dQw4w9WgXcQ\";\n          document.body.replaceChildren(iframe);\n        });\n      });\n      return page;\n    },\n  };\n}\n```\n\nTo use your plugin, add it to the `plugins` option.\n\n```javascript\nimport { chromium } from \"playwright-ghost\";\nimport plugins from \"playwright-ghost/plugins\";\nimport rickrollPlugin from \"./rickrollPlugin.js\";\n\nconst browser = await chromium.launch({\n  plugins: [...plugins.recommended(), rickrollPlugin()],\n});\n// ...\n```\n\nThis plugin isn't perfect, so\n[let's see how we can improve it](docs/customize.md) (and also discover other\nfeatures).\n\n[img-npm]:\n  https://img.shields.io/npm/dm/playwright-ghost?label=npm\u0026logo=npm\u0026logoColor=whitesmoke\n[img-build]:\n  https://img.shields.io/github/actions/workflow/status/regseb/playwright-ghost/ci.yml?branch=main\u0026logo=github\u0026logoColor=whitesmoke\n[img-coverage]:\n  https://img.shields.io/endpoint?label=coverage\u0026url=https%3A%2F%2Fbadge-api.stryker-mutator.io%2Fgithub.com%2Fregseb%2Fplaywright-ghost%2Fmain\n[img-semver]:\n  https://img.shields.io/badge/semver-2.0.0-blue?logo=semver\u0026logoColor=whitesmoke\n[link-npm]: https://www.npmjs.com/package/playwright-ghost\n[link-build]:\n  https://github.com/regseb/playwright-ghost/actions/workflows/ci.yml?query=branch%3Amain\n[link-coverage]:\n  https://dashboard.stryker-mutator.io/reports/github.com/regseb/playwright-ghost/main\n[link-semver]: https://semver.org/spec/v2.0.0.html \"Semantic Versioning 2.0.0\"\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fregseb%2Fplaywright-ghost","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fregseb%2Fplaywright-ghost","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fregseb%2Fplaywright-ghost/lists"}