We're open sourcing something we built internally: Rustwright: Playwright on an in-process Rust CDP engine. It’s a full rewrite of Playwright in Rust using CDP, which consumes 70% less memory than Playwright’s node driver, and is 2.55x faster to boot.
We run a lot of browser automations at Skyvern, and while we were doing some performance tuning, we realized the playwright driver was the source of a lot of wasted memory and CPU cycles. We built Rustwright to solve that problem, and instead of making it an internal library, we thought this might be valuable to the open source community
The core idea fits in two lines:
playwright-python:
your code ──pipe──► Node driver ──CDP──► Chrome
rustwright:
your code ─────── raw CDP ────────► Chrome
The way playwright-python works is that it pipes every call through a bundled Node driver process. This brings its own added memory / cpu overhead, whereas Rustwright drives Chrome over raw CDP from a Rust core, effectively skipping that overhead.Replacing Playwright’s node driver with a direct CDP connection changes three things:
1. Less abstraction means less complexity. The driver subprocess and its pipe sends CDP commands straight to Chrome instead of going through a middle layer with its own overhead. In our experimentation, the new engine is approx. 2.55× faster and uses 70% less memory than playwright
2. No Playwright driver fingerprint. Stock playwright screamed “I’m a bot” as anyone used it, and while well intentioned, created a frustrating rite of passage for new developers. With this rewrite, the driver doesn’t exist, so its famous signatures (__playwright__binding__ globals, Runtime.enable on the default path (the well-known console-serialization leak)) don’t exist. We just want to be clear: this means there are no Playwright-specific automation fingerprints, the library is not "undetectable"
3. An interoperable rust wrapper makes migration easy: Because rustwright is meant to be interoperable with Playwright, a developer does not need to migrate every callsite to direct CDP. Instead, they can just a one-line import change: from rustwright.sync_api import sync_playwright.All events (clicking / typing) go through the rust shim and execute real CDP calls (not synthetic DOM calls). Complex cases like cross-origin iframes also auto-attach with frame_locator() routing across origins, as you would want them to.
Rustwright is built on Tokio (WebSocket, plus opt-in Unix-pipe transport) and the bindings are exposed to other languages in-process through thin language specific bindings (eg PyO3 bindings for Python and napi-rs bindings for Node). We’re planning on adding support for more languages soon :)
Want to give it a try? Change one line of code!
pip install rustwright
- from playwright.sync_api import sync_playwright
+ from rustwright.sync_api import sync_playwright
Let us know what you think: https://github.com/Skyvern-AI/rustwright
yodon•53m ago
suchintan•28m ago
The first iteration was a rewrite using codex /goal but it ended up producing junk
The second iteration was fable controlling gpt-5.5 agents to write the code with really strong acceptance tests. This pushed the library in the right direction and is basically what you see here today