From 935cb5c0dad27d1f3742f64192e2f7a5dac88bb4 Mon Sep 17 00:00:00 2001 From: SGavrl <277843151+SGavrl@users.noreply.github.com> Date: Sat, 16 May 2026 15:11:18 +0000 Subject: [PATCH] fix(cli,js): add --version flag and script-error-resilience test (#147) Two improvements while investigating #147: 1. `obscura --version` / `-V` now print the CARGO_PKG_VERSION instead of erroring out. The issue reporter explicitly called out that there was no version flag, and the existing `obscura serve` banner is the only way to discover the version. 2. New regression test in obscura-js verifies that a TypeError thrown from one script does not poison the runtime: subsequent scripts and DOM queries must continue to work. The reporter's symptom was that offside.js crashing collapsed --dump text output to 1 byte; that cascade should never happen and this test pins it down. The classList error itself is offside.js's own bug (it indexes into the querySelectorAll result without checking for an empty array) and only fires when the .slideout-navigation element is absent from the DOM. On current main that element parses correctly and offside.js runs without error against a captured copy of the affected page. --- crates/obscura-cli/src/main.rs | 6 +++++- crates/obscura-js/src/runtime.rs | 31 +++++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/crates/obscura-cli/src/main.rs b/crates/obscura-cli/src/main.rs index 0e22f04..313fe4c 100644 --- a/crates/obscura-cli/src/main.rs +++ b/crates/obscura-cli/src/main.rs @@ -8,7 +8,11 @@ use tokio::process::Command as TokioCommand; use tokio::time::{timeout, Duration}; #[derive(Parser)] -#[command(name = "obscura", about = "Obscura - A lightweight headless browser for web scraping and automation")] +#[command( + name = "obscura", + version = env!("CARGO_PKG_VERSION"), + about = "Obscura - A lightweight headless browser for web scraping and automation", +)] struct Args { #[arg(short, long, global = true)] verbose: bool, diff --git a/crates/obscura-js/src/runtime.rs b/crates/obscura-js/src/runtime.rs index 7e14ba7..8b5b527 100644 --- a/crates/obscura-js/src/runtime.rs +++ b/crates/obscura-js/src/runtime.rs @@ -934,6 +934,37 @@ mod tests { assert_eq!(result, serde_json::json!(["A", "B"])); } + /// Regression test for #147: a TypeError in one script must not poison + /// the runtime so that subsequent scripts (or DOM queries) collapse to + /// empty. The reporter saw `--dump text` return 1 byte after offside.js + /// crashed; that cascade should never happen. + #[test] + fn script_typeerror_does_not_poison_subsequent_execution() { + let mut rt = setup_runtime( + "

BODY_TEXT

", + ); + + // 1. First script throws the same flavor of error offside.js produced + // (`Cannot read properties of undefined (reading 'classList')`). + let err = rt + .execute_script("buggy", "var x; x.classList.add('y');") + .unwrap_err(); + assert!(err.contains("classList") || err.contains("undefined"), + "expected classList/undefined error, got: {}", err); + + // 2. The runtime must still be usable: a follow-up script runs. + rt.execute_script("ok", "globalThis.__after_error = 'still alive';") + .unwrap(); + let result = rt.evaluate("globalThis.__after_error").unwrap(); + assert_eq!(result, serde_json::json!("still alive")); + + // 3. DOM queries still work after the script error. + let text = rt + .evaluate("document.querySelector('#hit').textContent") + .unwrap(); + assert_eq!(text, serde_json::json!("BODY_TEXT")); + } + #[test] fn test_console_log() { let mut rt = setup_runtime("");