Skip to main content

eoka_server/
target.rs

1use crate::eoka::{Page, Result};
2use serde::Deserialize;
3
4#[derive(Debug, Clone)]
5pub enum Target {
6    Index(usize),
7    Ref(String),
8    Live(LivePattern),
9}
10
11#[derive(Debug, Clone)]
12pub enum LivePattern {
13    Text(String),
14    Placeholder(String),
15    Role(String),
16    Css(String),
17    Id(String),
18}
19
20impl Target {
21    pub fn parse(s: &str) -> Self {
22        let s = s.trim();
23
24        let index = s
25            .strip_prefix("index:")
26            .or_else(|| s.strip_prefix("idx:"))
27            .unwrap_or(s)
28            .trim();
29
30        let index = index
31            .strip_prefix('[')
32            .and_then(|v| v.strip_suffix(']'))
33            .unwrap_or(index)
34            .trim();
35        if let Ok(idx) = index.parse::<usize>() {
36            return Target::Index(idx);
37        }
38
39        if s.starts_with('@') {
40            return Target::Ref(s.to_string());
41        }
42
43        Target::Live(LivePattern::parse(s))
44    }
45}
46
47impl LivePattern {
48    pub fn parse(s: &str) -> Self {
49        if let Some(v) = s.strip_prefix("text:") {
50            return LivePattern::Text(v.into());
51        }
52        if let Some(v) = s.strip_prefix("placeholder:") {
53            return LivePattern::Placeholder(v.into());
54        }
55        if let Some(v) = s.strip_prefix("role:") {
56            return LivePattern::Role(v.into());
57        }
58        if let Some(v) = s.strip_prefix("css:") {
59            return LivePattern::Css(v.into());
60        }
61        if let Some(v) = s.strip_prefix("id:") {
62            return LivePattern::Id(v.into());
63        }
64        LivePattern::Text(s.into())
65    }
66
67    fn as_js_args(&self) -> (&'static str, &str) {
68        match self {
69            LivePattern::Text(v) => ("text", v),
70            LivePattern::Placeholder(v) => ("placeholder", v),
71            LivePattern::Role(v) => ("role", v),
72            LivePattern::Css(v) => ("css", v),
73            LivePattern::Id(v) => ("id", v),
74        }
75    }
76}
77
78#[derive(Debug, Deserialize, Clone, Default)]
79pub struct BBox {
80    pub x: f64,
81    pub y: f64,
82    pub width: f64,
83    pub height: f64,
84}
85
86#[derive(Debug, Deserialize)]
87pub struct Resolved {
88    pub selector: String,
89    pub tag: String,
90    pub text: String,
91    pub found: bool,
92    #[serde(default)]
93    pub error: Option<String>,
94    #[serde(default)]
95    pub bbox: BBox,
96}
97
98const RESOLVE_JS: &str = include_str!("js/resolve.js");
99
100pub async fn resolve(page: &Page, pattern: &LivePattern) -> Result<Resolved> {
101    let (t, v) = pattern.as_js_args();
102    let js = format!(
103        "{}({},{})",
104        RESOLVE_JS,
105        serde_json::to_string(t).unwrap(),
106        serde_json::to_string(v).unwrap()
107    );
108    page.evaluate(&js).await
109}
110
111#[cfg(test)]
112mod tests {
113    use super::*;
114
115    #[test]
116    fn parse_index() {
117        assert!(matches!(Target::parse("0"), Target::Index(0)));
118        assert!(matches!(Target::parse("15"), Target::Index(15)));
119        assert!(matches!(Target::parse("  42  "), Target::Index(42)));
120        assert!(matches!(Target::parse("[38]"), Target::Index(38)));
121        assert!(matches!(Target::parse(" [ 38 ] "), Target::Index(38)));
122        assert!(matches!(Target::parse("index:38"), Target::Index(38)));
123        assert!(matches!(Target::parse("index: 38"), Target::Index(38)));
124        assert!(matches!(Target::parse("idx:38"), Target::Index(38)));
125    }
126
127    #[test]
128    fn parse_ref() {
129        assert!(matches!(Target::parse("@e1"), Target::Ref(ref s) if s == "@e1"));
130        assert!(matches!(Target::parse("@e42"), Target::Ref(ref s) if s == "@e42"));
131        assert!(matches!(Target::parse("  @e5  "), Target::Ref(ref s) if s == "@e5"));
132        assert!(matches!(Target::parse("@foo"), Target::Ref(ref s) if s == "@foo"));
133    }
134
135    #[test]
136    fn parse_live_prefixed() {
137        assert!(matches!(
138            Target::parse("text:Submit"),
139            Target::Live(LivePattern::Text(_))
140        ));
141        assert!(matches!(
142            Target::parse("placeholder:Email"),
143            Target::Live(LivePattern::Placeholder(_))
144        ));
145        assert!(matches!(
146            Target::parse("css:button"),
147            Target::Live(LivePattern::Css(_))
148        ));
149        assert!(matches!(
150            Target::parse("id:btn"),
151            Target::Live(LivePattern::Id(_))
152        ));
153        assert!(matches!(
154            Target::parse("role:button"),
155            Target::Live(LivePattern::Role(_))
156        ));
157    }
158
159    #[test]
160    fn parse_live_unprefixed() {
161        assert!(matches!(
162            Target::parse("Submit"),
163            Target::Live(LivePattern::Text(_))
164        ));
165        assert!(matches!(
166            Target::parse("Click Me"),
167            Target::Live(LivePattern::Text(_))
168        ));
169        assert!(matches!(
170            Target::parse("[not-an-index]"),
171            Target::Live(LivePattern::Text(_))
172        ));
173        assert!(matches!(
174            Target::parse("index:abc"),
175            Target::Live(LivePattern::Text(_))
176        ));
177        assert!(matches!(
178            Target::parse("text:[38]"),
179            Target::Live(LivePattern::Text(_))
180        ));
181    }
182
183    #[test]
184    fn parse_preserves_value() {
185        if let Target::Live(LivePattern::Text(v)) = Target::parse("Submit Form") {
186            assert_eq!(v, "Submit Form");
187        } else {
188            panic!("Expected Text");
189        }
190
191        if let Target::Live(LivePattern::Css(v)) = Target::parse("css:button.primary") {
192            assert_eq!(v, "button.primary");
193        } else {
194            panic!("Expected Css");
195        }
196
197        if let Target::Live(LivePattern::Placeholder(v)) = Target::parse("placeholder:Enter email")
198        {
199            assert_eq!(v, "Enter email");
200        } else {
201            panic!("Expected Placeholder");
202        }
203    }
204
205    #[test]
206    fn as_js_args() {
207        assert_eq!(
208            LivePattern::Text("foo".into()).as_js_args(),
209            ("text", "foo")
210        );
211        assert_eq!(
212            LivePattern::Placeholder("bar".into()).as_js_args(),
213            ("placeholder", "bar")
214        );
215        assert_eq!(
216            LivePattern::Css("div.x".into()).as_js_args(),
217            ("css", "div.x")
218        );
219        assert_eq!(LivePattern::Id("myid".into()).as_js_args(), ("id", "myid"));
220        assert_eq!(
221            LivePattern::Role("button".into()).as_js_args(),
222            ("role", "button")
223        );
224    }
225}