diff --git a/crates/glass-mcp/src/tools.rs b/crates/glass-mcp/src/tools.rs
index 67a9e32b..fe939ff2 100644
--- a/crates/glass-mcp/src/tools.rs
+++ b/crates/glass-mcp/src/tools.rs
@@ -497,1771 +497,7 @@ mod input; // filled in Task 5
mod wait;
#[cfg(test)]
-pub(crate) mod testutil {
- //! A scriptable in-memory `Platform` so tool logic can be tested with no X
- //! server. Mirrors the one in glass-core's own tests.
- use std::collections::VecDeque;
- use std::sync::{Arc, Mutex};
-
- use glass_core::{
- Accessibility, AppSpec, AxContext, AxNode, AxNodeId, AxRect, AxRole, AxStates, AxTarget,
- AxTree, Backend, BaselineStore, Frame, Glass, GlassError, KeyEvent, Platform,
- PlatformFactory, PointerEvent, Region, Result, Stream, Truncation, TruncationLimit,
- WindowGeometry, WindowId, WindowInfo, WindowOp,
- };
-
- use super::{OutContent, ToolOutput};
-
- #[derive(Default)]
- pub struct FakePlatform {
- pub geometry: WindowGeometry,
- pub frames: VecDeque,
- pub pending_logs: Vec<(Stream, String)>,
- pub pointer_events: Vec,
- pub key_events: Vec,
- pub started: bool,
- pub events: Arc>>,
- pub clipboard: String,
- /// Count of `capture_frame` calls — lets a test assert a settle actually captured
- /// frames (e.g. `return:"snapshot"` settling before it folds the tree).
- pub captures: Arc>,
- /// Specs `start_app` was handed, in order — the only observer of what the tool layer
- /// built from `glass_start`'s arguments.
- pub specs: Arc>>,
- }
-
- impl FakePlatform {
- pub fn new(width: u32, height: u32) -> Self {
- Self {
- geometry: WindowGeometry {
- x: 0,
- y: 0,
- width,
- height,
- },
- ..Default::default()
- }
- }
- pub fn with_frames(mut self, frames: Vec) -> Self {
- self.frames = frames.into();
- self
- }
- pub fn with_logs(mut self, logs: Vec<(Stream, &str)>) -> Self {
- self.pending_logs = logs.into_iter().map(|(s, t)| (s, t.to_string())).collect();
- self
- }
- pub fn with_event_log(mut self, log: Arc>>) -> Self {
- self.events = log;
- self
- }
- pub fn with_capture_log(mut self, log: Arc>) -> Self {
- self.captures = log;
- self
- }
- pub fn with_spec_log(mut self, log: Arc>>) -> Self {
- self.specs = log;
- self
- }
- }
-
- /// A 4x4 opaque frame, constant everywhere except pixel (3,3), set to `corner` —
- /// a stand-in for a perpetually animating rect (a blinking caret, a clock) in
- /// `ignore`-masking tests. Mirrors glass-core's own test helper of the same name.
- pub fn frame_4x4_corner(corner: [u8; 4]) -> Frame {
- let mut px = vec![0u8; 4 * 4 * 4];
- for i in 0..16 {
- px[i * 4 + 3] = 255; // alpha
- }
- let idx = (3 * 4 + 3) * 4;
- px[idx..idx + 4].copy_from_slice(&corner);
- Frame::new(4, 4, px).expect("4x4 frame is well-formed")
- }
-
- impl Platform for FakePlatform {
- fn start_app(&mut self, spec: &AppSpec) -> Result {
- self.specs.lock().unwrap().push(spec.clone());
- self.started = true;
- Ok(self.geometry.clone())
- }
- fn stop_app_by(&mut self, _deadline: glass_core::Deadline) -> Result<()> {
- self.started = false;
- Ok(())
- }
- fn capture_frame(&mut self, region: Option<&Region>) -> Result {
- *self.captures.lock().unwrap() += 1;
- let frame = match self.frames.pop_front() {
- Some(f) => {
- if self.frames.is_empty() {
- self.frames.push_back(f.clone());
- }
- f
- }
- None => return Err(GlassError::CaptureFailed("no scripted frames".into())),
- };
- match region {
- Some(r) => frame.crop(r),
- None => Ok(frame),
- }
- }
- fn send_pointer(&mut self, e: &PointerEvent) -> Result<()> {
- self.events.lock().unwrap().push(match e {
- PointerEvent::Click { x, y, .. } => format!("click({x},{y})"),
- PointerEvent::Move { x, y } => format!("move({x},{y})"),
- PointerEvent::Drag {
- from_x,
- from_y,
- to_x,
- to_y,
- ..
- } => {
- format!("drag({from_x},{from_y}->{to_x},{to_y})")
- }
- PointerEvent::Scroll { x, y, dx, dy, .. } => format!("scroll({x},{y},{dx},{dy})"),
- PointerEvent::Gesture { pointers, .. } => format!("gesture({})", pointers.len()),
- });
- self.pointer_events.push(e.clone());
- Ok(())
- }
- fn send_key(&mut self, e: &KeyEvent) -> Result<()> {
- self.events.lock().unwrap().push(match e {
- KeyEvent::Text(t) => format!("type({t})"),
- KeyEvent::Chord(c) => format!("key({c})"),
- });
- self.key_events.push(e.clone());
- Ok(())
- }
- fn window(&mut self, op: &WindowOp) -> Result {
- match *op {
- WindowOp::Resize { width, height } => {
- self.geometry.width = width;
- self.geometry.height = height;
- }
- WindowOp::Move { x, y } => {
- self.geometry.x = x;
- self.geometry.y = y;
- }
- WindowOp::Focus | WindowOp::Geometry => {}
- }
- Ok(self.geometry.clone())
- }
- fn list_windows(&mut self) -> Result> {
- Ok(vec![WindowInfo {
- id: WindowId(0),
- title: Some("fake".into()),
- class: None,
- geometry: self.geometry.clone(),
- active: true,
- }])
- }
- fn select_window(&mut self, id: WindowId) -> Result {
- if id == WindowId(0) {
- Ok(self.geometry.clone())
- } else {
- Err(GlassError::WindowNotFound)
- }
- }
- fn drain_logs(&mut self) -> Vec<(Stream, String)> {
- std::mem::take(&mut self.pending_logs)
- }
- fn get_clipboard(&mut self) -> Result {
- Ok(self.clipboard.clone())
- }
- fn set_clipboard(&mut self, text: &str) -> Result<()> {
- self.clipboard = text.to_string();
- Ok(())
- }
- }
-
- /// Build a `Glass` over a `FakePlatform` with a throwaway baseline dir.
- pub fn glass_with(platform: FakePlatform) -> Glass {
- let dir = tempfile::tempdir().unwrap();
- let root = dir.path().join("baselines");
- std::mem::forget(dir); // keep the dir alive for the test
- // Factory yields the pre-scripted platform once.
- let mut held: Option> = Some(Box::new(platform));
- let factory: PlatformFactory = Box::new(move |_backend| {
- let platform = held
- .take()
- .ok_or_else(|| GlassError::Backend("test factory called twice".into()))?;
- Ok(Backend::display_only(platform))
- });
- Glass::new(factory, "x11".into(), BaselineStore::new(root), 100)
- }
-
- /// What `FakeAccessibility::set_value` should do — lets a test model the
- /// backend rejecting a write (element not editable, or changed since the
- /// snapshot) so the tool layer's error propagation can be exercised.
- #[derive(Clone, Copy, Default, PartialEq)]
- pub enum SetOutcome {
- #[default]
- Ok,
- NotEditable,
- Changed,
- }
-
- /// What `FakeAccessibility::invoke` should do. Default mirrors the trait's own
- /// default (unsupported) — a backend that never implemented the native action,
- /// so `click_element` falls back to the pointer path unless a test opts into
- /// [`InvokeOutcome::Ok`].
- #[derive(Clone, Copy, Default, PartialEq)]
- pub enum InvokeOutcome {
- #[default]
- Unsupported,
- Ok,
- /// The native action fired on a different element than the one named.
- OkOnAnother(u32),
- }
-
- pub struct FakeAccessibility {
- pub tree: AxTree,
- pub set_log: std::sync::Arc>>,
- pub set_outcome: SetOutcome,
- pub invoke_outcome: InvokeOutcome,
- }
-
- impl Accessibility for FakeAccessibility {
- fn snapshot(&mut self, _ctx: &AxContext) -> Result {
- Ok(self.tree.clone())
- }
- fn set_value(&mut self, _ctx: &AxContext, target: &AxTarget, text: &str) -> Result<()> {
- match self.set_outcome {
- SetOutcome::NotEditable => {
- return Err(GlassError::AxElementNotEditable(target.id.0));
- }
- SetOutcome::Changed => return Err(GlassError::AxElementChanged(target.id.0)),
- SetOutcome::Ok => {}
- }
- self.set_log
- .lock()
- .unwrap()
- .push((target.clone(), text.to_string()));
- Ok(())
- }
- fn invoke(&mut self, _ctx: &AxContext, _target: &AxTarget) -> Result