1use crate::error::{no_such_element, WebDriverErrorInner, WebDriverResult};
23use crate::{By, WebElement};
24use std::fmt::{Display, Formatter};
25
26async fn set_selected(element: &WebElement, select: bool) -> WebDriverResult<()> {
28    if element.is_selected().await? != select {
29        element.click().await?;
30    }
31    Ok(())
32}
33
34struct Escaped<'a>(&'a str);
35
36impl Display for Escaped<'_> {
37    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
38        for (i, substring) in self.0.split('\"').enumerate() {
39            if i != 0 {
40                f.write_str(", '\"', ")?
41            }
42            write!(f, "\"{}\"", substring)?;
43        }
44        if self.0.ends_with('\"') {
45            f.write_str(", '\"'")?;
46        }
47        Ok(())
48    }
49}
50
51pub fn escape_string(value: &str) -> String {
53    let contains_single = value.contains('\'');
54    let contains_double = value.contains('\"');
55    if contains_single && contains_double {
56        format!("concat({})", Escaped(value))
57    } else if contains_double {
58        format!("'{}'", value)
59    } else {
60        format!("\"{}\"", value)
61    }
62}
63
64fn get_longest_token(value: &str) -> &str {
66    value.split(' ').max_by_key(|x| x.len()).unwrap_or("")
67}
68
69#[derive(Debug)]
71pub struct SelectElement {
72    element: WebElement,
73    multiple: bool,
74}
75
76impl SelectElement {
77    pub async fn new(element: &WebElement) -> WebDriverResult<SelectElement> {
79        let multiple = element.attr("multiple").await?.filter(|x| x != "false").is_some();
80        let element = element.clone();
81        Ok(SelectElement {
82            element,
83            multiple,
84        })
85    }
86
87    pub async fn options(&self) -> WebDriverResult<Vec<WebElement>> {
89        self.element.find_all(By::Tag("option")).await
90    }
91
92    pub async fn all_selected_options(&self) -> WebDriverResult<Vec<WebElement>> {
94        let mut selected = Vec::new();
95        for option in self.options().await? {
96            if option.is_selected().await? {
97                selected.push(option);
98            }
99        }
100        Ok(selected)
101    }
102
103    pub async fn first_selected_option(&self) -> WebDriverResult<WebElement> {
105        for option in self.options().await? {
106            if option.is_selected().await? {
107                return Ok(option);
108            }
109        }
110        Err(no_such_element("No options are selected".to_string()))
111    }
112
113    async fn set_selection_all(&self, select: bool) -> WebDriverResult<()> {
115        for option in self.options().await? {
116            set_selected(&option, select).await?;
117        }
118        Ok(())
119    }
120
121    async fn set_selection_by_value(&self, value: &str, select: bool) -> WebDriverResult<()> {
123        let selector = format!("option[value={}]", escape_string(value));
124        let options = self.element.find_all(By::Css(&*selector)).await?;
125        for option in options {
126            set_selected(&option, select).await?;
127            if !self.multiple {
128                break;
129            }
130        }
131        Ok(())
132    }
133
134    async fn set_selection_by_index(&self, index: u32, select: bool) -> WebDriverResult<()> {
136        let selector = format!("option:nth-of-type({})", index + 1);
137        let option = self.element.find(By::Css(&*selector)).await?;
138        set_selected(&option, select).await?;
139        Ok(())
140    }
141
142    async fn set_selection_by_visible_text(&self, text: &str, select: bool) -> WebDriverResult<()> {
153        let mut xpath = format!(".//option[normalize-space(.) = {}]", escape_string(text));
154        let options = match self.element.find_all(By::XPath(&*xpath)).await {
155            Ok(elems) => elems,
156            Err(e) if matches!(*e, WebDriverErrorInner::NoSuchElement(_)) => Vec::new(),
157            Err(e) => return Err(e),
158        };
159
160        let mut matched = false;
161        for option in &options {
162            set_selected(option, select).await?;
163            if !self.multiple {
164                return Ok(());
165            }
166            matched = true;
167        }
168
169        if options.is_empty() && text.contains(' ') {
170            let substring_without_space = get_longest_token(text);
171            let candidates = if substring_without_space.is_empty() {
172                self.options().await?
173            } else {
174                xpath =
175                    format!(".//option[contains(.,{})]", escape_string(substring_without_space));
176                self.element.find_all(By::XPath(&*xpath)).await?
177            };
178            for candidate in candidates {
179                if text == candidate.text().await? {
180                    set_selected(&candidate, select).await?;
181                    if !self.multiple {
182                        return Ok(());
183                    }
184                    matched = true;
185                }
186            }
187        }
188
189        if !matched {
190            Err(no_such_element(format!("Could not locate element with visible text: {}", text)))
191        } else {
192            Ok(())
193        }
194    }
195
196    async fn set_selection_by_xpath_condition(
198        &self,
199        condition: &str,
200        select: bool,
201    ) -> WebDriverResult<()> {
202        let xpath = format!(".//option[{}]", condition);
203        let options = self.element.find_all(By::XPath(&*xpath)).await?;
204        if options.is_empty() {
205            return Err(no_such_element(format!(
206                "Could not locate element matching XPath condition: {:?}",
207                xpath
208            )));
209        }
210
211        for option in &options {
212            set_selected(option, select).await?;
213            if !self.multiple {
214                break;
215            }
216        }
217
218        Ok(())
219    }
220
221    async fn set_selection_by_exact_text(&self, text: &str, select: bool) -> WebDriverResult<()> {
223        let condition = format!("text() = {}", escape_string(text));
224        self.set_selection_by_xpath_condition(&condition, select).await
225    }
226
227    async fn set_selection_by_partial_text(&self, text: &str, select: bool) -> WebDriverResult<()> {
229        let condition = format!("contains(text(), {})", escape_string(text));
230        self.set_selection_by_xpath_condition(&condition, select).await
231    }
232
233    pub async fn select_all(&self) -> WebDriverResult<()> {
235        assert!(self.multiple, "You may only select all options of a multi-select");
236        self.set_selection_all(true).await
237    }
238
239    pub async fn select_by_value(&self, value: &str) -> WebDriverResult<()> {
241        self.set_selection_by_value(value, true).await
242    }
243
244    pub async fn select_by_index(&self, index: u32) -> WebDriverResult<()> {
247        self.set_selection_by_index(index, true).await
248    }
249
250    pub async fn select_by_visible_text(&self, text: &str) -> WebDriverResult<()> {
259        self.set_selection_by_visible_text(text, true).await
260    }
261
262    pub async fn select_by_xpath_condition(&self, condition: &str) -> WebDriverResult<()> {
272        self.set_selection_by_xpath_condition(condition, true).await
273    }
274
275    pub async fn select_by_exact_text(&self, text: &str) -> WebDriverResult<()> {
279        self.set_selection_by_exact_text(text, true).await
280    }
281
282    pub async fn select_by_partial_text(&self, text: &str) -> WebDriverResult<()> {
286        self.set_selection_by_partial_text(text, true).await
287    }
288
289    pub async fn deselect_all(&self) -> WebDriverResult<()> {
291        assert!(self.multiple, "You may only deselect all options of a multi-select");
292        self.set_selection_all(false).await
293    }
294
295    pub async fn deselect_by_value(&self, value: &str) -> WebDriverResult<()> {
297        assert!(self.multiple, "You may only deselect options of a multi-select");
298        self.set_selection_by_value(value, false).await
299    }
300
301    pub async fn deselect_by_index(&self, index: u32) -> WebDriverResult<()> {
304        assert!(self.multiple, "You may only deselect options of a multi-select");
305        self.set_selection_by_index(index, false).await
306    }
307
308    pub async fn deselect_by_visible_text(&self, text: &str) -> WebDriverResult<()> {
315        assert!(self.multiple, "You may only deselect options of a multi-select");
316        self.set_selection_by_visible_text(text, false).await
317    }
318
319    pub async fn deselect_by_xpath_condition(&self, condition: &str) -> WebDriverResult<()> {
329        self.set_selection_by_xpath_condition(condition, false).await
330    }
331
332    pub async fn deselect_by_exact_text(&self, text: &str) -> WebDriverResult<()> {
334        assert!(self.multiple, "You may only deselect options of a multi-select");
335        self.set_selection_by_exact_text(text, false).await
336    }
337
338    pub async fn deselect_by_partial_text(&self, text: &str) -> WebDriverResult<()> {
340        assert!(self.multiple, "You may only deselect options of a multi-select");
341        self.set_selection_by_partial_text(text, false).await
342    }
343}