1use scout_parser::ast::{CallLiteral, ExprKind, Identifier, Program, StmtKind};
2use serde::Deserialize;
3
4#[derive(Debug, Deserialize)]
7pub struct ScoutJSON {
8 steps: Vec<Step>,
9}
10
11#[derive(Debug, Deserialize)]
12#[serde(tag = "type")]
13#[serde(rename_all = "camelCase")]
14pub enum Step {
15 SetViewport {
16 width: u32,
17 height: u32,
18 },
19 Navigate {
20 url: String,
21 },
22 Click {
23 selectors: Vec<Vec<String>>,
24 },
25 Change {
26 value: String,
27 selectors: Vec<Vec<String>>,
28 },
29}
30
31impl ScoutJSON {
32 pub fn to_ast(&self) -> Program {
33 let mut stmts = Vec::new();
34 for step in &self.steps {
35 stmts.push(step.to_stmt());
36 }
37
38 Program { stmts }
39 }
40}
41
42impl Step {
43 pub fn to_stmt(&self) -> StmtKind {
44 use Step::*;
45 match self {
46 SetViewport { width, height } => {
47 let lit = CallLiteral {
48 ident: Identifier::new("setViewport".to_string()),
49 args: vec![
50 ExprKind::Number(*width as f64),
51 ExprKind::Number(*height as f64),
52 ],
53 kwargs: Vec::new(),
54 };
55 StmtKind::Expr(ExprKind::Call(lit))
56 }
57 Navigate { url } => StmtKind::Goto(ExprKind::Str(url.clone())),
58 Click { selectors } => {
59 let elem = ExprKind::Select(selector_from_recorder_mtx(selectors.as_ref()), None);
60 let lit = CallLiteral {
61 ident: Identifier::new("click".to_string()),
62 args: vec![elem],
63 kwargs: Vec::new(),
64 };
65 StmtKind::Expr(ExprKind::Call(lit))
66 }
67 Change { value, selectors } => {
68 let elem = ExprKind::Select(selector_from_recorder_mtx(selectors.as_ref()), None);
69 let val = ExprKind::Str(value.clone());
70 let lit = CallLiteral {
71 ident: Identifier::new("input".to_string()),
72 args: vec![elem, val],
73 kwargs: Vec::new(),
74 };
75 StmtKind::Expr(ExprKind::Call(lit))
76 }
77 }
78 }
79}
80
81fn selector_from_recorder_mtx(mtx: &[Vec<String>]) -> String {
82 mtx[0][0].clone()
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98 use test_case::test_case;
99
100 #[test_case(
101 r#"{
102 "type": "navigate",
103 "url": "https://stackoverflow.com/",
104 "assertedEvents": [
105 {
106 "type": "navigation",
107 "url": "https://stackoverflow.com/",
108 "title": ""
109 }
110 ]
111 }"#,
112 StmtKind::Goto(ExprKind::Str("https://stackoverflow.com/".to_string()));
113 "navigate step"
114 )]
115 #[test_case(
116 r##"{
117 "type": "click",
118 "target": "main",
119 "selectors": [
120 [
121 "#question-summary-78853169 h3 > a"
122 ]
123 ],
124 "offsetY": 2.875,
125 "offsetX": 183,
126 "assertedEvents": [
127 {
128 "type": "navigation",
129 "url": "https://stackoverflow.com/questions/78853169/how-can-i-pass-variables-to-svelte-through-csv",
130 "title": "typescript - How can I pass variables to svelte through CSV - Stack Overflow"
131 }
132 ]
133 }"##,
134 StmtKind::Expr(ExprKind::Call(CallLiteral {
135 ident: Identifier::new("click".to_string()),
136 args: vec![ExprKind::Select("#question-summary-78853169 h3 > a".to_string(), None)],
137 kwargs: Vec::new(),
138 }));
139 "click step"
140 )]
141 #[test_case(
142 r#"{
143 "type": "setViewport",
144 "width": 1365,
145 "height": 945,
146 "deviceScaleFactor": 1,
147 "isMobile": false,
148 "hasTouch": false,
149 "isLandscape": false
150 }"#,
151 StmtKind::Expr(ExprKind::Call(CallLiteral {
152 ident: Identifier::new("setViewport".to_string()),
153 args: vec![
154 ExprKind::Number(1365.),
155 ExprKind::Number(945.),
156 ],
157 kwargs: Vec::new(),
158 }));
159 "setViewport step"
160 )]
161 fn parse_step_json(input: &str, exp: StmtKind) {
162 assert_eq!(exp, serde_json::from_str::<Step>(input).unwrap().to_stmt())
163 }
164}