From 1a67d0c5a48042f567641dba9b7519a02ab0b452 Mon Sep 17 00:00:00 2001 From: slhx agent Date: Thu, 25 Jun 2026 18:37:27 +0200 Subject: [PATCH] test(xtask): assert html examples avoid reloads Wrap html_examples browser smoke interactions with a no-navigation guard so dynamic paths fail if native form fallback or full-page reload occurs. req: test/014 req: test/006 --- AGENTS.md | 2 +- REQUIREMENTS.md | 3 +++ hemx-xtask/src/main.rs | 31 +++++++++++++++++++++++++++---- 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 3284824..6eb4b2f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -51,7 +51,7 @@ Keep it stable. Prefer pointers to canonical sources over copied structure, file - Add only durable style, ownership, gotchas, and at most a few stable commands agents should actually run. - Prefer links or pointers to canonical sources over copied lists. - Avoid project trees, architecture maps, generated inventories, current file sizes, issue lists, TODO inventories, and other snapshots that will rot. -- Stable commands: `cargo run -p hemx-xtask -- test`, `cargo run -p hemx-xtask -- html-examples-smoke`, `cargo check --workspace`, `redgate health --strict`. Use the xtask runner for full verification so jobs are capped from local CPU and memory; use the html_examples smoke for focused repo-owned browser verification of the HTML pattern gallery, not `/tmp` scripts. req: test/004 req: test/006 req: test/012 req: test/013 +- Stable commands: `cargo run -p hemx-xtask -- test`, `cargo run -p hemx-xtask -- html-examples-smoke`, `cargo check --workspace`, `redgate health --strict`. Use the xtask runner for full verification so jobs are capped from local CPU and memory; use the html_examples smoke for focused repo-owned browser verification of the HTML pattern gallery, no-reload dynamic interactions, and no `/tmp` scripts. req: test/004 req: test/006 req: test/012 req: test/013 req: test/014 - Example behavior tests should prefer `hemx_test` generated-resource assertions over raw slot constants or raw effect/payload matching; keep browser selector helpers as test adapters only, not authoring APIs. req: test/008 req: test/009 req: test/010 - Run the workout product exemplar with `cargo run -p hemx-xtask -- workout dev` and open `http://127.0.0.1:3028`; set `HEMX_WORKOUT_ADDR=127.0.0.1:3030` if the default port is busy. Its durable visual direction and recovery expectations live in `examples/workout/DESIGN.md`. req: examples/008 - Use the same Workout command surface for tests, production build, and mobile release: `cargo run -p hemx-xtask -- workout test`, `cargo run -p hemx-xtask -- workout build`, `HEMX_WORKOUT_ORIGIN=https://workout.example.com cargo run -p hemx-xtask -- workout mobile-release`, and `HEMX_WORKOUT_ORIGIN=https://workout.example.com cargo run -p hemx-xtask -- workout mobile-verify`; Android/iOS SDKs, store submission targets, and signing remain external blockers, not repo-owned secrets, and do not imply a broad `hemx-mobile` framework. req: examples/006 req: examples/011 req: examples/013 diff --git a/REQUIREMENTS.md b/REQUIREMENTS.md index 96683e5..d9ba17b 100644 --- a/REQUIREMENTS.md +++ b/REQUIREMENTS.md @@ -856,6 +856,9 @@ what a valid business email is. [north_star] ### req: test/013 0 013 Durable browser coverage must not rely on ad hoc `/tmp` scripts. [north_star] +### req: test/014 +0 014 html_examples browser smoke asserts dynamic interactions complete without full-page reload or navigation. [north_star] + --- ## check diff --git a/hemx-xtask/src/main.rs b/hemx-xtask/src/main.rs index 946f405..7fba4fc 100644 --- a/hemx-xtask/src/main.rs +++ b/hemx-xtask/src/main.rs @@ -103,7 +103,10 @@ fn run_html_examples_smoke() -> ExitCode { if let Err(code) = cdp_wait_for_html_examples() { return code; } - if let Err(code) = cdp_assert("revealed fallback without IntersectionObserver", REVEALED_FALLBACK_SMOKE) { + if let Err(code) = cdp_assert( + "revealed fallback without IntersectionObserver", + REVEALED_FALLBACK_SMOKE, + ) { return code; } println!("html_examples smoke ok: revealed fallback without IntersectionObserver"); @@ -124,7 +127,10 @@ fn pick_unused_port() -> Result { fn start_html_examples_server(addr: &str) -> Result { let mut child = Command::new("cargo") .args(["run", "-p", "hemx-html-examples"]) - .env("HEMX_HTML_EXAMPLES_PORT", addr.rsplit(':').next().unwrap_or("3029")) + .env( + "HEMX_HTML_EXAMPLES_PORT", + addr.rsplit(':').next().unwrap_or("3029"), + ) .stdout(Stdio::null()) .stderr(Stdio::null()) .spawn() @@ -206,7 +212,9 @@ fn cdp_wait_for_html_examples() -> Result<(), ExitCode> { ExitCode::FAILURE })?; if output.status.success() - && String::from_utf8_lossy(&output.stdout).trim().ends_with("true") + && String::from_utf8_lossy(&output.stdout) + .trim() + .ends_with("true") { return Ok(()); } @@ -217,8 +225,23 @@ fn cdp_wait_for_html_examples() -> Result<(), ExitCode> { } fn cdp_assert(name: &str, script: &str) -> Result<(), ExitCode> { + // Each interaction must be handled by hemx without full-page navigation or + // reload; failures here catch native form fallback sneaking into the smoke. req: test/014 + let guarded_script = format!( + r#"(async()=>{{ + const __hemxSmokeUrl = location.href; + const __hemxSmokeMarker = String(Date.now()) + Math.random(); + const __hemxSmokeNavCount = performance.getEntriesByType("navigation").length; + window.__hemxSmokeNoReload = __hemxSmokeMarker; + const __hemxSmokeResult = await ({script}); + if (location.href !== __hemxSmokeUrl) throw new Error("page navigated during smoke interaction"); + if (window.__hemxSmokeNoReload !== __hemxSmokeMarker) throw new Error("page reloaded during smoke interaction"); + if (performance.getEntriesByType("navigation").length !== __hemxSmokeNavCount) throw new Error("navigation entry changed during smoke interaction"); + return __hemxSmokeResult; + }})()"# + ); let output = Command::new("cdp-browser") - .args(["js", script]) + .args(["js", &guarded_script]) .output() .map_err(|err| { eprintln!("failed to run browser smoke `{name}`: {err}");