242 lines
9.0 KiB
Python
242 lines
9.0 KiB
Python
|
# 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 selenium.webdriver.common.by import By
|
||
|
from selenium.common.exceptions import NoSuchElementException, UnexpectedTagNameException
|
||
|
|
||
|
|
||
|
class Select:
|
||
|
|
||
|
def __init__(self, webelement):
|
||
|
"""
|
||
|
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 - element 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(
|
||
|
"Select only works on <select> elements, not on <%s>" %
|
||
|
webelement.tag_name)
|
||
|
self._el = webelement
|
||
|
multi = self._el.get_attribute("multiple")
|
||
|
self.is_multiple = multi and multi != "false"
|
||
|
|
||
|
@property
|
||
|
def options(self):
|
||
|
"""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):
|
||
|
"""Returns a list of all selected options belonging to this select tag"""
|
||
|
ret = []
|
||
|
for opt in self.options:
|
||
|
if opt.is_selected():
|
||
|
ret.append(opt)
|
||
|
return ret
|
||
|
|
||
|
@property
|
||
|
def first_selected_option(self):
|
||
|
"""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")
|
||
|
|
||
|
def select_by_value(self, value):
|
||
|
"""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 specisied value in SELECT
|
||
|
"""
|
||
|
css = "option[value =%s]" % self._escapeString(value)
|
||
|
opts = self._el.find_elements(By.CSS_SELECTOR, css)
|
||
|
matched = False
|
||
|
for opt in opts:
|
||
|
self._setSelected(opt)
|
||
|
if not self.is_multiple:
|
||
|
return
|
||
|
matched = True
|
||
|
if not matched:
|
||
|
raise NoSuchElementException("Cannot locate option with value: %s" % value)
|
||
|
|
||
|
def select_by_index(self, index):
|
||
|
"""Select the option at the given index. This is done by examing 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 specisied index in SELECT
|
||
|
"""
|
||
|
match = str(index)
|
||
|
for opt in self.options:
|
||
|
if opt.get_attribute("index") == match:
|
||
|
self._setSelected(opt)
|
||
|
return
|
||
|
raise NoSuchElementException("Could not locate element with index %d" % index)
|
||
|
|
||
|
def select_by_visible_text(self, text):
|
||
|
"""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 specisied text in SELECT
|
||
|
"""
|
||
|
xpath = ".//option[normalize-space(.) = %s]" % self._escapeString(text)
|
||
|
opts = self._el.find_elements(By.XPATH, xpath)
|
||
|
matched = False
|
||
|
for opt in opts:
|
||
|
self._setSelected(opt)
|
||
|
if not self.is_multiple:
|
||
|
return
|
||
|
matched = True
|
||
|
|
||
|
if len(opts) == 0 and " " in text:
|
||
|
subStringWithoutSpace = self._get_longest_token(text)
|
||
|
if subStringWithoutSpace == "":
|
||
|
candidates = self.options
|
||
|
else:
|
||
|
xpath = ".//option[contains(.,%s)]" % self._escapeString(subStringWithoutSpace)
|
||
|
candidates = self._el.find_elements(By.XPATH, xpath)
|
||
|
for candidate in candidates:
|
||
|
if text == candidate.text:
|
||
|
self._setSelected(candidate)
|
||
|
if not self.is_multiple:
|
||
|
return
|
||
|
matched = True
|
||
|
|
||
|
if not matched:
|
||
|
raise NoSuchElementException("Could not locate element with visible text: %s" % text)
|
||
|
|
||
|
def deselect_all(self):
|
||
|
"""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._unsetSelected(opt)
|
||
|
|
||
|
def deselect_by_value(self, value):
|
||
|
"""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 specisied value in SELECT
|
||
|
"""
|
||
|
if not self.is_multiple:
|
||
|
raise NotImplementedError("You may only deselect options of a multi-select")
|
||
|
matched = False
|
||
|
css = "option[value = %s]" % self._escapeString(value)
|
||
|
opts = self._el.find_elements(By.CSS_SELECTOR, css)
|
||
|
for opt in opts:
|
||
|
self._unsetSelected(opt)
|
||
|
matched = True
|
||
|
if not matched:
|
||
|
raise NoSuchElementException("Could not locate element with value: %s" % value)
|
||
|
|
||
|
def deselect_by_index(self, index):
|
||
|
"""Deselect the option at the given index. This is done by examing 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 specisied 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._unsetSelected(opt)
|
||
|
return
|
||
|
raise NoSuchElementException("Could not locate element with index %d" % index)
|
||
|
|
||
|
def deselect_by_visible_text(self, text):
|
||
|
"""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 = ".//option[normalize-space(.) = %s]" % self._escapeString(text)
|
||
|
opts = self._el.find_elements(By.XPATH, xpath)
|
||
|
for opt in opts:
|
||
|
self._unsetSelected(opt)
|
||
|
matched = True
|
||
|
if not matched:
|
||
|
raise NoSuchElementException("Could not locate element with visible text: %s" % text)
|
||
|
|
||
|
def _setSelected(self, option):
|
||
|
if not option.is_selected():
|
||
|
option.click()
|
||
|
|
||
|
def _unsetSelected(self, option):
|
||
|
if option.is_selected():
|
||
|
option.click()
|
||
|
|
||
|
def _escapeString(self, value):
|
||
|
if '"' in value and "'" in value:
|
||
|
substrings = value.split("\"")
|
||
|
result = ["concat("]
|
||
|
for substring in substrings:
|
||
|
result.append("\"%s\"" % substring)
|
||
|
result.append(", '\"', ")
|
||
|
result = result[0:-1]
|
||
|
if value.endswith('"'):
|
||
|
result.append(", '\"'")
|
||
|
return "".join(result) + ")"
|
||
|
|
||
|
if '"' in value:
|
||
|
return "'%s'" % value
|
||
|
|
||
|
return "\"%s\"" % value
|
||
|
|
||
|
def _get_longest_token(self, value):
|
||
|
items = value.split(" ")
|
||
|
longest = ""
|
||
|
for item in items:
|
||
|
if len(item) > len(longest):
|
||
|
longest = item
|
||
|
return longest
|