Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# Changelog

All notable user-facing changes to Rustwright are documented in this file.

## [Unreleased]

### Added

- Added a persistent `rustwright` CLI for browser sessions, accessibility snapshots, and element-reference actions.
- Added `rustwright-mcp`, an MCP stdio server that exposes browser automation tools to MCP clients.
- Added alpha bindings for Go, Java, C#/.NET, Ruby, and PHP, plus a native Rust API, backed by the shared Rust engine.

### Changed

- Moved actionability waits for supported, optionless `AsyncPage.click()` and `AsyncPage.fill()` calls into the Rust core while preserving trusted browser input and a single action deadline.
- Centralized evaluation value decoding in the Rust core for the Go/C-ABI and native Rust surfaces, and added structured timeout, closed, crashed, and disconnected errors for the Python API.

### Fixed

- Fixed locator waits so they re-arm after mid-wait navigation against the original timeout instead of surfacing execution-context errors.
- Fixed remote-CDP actionability probes so they receive the full remaining action budget rather than a short per-probe cap.
- Fixed Node.js evaluation decoding for special numeric values, BigInt, and regular expressions; Go/C-ABI and native Rust now use the core's canonical wire decoder.

## [0.1.1] - 2026-07-15

### Added

- Published the experimental Node.js binding to npm.
- Added native async execution for Chromium launch, context and page creation, and common page operations, with an executor fallback for unsupported cases.

### Changed

- Aligned Chromium launch defaults with Playwright while retaining Rustwright's automation-signal suppression and CDP transport choices.

### Fixed

- Fixed `Locator.fill()` for React-controlled inputs by using the browser input path for ordinary editable text.
- Fixed trusted pointer actions and frame remapping during navigation in nested cross-origin iframes.

## [0.1.0] - 2026-07-14

### Added

- Published the Python package on PyPI for installation with `pip install rustwright`.
- Added a documented parity map for the Python sync and async Playwright API surfaces.

## [0.1.0-alpha.4] - 2026-07-13

### Fixed

- Fixed the npm release command so the assembled package tarball is treated as a local file.

## [0.1.0-alpha.3] - 2026-07-13

### Added

- Released the initial Chromium-only alpha with an in-process Rust CDP core and Playwright-shaped Python sync and async APIs.
- Added trusted CDP input, cross-origin iframe support, and opt-in Python compatibility imports for existing Playwright code.
- Added an experimental Node.js binding for launching Chromium and performing core page navigation, interaction, evaluation, screenshot, and lifecycle operations.

[Unreleased]: https://github.com/Skyvern-AI/rustwright/commits/main
[0.1.1]: https://github.com/Skyvern-AI/rustwright/releases/tag/v0.1.1
[0.1.0]: https://github.com/Skyvern-AI/rustwright/releases/tag/v0.1.0
[0.1.0-alpha.4]: https://github.com/Skyvern-AI/rustwright/releases/tag/v0.1.0-alpha.4
[0.1.0-alpha.3]: https://github.com/Skyvern-AI/rustwright/releases/tag/v0.1.0-alpha.3
2 changes: 2 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ Rustwright is an alpha Rust/PyO3 project with a Playwright-shaped Python API.
Expect some rough edges, large files, and compatibility behavior that is still
being proven.

PRs with user-facing changes should add a bullet under `## [Unreleased]` in `CHANGELOG.md`.

## Local Setup

Use a virtual environment and build the native extension with maturin:
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,8 @@ The Node binding drives an existing Chromium/Chrome — point Rustwright at it w
await browser.close();
```

See the network-independent [`examples/quickstart.js`](examples/quickstart.js) example. After building the Node binding from source, run it from the repository root with `node examples/quickstart.js`.

Only a subset of the API surface is bridged — see [Limitations](#limitations).

## Why Rustwright?
Expand Down
48 changes: 48 additions & 0 deletions examples/fill_form.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
"""Demonstrate filling and submitting a form with Rustwright's sync API."""

from urllib.parse import quote

from rustwright.sync_api import sync_playwright


# Keeping the page inline makes this example deterministic and safe to run offline.
html = """
<!doctype html>
<html>
<body>
<form id="profile-form">
<label>Name <input name="name"></label>
<label>Email <input name="email" type="email"></label>
<button type="submit">Submit</button>
</form>
<output id="submitted"></output>
<script>
document.querySelector("#profile-form").addEventListener("submit", event => {
event.preventDefault();
const form = new FormData(event.currentTarget);
document.querySelector("#submitted").textContent =
`${form.get("name")} (${form.get("email")})`;
});
</script>
</body>
</html>
"""
page_url = "data:text/html;charset=utf-8," + quote(html)


with sync_playwright() as playwright:
browser = playwright.chromium.launch(headless=True)
try:
page = browser.new_page()
page.goto(page_url)

# Label locators describe the form the same way a user does.
page.get_by_label("Name").fill("Ada Lovelace")
page.get_by_label("Email").fill("ada@example.test")

# Submit through the visible button, then read the result rendered by the page.
page.get_by_role("button", name="Submit").click()
submitted = page.locator("#submitted").inner_text()
print(f"submitted: {submitted}")
finally:
browser.close()
44 changes: 44 additions & 0 deletions examples/quickstart.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// --- Source-checkout bootstrap -------------------------------------------
// Installed from npm? Delete this block and use:
// const { chromium } = require('rustwright');
// ESM projects ("type": "module") use: import { chromium } from 'rustwright';
let rustwrightModule = 'rustwright';
try {
require.resolve(rustwrightModule);
} catch (error) {
const firstLine = error instanceof Error ? error.message.split(/\r?\n/, 1)[0] : '';
if (error?.code !== 'MODULE_NOT_FOUND' || firstLine !== "Cannot find module 'rustwright'") {
throw error;
}
rustwrightModule = '../node/index.cjs';
}
const { chromium } = require(rustwrightModule);
// --- End source-checkout bootstrap ---------------------------------------

async function main() {
const { join } = await import('node:path');
const { tmpdir } = await import('node:os');
const screenshotPath = join(tmpdir(), `rustwright-quickstart-${process.pid}.png`);

// Browser discovery checks RUSTWRIGHT_CHROMIUM, CHROME, then CHROMIUM.
const browser = await chromium.launch({ headless: true });
try {
const page = await browser.newPage();
try {
await page.goto('data:text/html,%3Ctitle%3ERustwright%20works%3C/title%3E');
const title = await page.title();
await page.screenshot({ path: screenshotPath });
console.log(`title: ${title}`);
console.log(`screenshot: ${screenshotPath}`);
} finally {
await page.close();
}
} finally {
await browser.close();
}
}

main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
46 changes: 46 additions & 0 deletions examples/scrape_table.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"""Demonstrate scraping an HTML table into dictionaries with Rustwright's sync API."""

from urllib.parse import quote

from rustwright.sync_api import sync_playwright


# This inline fixture keeps the scraping example independent of external websites.
html = """
<!doctype html>
<html>
<body>
<table>
<thead>
<tr><th>Product</th><th>Price</th><th>Stock</th></tr>
</thead>
<tbody>
<tr><td>Notebook</td><td>$4.50</td><td>12</td></tr>
<tr><td>Pen</td><td>$1.25</td><td>40</td></tr>
</tbody>
</table>
</body>
</html>
"""
page_url = "data:text/html;charset=utf-8," + quote(html)


with sync_playwright() as playwright:
browser = playwright.chromium.launch(headless=True)
try:
page = browser.new_page()
page.goto(page_url)

# Read the headings once; they become the keys in every result dictionary.
headers = [text.strip() for text in page.locator("thead th").all_inner_texts()]

# A locator can represent many matches. Use count() and nth() to visit each row.
body_rows = page.locator("tbody tr")
rows = []
for index in range(body_rows.count()):
cells = [text.strip() for text in body_rows.nth(index).locator("td").all_inner_texts()]
rows.append(dict(zip(headers, cells)))

print(f"rows: {rows}")
finally:
browser.close()
35 changes: 35 additions & 0 deletions examples/screenshot_element.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
"""Demonstrate saving a screenshot of one CSS-selected element with Rustwright's sync API."""

from pathlib import Path
from urllib.parse import quote

from rustwright.sync_api import sync_playwright


# A styled inline card gives the element screenshot a visible, repeatable target.
html = """
<!doctype html>
<html>
<body>
<article class="receipt" style="width: 260px; padding: 24px; background: #eef6ff;">
<h1>Order ready</h1>
<p>Your notebook and pen are packed.</p>
</article>
</body>
</html>
"""
page_url = "data:text/html;charset=utf-8," + quote(html)
output_path = Path("screenshot_element.png")


with sync_playwright() as playwright:
browser = playwright.chromium.launch(headless=True)
try:
page = browser.new_page()
page.goto(page_url)

# Locator screenshots capture only the matching element, not the full page.
page.locator(".receipt").screenshot(path=str(output_path))
print(f"saved: {output_path}")
finally:
browser.close()
2 changes: 1 addition & 1 deletion node/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
"scripts": {
"build": "napi build --release -p rustwright-node --dts native.d.ts && node ./scripts/generate-types.mjs",
"smoke": "node ./smoke.mjs",
"test": "node --test test/"
"test": "node --test"
},
"devDependencies": {
"@napi-rs/cli": "^2.18.4"
Expand Down
Loading
Loading