diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 33caede..0427288 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -29,8 +29,24 @@ A few notes to keep the project maintainable: ## Building +Obscura supports four release configurations. Keep all four building when you +change feature gates or shared code: + +| Configuration | Command | +| --- | --- | +| Rendering | `cargo build --release -p obscura-cli --bins --features render` | +| Rendering and stealth | `cargo build --release -p obscura-cli --bins --features render,stealth` | +| No rendering | `cargo build --release -p obscura-cli --bins --no-default-features` | +| No rendering, with stealth | `cargo build --release -p obscura-cli --bins --no-default-features --features stealth` | + +The standard release archives and Docker image include rendering. The +`-no-render` release variants keep the smaller DOM, JavaScript, networking, +CDP, and automation runtime but intentionally omit screenshots, screencast, +and PDF output. + ```bash -cargo build --release --features render # binary at ./target/release/obscura +cargo build --release -p obscura-cli --bins --features render +# binary at ./target/release/obscura ``` - The first build compiles V8 from source: roughly 5 minutes and a few GB of @@ -52,6 +68,11 @@ cargo nextest run --release --features render -p cargo nextest run --release --features render --no-fail-fast ``` +If you changed shared DOM, JavaScript, CDP, CLI, or feature-gated code, also +run the affected tests without rendering and build the no-render variant. A +render-only request made through a no-render binary must return a clear +unsupported error rather than panic or silently produce an invalid result. + `cargo test` runs the whole test binary in one process, but the engine holds a single V8 isolate per process, so the runtime tests fail under it. `nextest` runs each test in its own process, which is the only supported way. @@ -66,13 +87,56 @@ OBSCURA_BIN=./target/release/obscura python3 obstacle-course/run.py --runs 1 --w It serves local fixtures, so it is deterministic and offline. +## Rendering changes + +The native renderer is shared by every capture surface: + +- CLI `fetch --screenshot` (`-s`), including viewport and full-page captures. +- CDP `Page.captureScreenshot`, `Page.startScreencast`, + `Page.stopScreencast`, and `Page.printToPDF`. +- Puppeteer and Playwright screenshot and PDF calls made through CDP. +- MCP `browser_screenshot` and `browser_pdf`. + +It currently covers block, inline, flex, grid, table, float, positioned, +overflow, and transformed layout, plus text, images, SVG, canvas, backgrounds, +borders, animation, fixed and sticky elements, and screen and print output. +This is an independent engine, not Chromium, so long-tail CSS, media, +compositor behavior, and platform font rasterization can still differ. Fix the +underlying layout or paint rule instead of adding site-specific behavior. + +For layout, style, paint, image, animation, viewport, scrolling, or pagination +changes: + +1. Add or update a small deterministic fixture in `render-repros/` that + isolates the behavior. +2. Build the render binary, then run `render-repros/run.sh`. This captures each + fixture in Obscura and Chromium and runs the structural checks. +3. Run `render-repros/representative-suite/run.sh ` at the top + of the configured pages, then run it again with `bottom` as the second + argument. The output directory must not already exist. +4. Exercise the affected public surface, not just the renderer internally. In + particular, cover clipped and full-page screenshots, viewport size and + device-pixel ratio, and a scrolled page when geometry is involved. +5. If lifecycle or animation scheduling changed, verify screencast frame + delivery and acknowledgment. If print layout changed, verify PDF page size, + margins, backgrounds, page ranges, and stream output where relevant. +6. Compare old and new revisions with identical release builds, fixtures, + viewport, device-pixel ratio, fonts, network state, settle time, scroll + position, and capture options. Confirm resources loaded successfully before + interpreting image differences. + +Pixel deltas are useful tripwires, but they are not a fidelity score by +themselves. Include the focused repro, before and after images, structural +metrics, and any remaining expected difference in the PR. Rendering work must +also preserve non-render automation behavior and the no-render build. + ## Before you open a PR For any code change: 1. `cargo nextest run --release --features render` passes for the crates you touched. 2. The full render-feature nextest command above passes. -3. `CARGO_INCREMENTAL=0 CARGO_BUILD_JOBS=2 cargo build --release --features render` compiles clean. +3. `CARGO_INCREMENTAL=0 CARGO_BUILD_JOBS=2 cargo build --release -p obscura-cli --bins --features render` compiles clean. 4. The obstacle course still reports **33/33**. 5. **Performance is a hard constraint.** Obscura is roughly 12x faster and uses about 6x less memory than headless Chrome on framework pages. Keep native @@ -81,11 +145,8 @@ For any code change: with identical release builds, fixtures, networks, viewport, settle policy, and capture path. Report a distribution and memory use; the noise floor is about plus or minus 10%. -6. For rendering changes, run the deterministic `render-repros` fixtures and - the representative real-site suite at both the top and bottom of pages. - Confirm resource and navigation success before interpreting image diffs; - pixel error is a tripwire, not a verdict. Never add hostname-specific layout - or style behavior. +6. For rendering changes, complete the rendering checks above. For shared or + feature-gated changes, also build and test without the `render` feature. 7. For stealth changes, re-test with `--stealth`. A non-stealth binary does not exercise the `wreq` path. @@ -130,11 +191,15 @@ Fixes #316. Open an issue with enough detail to reproduce: - The obscura version or commit, plus OS and architecture. +- The build configuration: render, render and stealth, no-render, or no-render + and stealth. - A repro: a URL, an `--eval` snippet, or a short CDP sequence. - What you expected and what actually happened. - If it is a rendering or compatibility issue, whether headless Chrome behaves - the same. Anti-bot, CAPTCHA, and login walls block headless Chrome too from a - datacenter IP, so those are not engine bugs. + the same. Include the viewport, device-pixel ratio, scroll position, capture + mode, screenshot clip or full-page setting, and PDF options where applicable. + Anti-bot, CAPTCHA, and login walls block headless Chrome too from a datacenter + IP, so those are not engine bugs. Multi-statement `--eval` that starts with `const` returns `null` (V8 gives `const` an empty completion value). Wrap repro snippets in an IIFE: