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
This commit is contained in:
slhx agent
2026-06-25 18:37:27 +02:00
parent da8fb70168
commit 1a67d0c5a4
3 changed files with 31 additions and 5 deletions
+1 -1
View File
@@ -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
+3
View File
@@ -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
+27 -4
View File
@@ -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<u16, ExitCode> {
fn start_html_examples_server(addr: &str) -> Result<HtmlExamplesServer, ExitCode> {
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}");