Source code for selenium.webdriver.support.select

# Licensed to the Software Freedom Conservancy (SFC) under one
# or more contributor license agreements.  See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership.  The SFC licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License.  You may obtain a copy of the License at
#
#   http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied.  See the License for the
# specific language governing permissions and limitations
# under the License.

from typing import List

from selenium.common.exceptions import NoSuchElementException
from selenium.common.exceptions import UnexpectedTagNameException
from selenium.webdriver.common.by import By
from selenium.webdriver.remote.webelement import WebElement


[docs]class Select: def __init__(self, webelement: WebElement) -> None: """Constructor. A check is made that the given element is, indeed, a SELECT tag. If it is not, then an UnexpectedTagNameException is thrown. :Args: - webelement - SELECT element to wrap Example: from selenium.webdriver.support.ui import Select \n Select(driver.find_element(By.TAG_NAME, "select")).select_by_index(2) """ if webelement.tag_name.lower() != "select": raise UnexpectedTagNameException(f"Select only works on <select> elements, not on {webelement.tag_name}") self._el = webelement multi = self._el.get_dom_attribute("multiple") self.is_multiple = multi and multi != "false" @property def options(self) -> List[WebElement]: """Returns a list of all options belonging to this select tag.""" return self._el.find_elements(By.TAG_NAME, "option") @property def all_selected_options(self) -> List[WebElement]: """Returns a list of all selected options belonging to this select tag.""" return [opt for opt in self.options if opt.is_selected()] @property def first_selected_option(self) -> WebElement: """The first selected option in this select tag (or the currently selected option in a normal select)""" for opt in self.options: if opt.is_selected(): return opt raise NoSuchElementException("No options are selected")
[docs] def select_by_value(self, value: str) -> None: """Select all options that have a value matching the argument. That is, when given "foo" this would select an option like: <option value="foo">Bar</option> :Args: - value - The value to match against throws NoSuchElementException If there is no option with specified value in SELECT """ css = f"option[value ={self._escape_string(value)}]" opts = self._el.find_elements(By.CSS_SELECTOR, css) matched = False for opt in opts: self._set_selected(opt) if not self.is_multiple: return matched = True if not matched: raise NoSuchElementException(f"Cannot locate option with value: {value}")
[docs] def select_by_index(self, index: int) -> None: """Select the option at the given index. This is done by examining the "index" attribute of an element, and not merely by counting. :Args: - index - The option at this index will be selected throws NoSuchElementException If there is no option with specified index in SELECT """ match = str(index) for opt in self.options: if opt.get_attribute("index") == match: self._set_selected(opt) return raise NoSuchElementException(f"Could not locate element with index {index}")
[docs] def select_by_visible_text(self, text: str) -> None: """Select all options that display text matching the argument. That is, when given "Bar" this would select an option like: <option value="foo">Bar</option> :Args: - text - The visible text to match against throws NoSuchElementException If there is no option with specified text in SELECT """ xpath = f".//option[normalize-space(.) = {self._escape_string(text)}]" opts = self._el.find_elements(By.XPATH, xpath) matched = False for opt in opts: self._set_selected(opt) if not self.is_multiple: return matched = True if len(opts) == 0 and " " in text: sub_string_without_space = self._get_longest_token(text) if sub_string_without_space == "": candidates = self.options else: xpath = f".//option[contains(.,{self._escape_string(sub_string_without_space)})]" candidates = self._el.find_elements(By.XPATH, xpath) for candidate in candidates: if text == candidate.text: self._set_selected(candidate) if not self.is_multiple: return matched = True if not matched: raise NoSuchElementException(f"Could not locate element with visible text: {text}")
[docs] def deselect_all(self) -> None: """Clear all selected entries. This is only valid when the SELECT supports multiple selections. throws NotImplementedError If the SELECT does not support multiple selections """ if not self.is_multiple: raise NotImplementedError("You may only deselect all options of a multi-select") for opt in self.options: self._unset_selected(opt)
[docs] def deselect_by_value(self, value: str) -> None: """Deselect all options that have a value matching the argument. That is, when given "foo" this would deselect an option like: <option value="foo">Bar</option> :Args: - value - The value to match against throws NoSuchElementException If there is no option with specified value in SELECT """ if not self.is_multiple: raise NotImplementedError("You may only deselect options of a multi-select") matched = False css = f"option[value = {self._escape_string(value)}]" opts = self._el.find_elements(By.CSS_SELECTOR, css) for opt in opts: self._unset_selected(opt) matched = True if not matched: raise NoSuchElementException(f"Could not locate element with value: {value}")
[docs] def deselect_by_index(self, index: int) -> None: """Deselect the option at the given index. This is done by examining the "index" attribute of an element, and not merely by counting. :Args: - index - The option at this index will be deselected throws NoSuchElementException If there is no option with specified index in SELECT """ if not self.is_multiple: raise NotImplementedError("You may only deselect options of a multi-select") for opt in self.options: if opt.get_attribute("index") == str(index): self._unset_selected(opt) return raise NoSuchElementException(f"Could not locate element with index {index}")
[docs] def deselect_by_visible_text(self, text: str) -> None: """Deselect all options that display text matching the argument. That is, when given "Bar" this would deselect an option like: <option value="foo">Bar</option> :Args: - text - The visible text to match against """ if not self.is_multiple: raise NotImplementedError("You may only deselect options of a multi-select") matched = False xpath = f".//option[normalize-space(.) = {self._escape_string(text)}]" opts = self._el.find_elements(By.XPATH, xpath) for opt in opts: self._unset_selected(opt) matched = True if not matched: raise NoSuchElementException(f"Could not locate element with visible text: {text}")
def _set_selected(self, option) -> None: if not option.is_selected(): if not option.is_enabled(): raise NotImplementedError("You may not select a disabled option") option.click() def _unset_selected(self, option) -> None: if option.is_selected(): option.click() def _escape_string(self, value: str) -> str: if '"' in value and "'" in value: substrings = value.split('"') result = ["concat("] for substring in substrings: result.append(f'"{substring}"') result.append(", '\"', ") result = result[0:-1] if value.endswith('"'): result.append(", '\"'") return "".join(result) + ")" if '"' in value: return f"'{value}'" return f'"{value}"' def _get_longest_token(self, value: str) -> str: items = value.split(" ") longest = "" for item in items: if len(item) > len(longest): longest = item return longest