rustwright-core 0.1.1

Rust CDP core for a Python Playwright-compatible automation API
# Rustwright PHP binding

This package is a plain-PHP FFI wrapper for the Rustwright C ABI. It requires
PHP 8.1 or newer with `ext-ffi`; Composer is optional because the entrypoints
include a small PSR-4 fallback autoloader.

## Smoke

From the repository root, run exactly:

```sh
php -d ffi.enable=1 php/smoke.php
```

The smoke launches headless Chromium, drives an inline form, evaluates
JavaScript, captures a PNG, and prints one JSON line.

## Benchmark runner

From the repository root, run exactly:

```sh
php -d ffi.enable=1 php/runner.php --manifest bindings/cases/smoke.json --lib target/release/librustwright_capi.dylib --out /tmp/php-layout.json
```

The runner also accepts `--cases id1,id2`. Selection preserves manifest order.
It writes the contract results object to `--out`, prints the same JSON, exits 0
only when every selected case passes, exits 1 for case failures, and exits 2
for invocation or manifest errors. `--manifest`, `--lib`, and `--out` are all
required.

When no library path is passed to `Chromium::launch()` or
`Chromium::executablePath()`, the binding loads
`target/release/librustwright_capi.dylib` on macOS or
`target/release/librustwright_capi.so` on Linux, relative to the current
working directory. Run from the repository root, or set
`RUSTWRIGHT_CAPI_LIB`/pass the explicit library path.

## API

The `Rustwright\Chromium`, `Rustwright\Browser`, and `Rustwright\Page` classes
provide the complete alpha surface:

- `Chromium::launch($options, $libraryPath)` and
  `Chromium::executablePath($libraryPath)`
- `Browser->newPage()`, `Browser->close()`, and `Browser->wsEndpoint()`
- `Page->goto()`, `click()`, `fill()`, `title()`, `textContent()`, `evaluate()`,
  `screenshot()`, and `close()`

Launch options accept camelCase Node names or the core snake_case aliases and
are normalized to the C JSON wire shape. Screenshot options accept the Node
names, with `full_page` and `omit_background` aliases. Timeouts are
milliseconds; omitted/null timeouts are sent as IEEE-754 `NAN`.

`evaluate()` recursively unwraps Rustwright CDP array/object wrappers. Dates
become `DateTimeImmutable`, URLs become strings, regular expressions become
objects with `pattern` and `flags`, and non-finite numbers become PHP floats.
PHP has no direct equivalents for JavaScript `undefined`, symbols, or
functions, so those tagged values intentionally decode to `null`. Manifest-v1
captures are JSON-compatible and do not require cyclic reference identity.

Rust-owned strings and screenshot buffers are copied before their matching
free call. Page and browser `close()` methods perform the native close and then
free each opaque handle exactly once; destructors provide best-effort cleanup.