select.py 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  1. # Licensed to the Software Freedom Conservancy (SFC) under one
  2. # or more contributor license agreements. See the NOTICE file
  3. # distributed with this work for additional information
  4. # regarding copyright ownership. The SFC licenses this file
  5. # to you under the Apache License, Version 2.0 (the
  6. # "License"); you may not use this file except in compliance
  7. # with the License. You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing,
  12. # software distributed under the License is distributed on an
  13. # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  14. # KIND, either express or implied. See the License for the
  15. # specific language governing permissions and limitations
  16. # under the License.
  17. from selenium.webdriver.common.by import By
  18. from selenium.common.exceptions import NoSuchElementException, UnexpectedTagNameException
  19. class Select(object):
  20. def __init__(self, webelement):
  21. """
  22. Constructor. A check is made that the given element is, indeed, a SELECT tag. If it is not,
  23. then an UnexpectedTagNameException is thrown.
  24. :Args:
  25. - webelement - element SELECT element to wrap
  26. Example:
  27. from selenium.webdriver.support.ui import Select \n
  28. Select(driver.find_element_by_tag_name("select")).select_by_index(2)
  29. """
  30. if webelement.tag_name.lower() != "select":
  31. raise UnexpectedTagNameException(
  32. "Select only works on <select> elements, not on <%s>" %
  33. webelement.tag_name)
  34. self._el = webelement
  35. multi = self._el.get_attribute("multiple")
  36. self.is_multiple = multi and multi != "false"
  37. @property
  38. def options(self):
  39. """Returns a list of all options belonging to this select tag"""
  40. return self._el.find_elements(By.TAG_NAME, 'option')
  41. @property
  42. def all_selected_options(self):
  43. """Returns a list of all selected options belonging to this select tag"""
  44. ret = []
  45. for opt in self.options:
  46. if opt.is_selected():
  47. ret.append(opt)
  48. return ret
  49. @property
  50. def first_selected_option(self):
  51. """The first selected option in this select tag (or the currently selected option in a
  52. normal select)"""
  53. for opt in self.options:
  54. if opt.is_selected():
  55. return opt
  56. raise NoSuchElementException("No options are selected")
  57. def select_by_value(self, value):
  58. """Select all options that have a value matching the argument. That is, when given "foo" this
  59. would select an option like:
  60. <option value="foo">Bar</option>
  61. :Args:
  62. - value - The value to match against
  63. throws NoSuchElementException If there is no option with specisied value in SELECT
  64. """
  65. css = "option[value =%s]" % self._escapeString(value)
  66. opts = self._el.find_elements(By.CSS_SELECTOR, css)
  67. matched = False
  68. for opt in opts:
  69. self._setSelected(opt)
  70. if not self.is_multiple:
  71. return
  72. matched = True
  73. if not matched:
  74. raise NoSuchElementException("Cannot locate option with value: %s" % value)
  75. def select_by_index(self, index):
  76. """Select the option at the given index. This is done by examing the "index" attribute of an
  77. element, and not merely by counting.
  78. :Args:
  79. - index - The option at this index will be selected
  80. throws NoSuchElementException If there is no option with specisied index in SELECT
  81. """
  82. match = str(index)
  83. for opt in self.options:
  84. if opt.get_attribute("index") == match:
  85. self._setSelected(opt)
  86. return
  87. raise NoSuchElementException("Could not locate element with index %d" % index)
  88. def select_by_visible_text(self, text):
  89. """Select all options that display text matching the argument. That is, when given "Bar" this
  90. would select an option like:
  91. <option value="foo">Bar</option>
  92. :Args:
  93. - text - The visible text to match against
  94. throws NoSuchElementException If there is no option with specisied text in SELECT
  95. """
  96. xpath = ".//option[normalize-space(.) = %s]" % self._escapeString(text)
  97. opts = self._el.find_elements(By.XPATH, xpath)
  98. matched = False
  99. for opt in opts:
  100. self._setSelected(opt)
  101. if not self.is_multiple:
  102. return
  103. matched = True
  104. if len(opts) == 0 and " " in text:
  105. subStringWithoutSpace = self._get_longest_token(text)
  106. if subStringWithoutSpace == "":
  107. candidates = self.options
  108. else:
  109. xpath = ".//option[contains(.,%s)]" % self._escapeString(subStringWithoutSpace)
  110. candidates = self._el.find_elements(By.XPATH, xpath)
  111. for candidate in candidates:
  112. if text == candidate.text:
  113. self._setSelected(candidate)
  114. if not self.is_multiple:
  115. return
  116. matched = True
  117. if not matched:
  118. raise NoSuchElementException("Could not locate element with visible text: %s" % text)
  119. def deselect_all(self):
  120. """Clear all selected entries. This is only valid when the SELECT supports multiple selections.
  121. throws NotImplementedError If the SELECT does not support multiple selections
  122. """
  123. if not self.is_multiple:
  124. raise NotImplementedError("You may only deselect all options of a multi-select")
  125. for opt in self.options:
  126. self._unsetSelected(opt)
  127. def deselect_by_value(self, value):
  128. """Deselect all options that have a value matching the argument. That is, when given "foo" this
  129. would deselect an option like:
  130. <option value="foo">Bar</option>
  131. :Args:
  132. - value - The value to match against
  133. throws NoSuchElementException If there is no option with specisied value in SELECT
  134. """
  135. if not self.is_multiple:
  136. raise NotImplementedError("You may only deselect options of a multi-select")
  137. matched = False
  138. css = "option[value = %s]" % self._escapeString(value)
  139. opts = self._el.find_elements(By.CSS_SELECTOR, css)
  140. for opt in opts:
  141. self._unsetSelected(opt)
  142. matched = True
  143. if not matched:
  144. raise NoSuchElementException("Could not locate element with value: %s" % value)
  145. def deselect_by_index(self, index):
  146. """Deselect the option at the given index. This is done by examing the "index" attribute of an
  147. element, and not merely by counting.
  148. :Args:
  149. - index - The option at this index will be deselected
  150. throws NoSuchElementException If there is no option with specisied index in SELECT
  151. """
  152. if not self.is_multiple:
  153. raise NotImplementedError("You may only deselect options of a multi-select")
  154. for opt in self.options:
  155. if opt.get_attribute("index") == str(index):
  156. self._unsetSelected(opt)
  157. return
  158. raise NoSuchElementException("Could not locate element with index %d" % index)
  159. def deselect_by_visible_text(self, text):
  160. """Deselect all options that display text matching the argument. That is, when given "Bar" this
  161. would deselect an option like:
  162. <option value="foo">Bar</option>
  163. :Args:
  164. - text - The visible text to match against
  165. """
  166. if not self.is_multiple:
  167. raise NotImplementedError("You may only deselect options of a multi-select")
  168. matched = False
  169. xpath = ".//option[normalize-space(.) = %s]" % self._escapeString(text)
  170. opts = self._el.find_elements(By.XPATH, xpath)
  171. for opt in opts:
  172. self._unsetSelected(opt)
  173. matched = True
  174. if not matched:
  175. raise NoSuchElementException("Could not locate element with visible text: %s" % text)
  176. def _setSelected(self, option):
  177. if not option.is_selected():
  178. option.click()
  179. def _unsetSelected(self, option):
  180. if option.is_selected():
  181. option.click()
  182. def _escapeString(self, value):
  183. if '"' in value and "'" in value:
  184. substrings = value.split("\"")
  185. result = ["concat("]
  186. for substring in substrings:
  187. result.append("\"%s\"" % substring)
  188. result.append(", '\"', ")
  189. result = result[0:-1]
  190. if value.endswith('"'):
  191. result.append(", '\"'")
  192. return "".join(result) + ")"
  193. if '"' in value:
  194. return "'%s'" % value
  195. return "\"%s\"" % value
  196. def _get_longest_token(self, value):
  197. items = value.split(" ")
  198. longest = ""
  199. for item in items:
  200. if len(item) > len(longest):
  201. longest = item
  202. return longest