use chromiumoxide::Page;
use crate::accessibility::FocusSnapshot;
use crate::audit::normalized::{JourneyStep, JourneyTrace};
use crate::error::Result;
use crate::interaction::{focus, keyboard, stability};
pub struct TabWalkRecord {
pub trace: JourneyTrace,
pub snapshots: Vec<FocusSnapshot>,
pub dom_order: Vec<String>,
}
pub async fn record(page: &Page, max_steps: usize) -> Result<TabWalkRecord> {
let mut trace = JourneyTrace {
journey: "tab_walk".to_string(),
steps: Vec::with_capacity(max_steps),
};
let mut snapshots: Vec<FocusSnapshot> = Vec::with_capacity(max_steps);
let dom_order = focus::collect_focusable_dom_order(page).await;
let start = focus::capture_focus(page).await?;
trace.steps.push(JourneyStep {
action: "start".to_string(),
target: None,
focus: start.selector.clone(),
result: None,
snapshot_label: Some("initial".to_string()),
});
let mut last_focus_selector = start.selector.clone();
snapshots.push(start);
for i in 0..max_steps {
keyboard::press_tab(page).await?;
stability::settle(page).await?;
let snap = focus::capture_focus(page).await?;
let current = snap.selector.clone();
let result = if current == last_focus_selector {
Some("focus_stuck".to_string())
} else if current.is_none() {
Some("focus_lost".to_string())
} else {
None
};
let stuck = result.as_deref() == Some("focus_stuck");
trace.steps.push(JourneyStep {
action: "tab".to_string(),
target: None,
focus: current.clone(),
result,
snapshot_label: Some(format!("after_tab_{}", i + 1)),
});
snapshots.push(snap);
if stuck {
break;
}
last_focus_selector = current;
}
Ok(TabWalkRecord {
trace,
snapshots,
dom_order,
})
}