1use serde::de::{self, MapAccess, Visitor};
2use serde::{Deserialize, Deserializer};
3use std::fmt;
4
5#[derive(Debug, Clone, Deserialize, Default)]
7pub struct Target {
8 pub selector: Option<String>,
10 pub text: Option<String>,
12}
13
14impl fmt::Display for Target {
15 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
16 match (&self.selector, &self.text) {
17 (Some(s), _) => write!(f, "selector '{}'", s),
18 (_, Some(t)) => write!(f, "text '{}'", t),
19 _ => write!(f, "unknown"),
20 }
21 }
22}
23
24#[derive(Debug, Clone)]
26pub enum Action {
27 Goto(GotoAction),
29 Back,
30 Forward,
31 Reload,
32
33 Wait(WaitAction),
35 WaitForNetworkIdle(WaitForNetworkIdleAction),
36 WaitFor(WaitForAction),
37 WaitForVisible(WaitForAction),
38 WaitForHidden(WaitForAction),
39 WaitForText(WaitForTextAction),
40 WaitForUrl(WaitForUrlAction),
41
42 Click(ClickAction),
44 TryClick(TargetAction),
45 TryClickAny(TryClickAnyAction),
46
47 Fill(FillAction),
49 Type(TypeAction),
50 Clear(ClearAction),
51 Select(SelectAction),
52 PressKey(PressKeyAction),
53
54 Hover(TargetAction),
56
57 SetCookie(SetCookieAction),
59 DeleteCookie(DeleteCookieAction),
60
61 Execute(ExecuteAction),
63
64 Scroll(ScrollAction),
66 ScrollTo(TargetAction),
67
68 Screenshot(ScreenshotAction),
70 Log(LogAction),
71 AssertText(AssertTextAction),
72 AssertUrl(AssertUrlAction),
73
74 IfTextExists(IfTextExistsAction),
76 IfSelectorExists(IfSelectorExistsAction),
77 Repeat(RepeatAction),
78
79 Include(IncludeAction),
81}
82
83impl Action {
84 pub fn name(&self) -> &'static str {
86 match self {
87 Self::Goto(_) => "goto",
88 Self::Back => "back",
89 Self::Forward => "forward",
90 Self::Reload => "reload",
91 Self::Wait(_) => "wait",
92 Self::WaitForNetworkIdle(_) => "wait_for_network_idle",
93 Self::WaitFor(_) => "wait_for",
94 Self::WaitForVisible(_) => "wait_for_visible",
95 Self::WaitForHidden(_) => "wait_for_hidden",
96 Self::WaitForText(_) => "wait_for_text",
97 Self::WaitForUrl(_) => "wait_for_url",
98 Self::Click(_) => "click",
99 Self::TryClick(_) => "try_click",
100 Self::TryClickAny(_) => "try_click_any",
101 Self::Fill(_) => "fill",
102 Self::Type(_) => "type",
103 Self::Clear(_) => "clear",
104 Self::Select(_) => "select",
105 Self::PressKey(_) => "press_key",
106 Self::Hover(_) => "hover",
107 Self::SetCookie(_) => "set_cookie",
108 Self::DeleteCookie(_) => "delete_cookie",
109 Self::Execute(_) => "execute",
110 Self::Scroll(_) => "scroll",
111 Self::ScrollTo(_) => "scroll_to",
112 Self::Screenshot(_) => "screenshot",
113 Self::Log(_) => "log",
114 Self::AssertText(_) => "assert_text",
115 Self::AssertUrl(_) => "assert_url",
116 Self::IfTextExists(_) => "if_text_exists",
117 Self::IfSelectorExists(_) => "if_selector_exists",
118 Self::Repeat(_) => "repeat",
119 Self::Include(_) => "include",
120 }
121 }
122}
123
124const ACTION_NAMES: &[&str] = &[
125 "goto",
126 "back",
127 "forward",
128 "reload",
129 "wait",
130 "wait_for_network_idle",
131 "wait_for",
132 "wait_for_visible",
133 "wait_for_hidden",
134 "wait_for_text",
135 "wait_for_url",
136 "click",
137 "try_click",
138 "try_click_any",
139 "fill",
140 "type",
141 "clear",
142 "select",
143 "press_key",
144 "hover",
145 "set_cookie",
146 "delete_cookie",
147 "execute",
148 "scroll",
149 "scroll_to",
150 "screenshot",
151 "log",
152 "assert_text",
153 "assert_url",
154 "if_text_exists",
155 "if_selector_exists",
156 "repeat",
157 "include",
158];
159
160impl<'de> Deserialize<'de> for Action {
161 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
162 where
163 D: Deserializer<'de>,
164 {
165 deserializer.deserialize_any(ActionVisitor)
166 }
167}
168
169struct ActionVisitor;
170
171impl<'de> Visitor<'de> for ActionVisitor {
172 type Value = Action;
173
174 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
175 formatter.write_str("an action (string for unit variants, or map with single key)")
176 }
177
178 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
179 where
180 E: de::Error,
181 {
182 match value {
183 "back" => Ok(Action::Back),
184 "forward" => Ok(Action::Forward),
185 "reload" => Ok(Action::Reload),
186 other => Err(de::Error::unknown_variant(
187 other,
188 &["back", "forward", "reload"],
189 )),
190 }
191 }
192
193 fn visit_map<M>(self, mut map: M) -> Result<Self::Value, M::Error>
194 where
195 M: MapAccess<'de>,
196 {
197 let key: String = map
198 .next_key()?
199 .ok_or_else(|| de::Error::custom("expected action type key"))?;
200
201 let action = match key.as_str() {
202 "goto" => Action::Goto(map.next_value()?),
203 "back" => {
204 let _: serde_yaml::Value = map.next_value()?;
205 Action::Back
206 }
207 "forward" => {
208 let _: serde_yaml::Value = map.next_value()?;
209 Action::Forward
210 }
211 "reload" => {
212 let _: serde_yaml::Value = map.next_value()?;
213 Action::Reload
214 }
215 "wait" => Action::Wait(map.next_value()?),
216 "wait_for_network_idle" => Action::WaitForNetworkIdle(map.next_value()?),
217 "wait_for" => Action::WaitFor(map.next_value()?),
218 "wait_for_visible" => Action::WaitForVisible(map.next_value()?),
219 "wait_for_hidden" => Action::WaitForHidden(map.next_value()?),
220 "wait_for_text" => Action::WaitForText(map.next_value()?),
221 "wait_for_url" => Action::WaitForUrl(map.next_value()?),
222 "click" => Action::Click(map.next_value()?),
223 "try_click" => Action::TryClick(map.next_value()?),
224 "try_click_any" => Action::TryClickAny(map.next_value()?),
225 "fill" => Action::Fill(map.next_value()?),
226 "type" => Action::Type(map.next_value()?),
227 "clear" => Action::Clear(map.next_value()?),
228 "select" => Action::Select(map.next_value()?),
229 "press_key" => Action::PressKey(map.next_value()?),
230 "hover" => Action::Hover(map.next_value()?),
231 "set_cookie" => Action::SetCookie(map.next_value()?),
232 "delete_cookie" => Action::DeleteCookie(map.next_value()?),
233 "execute" => Action::Execute(map.next_value()?),
234 "scroll" => Action::Scroll(map.next_value()?),
235 "scroll_to" => Action::ScrollTo(map.next_value()?),
236 "screenshot" => Action::Screenshot(map.next_value()?),
237 "log" => Action::Log(map.next_value()?),
238 "assert_text" => Action::AssertText(map.next_value()?),
239 "assert_url" => Action::AssertUrl(map.next_value()?),
240 "if_text_exists" => Action::IfTextExists(map.next_value()?),
241 "if_selector_exists" => Action::IfSelectorExists(map.next_value()?),
242 "repeat" => Action::Repeat(map.next_value()?),
243 "include" => Action::Include(map.next_value()?),
244 other => return Err(de::Error::unknown_variant(other, ACTION_NAMES)),
245 };
246
247 Ok(action)
248 }
249}
250
251#[derive(Debug, Clone, Deserialize)]
254pub struct GotoAction {
255 pub url: String,
256}
257
258#[derive(Debug, Clone, Deserialize)]
259pub struct WaitAction {
260 pub ms: u64,
261}
262
263fn default_idle_ms() -> u64 {
264 500
265}
266fn default_timeout_ms() -> u64 {
267 10000
268}
269
270#[derive(Debug, Clone, Deserialize)]
271pub struct WaitForNetworkIdleAction {
272 #[serde(default = "default_idle_ms")]
273 pub idle_ms: u64,
274 #[serde(default = "default_timeout_ms")]
275 pub timeout_ms: u64,
276}
277
278#[derive(Debug, Clone, Deserialize)]
279pub struct WaitForAction {
280 pub selector: String,
281 #[serde(default = "default_timeout_ms")]
282 pub timeout_ms: u64,
283}
284
285#[derive(Debug, Clone, Deserialize)]
286pub struct WaitForTextAction {
287 pub text: String,
288 #[serde(default = "default_timeout_ms")]
289 pub timeout_ms: u64,
290}
291
292#[derive(Debug, Clone, Deserialize)]
293pub struct WaitForUrlAction {
294 pub contains: String,
295 #[serde(default = "default_timeout_ms")]
296 pub timeout_ms: u64,
297}
298
299#[derive(Debug, Clone, Deserialize)]
300pub struct ClickAction {
301 #[serde(flatten)]
302 pub target: Target,
303 #[serde(default)]
304 pub human: bool,
305 #[serde(default)]
306 pub scroll_into_view: bool,
307}
308
309#[derive(Debug, Clone, Deserialize)]
310pub struct TryClickAnyAction {
311 pub selectors: Option<Vec<String>>,
312 pub texts: Option<Vec<String>>,
313}
314
315#[derive(Debug, Clone, Deserialize)]
316pub struct FillAction {
317 #[serde(flatten)]
318 pub target: Target,
319 pub value: String,
320 #[serde(default)]
321 pub human: bool,
322}
323
324#[derive(Debug, Clone, Deserialize)]
325pub struct TypeAction {
326 #[serde(flatten)]
327 pub target: Target,
328 pub value: String,
329}
330
331#[derive(Debug, Clone, Deserialize)]
332pub struct ClearAction {
333 #[serde(flatten)]
334 pub target: Target,
335}
336
337#[derive(Debug, Clone, Deserialize)]
338pub struct SelectAction {
339 #[serde(flatten)]
340 pub target: Target,
341 pub value: String,
342}
343
344#[derive(Debug, Clone, Deserialize)]
345pub struct PressKeyAction {
346 pub key: String,
347}
348
349#[derive(Debug, Clone, Deserialize)]
351pub struct TargetAction {
352 #[serde(flatten)]
353 pub target: Target,
354}
355
356#[derive(Debug, Clone, Deserialize)]
357pub struct SetCookieAction {
358 pub name: String,
359 pub value: String,
360 pub domain: Option<String>,
361 pub path: Option<String>,
362}
363
364#[derive(Debug, Clone, Deserialize)]
365pub struct DeleteCookieAction {
366 pub name: String,
367 pub domain: Option<String>,
368}
369
370#[derive(Debug, Clone, Deserialize)]
371pub struct ExecuteAction {
372 pub js: String,
373}
374
375fn default_scroll_amount() -> u32 {
376 1
377}
378
379#[derive(Debug, Clone, Deserialize)]
380pub struct ScrollAction {
381 pub direction: ScrollDirection,
382 #[serde(default = "default_scroll_amount")]
383 pub amount: u32,
384}
385
386#[derive(Debug, Clone, Deserialize)]
387#[serde(rename_all = "snake_case")]
388pub enum ScrollDirection {
389 Up,
390 Down,
391 Left,
392 Right,
393}
394
395#[derive(Debug, Clone, Deserialize)]
396pub struct ScreenshotAction {
397 pub path: String,
398}
399
400#[derive(Debug, Clone, Deserialize)]
401pub struct LogAction {
402 pub message: String,
403}
404
405#[derive(Debug, Clone, Deserialize)]
406pub struct AssertTextAction {
407 pub text: String,
408}
409
410#[derive(Debug, Clone, Deserialize)]
411pub struct AssertUrlAction {
412 pub contains: String,
413}
414
415#[derive(Debug, Clone, Deserialize)]
416pub struct IfTextExistsAction {
417 pub text: String,
418 #[serde(rename = "then")]
419 pub then_actions: Vec<Action>,
420 #[serde(rename = "else", default)]
421 pub else_actions: Vec<Action>,
422}
423
424#[derive(Debug, Clone, Deserialize)]
425pub struct IfSelectorExistsAction {
426 pub selector: String,
427 #[serde(rename = "then")]
428 pub then_actions: Vec<Action>,
429 #[serde(rename = "else", default)]
430 pub else_actions: Vec<Action>,
431}
432
433#[derive(Debug, Clone, Deserialize)]
434pub struct RepeatAction {
435 pub times: u32,
436 pub actions: Vec<Action>,
437}
438
439#[derive(Debug, Clone, Deserialize)]
441pub struct IncludeAction {
442 pub path: String,
444
445 #[serde(default)]
447 pub params: std::collections::HashMap<String, String>,
448}