1use eoka::{Page, Result};
4use serde::Deserialize;
5
6#[derive(Debug, Clone)]
8pub enum Target {
9 Index(usize),
11 Live(LivePattern),
13}
14
15#[derive(Debug, Clone)]
17pub enum LivePattern {
18 Text(String),
20 Placeholder(String),
22 Role(String),
24 Css(String),
26 Id(String),
28}
29
30impl Target {
31 pub fn parse(s: &str) -> Self {
33 let s = s.trim();
34
35 if let Ok(idx) = s.parse::<usize>() {
37 return Target::Index(idx);
38 }
39
40 Target::Live(LivePattern::parse(s))
42 }
43}
44
45impl LivePattern {
46 pub fn parse(s: &str) -> Self {
48 if let Some(v) = s.strip_prefix("text:") {
49 return LivePattern::Text(v.into());
50 }
51 if let Some(v) = s.strip_prefix("placeholder:") {
52 return LivePattern::Placeholder(v.into());
53 }
54 if let Some(v) = s.strip_prefix("role:") {
55 return LivePattern::Role(v.into());
56 }
57 if let Some(v) = s.strip_prefix("css:") {
58 return LivePattern::Css(v.into());
59 }
60 if let Some(v) = s.strip_prefix("id:") {
61 return LivePattern::Id(v.into());
62 }
63 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)]
80pub struct BBox {
81 pub x: f64,
82 pub y: f64,
83 pub width: f64,
84 pub height: f64,
85}
86
87#[derive(Debug, Deserialize)]
89pub struct Resolved {
90 pub selector: String,
91 pub tag: String,
92 pub text: String,
93 pub found: bool,
94 #[serde(default)]
95 pub error: Option<String>,
96 #[serde(default)]
97 pub bbox: BBox,
98}
99
100const RESOLVE_JS: &str = include_str!("js/resolve.js");
101
102pub async fn resolve(page: &Page, pattern: &LivePattern) -> Result<Resolved> {
104 let (t, v) = pattern.as_js_args();
105 let js = format!(
106 "{}({},{})",
107 RESOLVE_JS,
108 serde_json::to_string(t).unwrap(),
109 serde_json::to_string(v).unwrap()
110 );
111 page.evaluate(&js).await
112}
113
114#[cfg(test)]
115mod tests {
116 use super::*;
117
118 #[test]
119 fn parse_index() {
120 assert!(matches!(Target::parse("0"), Target::Index(0)));
121 assert!(matches!(Target::parse("15"), Target::Index(15)));
122 assert!(matches!(Target::parse(" 42 "), Target::Index(42)));
123 }
124
125 #[test]
126 fn parse_live_prefixed() {
127 assert!(matches!(
128 Target::parse("text:Submit"),
129 Target::Live(LivePattern::Text(_))
130 ));
131 assert!(matches!(
132 Target::parse("placeholder:Email"),
133 Target::Live(LivePattern::Placeholder(_))
134 ));
135 assert!(matches!(
136 Target::parse("css:button"),
137 Target::Live(LivePattern::Css(_))
138 ));
139 assert!(matches!(
140 Target::parse("id:btn"),
141 Target::Live(LivePattern::Id(_))
142 ));
143 assert!(matches!(
144 Target::parse("role:button"),
145 Target::Live(LivePattern::Role(_))
146 ));
147 }
148
149 #[test]
150 fn parse_live_unprefixed() {
151 assert!(matches!(
153 Target::parse("Submit"),
154 Target::Live(LivePattern::Text(_))
155 ));
156 assert!(matches!(
157 Target::parse("Click Me"),
158 Target::Live(LivePattern::Text(_))
159 ));
160 }
161
162 #[test]
163 fn parse_preserves_value() {
164 if let Target::Live(LivePattern::Text(v)) = Target::parse("Submit Form") {
165 assert_eq!(v, "Submit Form");
166 } else {
167 panic!("Expected Text");
168 }
169
170 if let Target::Live(LivePattern::Css(v)) = Target::parse("css:button.primary") {
171 assert_eq!(v, "button.primary");
172 } else {
173 panic!("Expected Css");
174 }
175
176 if let Target::Live(LivePattern::Placeholder(v)) = Target::parse("placeholder:Enter email")
177 {
178 assert_eq!(v, "Enter email");
179 } else {
180 panic!("Expected Placeholder");
181 }
182 }
183
184 #[test]
185 fn as_js_args() {
186 assert_eq!(
187 LivePattern::Text("foo".into()).as_js_args(),
188 ("text", "foo")
189 );
190 assert_eq!(
191 LivePattern::Placeholder("bar".into()).as_js_args(),
192 ("placeholder", "bar")
193 );
194 assert_eq!(
195 LivePattern::Css("div.x".into()).as_js_args(),
196 ("css", "div.x")
197 );
198 assert_eq!(LivePattern::Id("myid".into()).as_js_args(), ("id", "myid"));
199 assert_eq!(
200 LivePattern::Role("button".into()).as_js_args(),
201 ("role", "button")
202 );
203 }
204}