Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion crates/ps-qa/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "ps-qa"
description = "Drive a running Blitz app through its MCP control socket and assert what the renderer did"
version = "0.5.11"
version = "0.5.12"
edition = "2024"
rust-version = "1.88"
license = "MIT OR Apache-2.0"
Expand Down
22 changes: 21 additions & 1 deletion crates/ps-qa/src/target.rs
Original file line number Diff line number Diff line change
Expand Up @@ -160,8 +160,28 @@ pub(crate) async fn locate_control(
let mut candidates: Vec<_> = snapshot
.nodes
.iter()
/*
* The role gate is skipped for a selector that already names one
* exact node.
*
* `#id` and `@slot` identify a single element by what the author
* wrote, which is the reason `dom_id` is carried at all. Deciding
* first that only interactive roles are addressable threw those
* away before the selector was ever consulted, so a `<label>` --
* `generic`, with an id, painted and enabled -- could not be
* pressed by any spelling. That is not a niche shape: it is how
* every switch and styled checkbox on the web is built, the visible
* thing a person actually hits, and the only way to press a control
* the way its own markup intends.
*
* A name or `role:name` still goes through the gate. Those are
* descriptions rather than identities, and matching them against
* every generic node in a document would make one ambiguous.
*/
.filter(|n| {
roles.contains(&"*")
selector_dom_id(want).is_some()
|| selector_slot(want).is_some()
|| roles.contains(&"*")
|| roles.is_empty() && reach::interactive(n)
|| roles.contains(&n.role.as_str())
})
Expand Down
10 changes: 5 additions & 5 deletions crates/qa-inspect-host/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "qa-inspect-host"
description = "Host a Blitz document and serve it over the inspection socket"
version = "0.1.8"
version = "0.1.9"
edition = "2024"
rust-version = "1.88"
license = "MIT OR Apache-2.0"
Expand All @@ -18,11 +18,11 @@ publish = true
# precisely so that driving a control does not build a browser engine. Keeping
# the host in its own crate is what lets both stay true.
[dependencies]
blitz-dom = { package = "ps-blitz-dom", version = "^0.3", features = ["system-fonts"] }
blitz-script = { package = "ps-blitz-script", version = "^0.3", features = ["system-fonts"] }
blitz-traits = { package = "ps-blitz-traits", version = "^0.3" }
blitz-dom = { package = "ps-blitz-dom", version = "^0.4", features = ["system-fonts"] }
blitz-script = { package = "ps-blitz-script", version = "^0.4", features = ["system-fonts"] }
blitz-traits = { package = "ps-blitz-traits", version = "^0.4" }
brotli = { version = "^8.0.4", default-features = false, features = ["std"] }
tauri-runtime-blitz = { version = "^0.3", features = ["agent-control"] }
tauri-runtime-blitz = { version = "^0.3", default-features = false, features = ["agent-control"] }
# Only the channel: `ControlBridge` hands its answer back on a
# `tokio::sync::oneshot`, so the type has to match. No runtime, no reactor.
tokio = { version = "^1", default-features = false, features = ["sync"] }
Expand Down
30 changes: 29 additions & 1 deletion crates/qa-inspect-host/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,35 @@ fn create_dist_document(dist: &std::path::Path, url: &str) -> Result<ScriptDocum
trace(&format!("loading page: {}", page_path.display()));
let index = fs::read_to_string(&page_path)
.map_err(|error| format!("could not read {}: {error}", page_path.display()))?;
let javascript_url = asset_url(&index, "src")?;
/*
* A page that carries its own markup is served as it stands.
*
* Everything below rebuilds the document: it pulls the one external
* stylesheet and the one external script out of a bundler's `index.html`
* and synthesises a shell around them, because that is the shape a
* component harness emits and the `<div id="root">` it mounts into is not
* in the file.
*
* That shape is not the only useful one. A repository testing the engine
* itself, or a reduction of a bug, writes the markup by hand: a control, a
* listener, and a heading naming what the listener saw. Demanding a bundle
* from those meant standing up a JavaScript toolchain to assert that a
* checkbox toggles, so they went and wrote their own driver instead, which
* is how a renderer ends up with two testing stories and one of them
* untested.
*
* Detection is the absence of an external script, not a flag: a hand-written
* page has inline script or none, and a built one always has `src=`.
*/
let Ok(javascript_url) = asset_url(&index, "src") else {
trace("no external bundle; serving the page as written");
let config = DocumentConfig {
base_url: Some(url.into()),
..DocumentConfig::default()
};
return Ok(ScriptDocument::from_html(&index, config));
};

let stylesheet_url = asset_url(&index, "href")?;
let css = read_brotli_asset(dist, stylesheet_url, "external CSS")?;
let javascript = read_brotli_asset(dist, javascript_url, "external JavaScript")?;
Expand Down
Loading