use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct A11yNode {
pub node_id: String,
pub role: String,
pub name: Option<String>,
pub value: Option<String>,
pub bounds: Bounds,
pub children: Vec<String>,
pub focusable: bool,
pub focused: bool,
pub disabled: bool,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct Bounds {
pub x: f64,
pub y: f64,
pub width: f64,
pub height: f64,
}
impl Bounds {
pub fn new(x: f64, y: f64, width: f64, height: f64) -> Self {
Self {
x,
y,
width,
height,
}
}
pub fn center(&self) -> (f64, f64) {
(self.x + self.width / 2.0, self.y + self.height / 2.0)
}
pub fn contains(&self, x: f64, y: f64) -> bool {
x >= self.x && x <= self.x + self.width && y >= self.y && y <= self.y + self.height
}
pub fn overlaps(&self, other: &Bounds) -> bool {
self.x < other.x + other.width
&& self.x + self.width > other.x
&& self.y < other.y + other.height
&& self.y + self.height > other.y
}
pub fn iou(&self, other: &Bounds) -> f64 {
let x1 = self.x.max(other.x);
let y1 = self.y.max(other.y);
let x2 = (self.x + self.width).min(other.x + other.width);
let y2 = (self.y + self.height).min(other.y + other.height);
if x2 <= x1 || y2 <= y1 {
return 0.0;
}
let intersection = (x2 - x1) * (y2 - y1);
let self_area = self.width * self.height;
let other_area = other.width * other.height;
let union = self_area + other_area - intersection;
if union <= 0.0 {
0.0
} else {
intersection / union
}
}
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
pub struct Viewport {
pub width: u32,
pub height: u32,
pub device_pixel_ratio: f64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Modifier {
Shift,
Control,
Alt,
Meta,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", rename_all = "snake_case")]
pub enum WaitCondition {
UrlChanged,
A11yContainsText { text: String },
ElementWithName {
name_contains: String,
#[serde(skip_serializing_if = "Option::is_none")]
role: Option<String>,
},
PageLoaded,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CookieParam {
pub name: String,
pub value: String,
pub domain: String,
#[serde(default = "default_path")]
pub path: String,
#[serde(default)]
pub secure: bool,
#[serde(default)]
pub http_only: bool,
#[serde(default)]
pub same_site: Option<String>,
}
fn default_path() -> String {
"/".to_string()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bounds_center() {
let bounds = Bounds::new(100.0, 200.0, 50.0, 30.0);
let (cx, cy) = bounds.center();
assert_eq!(cx, 125.0);
assert_eq!(cy, 215.0);
}
#[test]
fn test_bounds_contains() {
let bounds = Bounds::new(100.0, 200.0, 50.0, 30.0);
assert!(bounds.contains(125.0, 215.0));
assert!(!bounds.contains(50.0, 215.0));
}
#[test]
fn test_bounds_iou() {
let a = Bounds::new(0.0, 0.0, 10.0, 10.0);
let b = Bounds::new(5.0, 5.0, 10.0, 10.0);
let iou = a.iou(&b);
assert!((iou - 25.0 / 175.0).abs() < 0.001);
}
#[test]
fn test_bounds_no_overlap() {
let a = Bounds::new(0.0, 0.0, 10.0, 10.0);
let b = Bounds::new(20.0, 20.0, 10.0, 10.0);
assert_eq!(a.iou(&b), 0.0);
assert!(!a.overlaps(&b));
}
}