Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
SeleniumHQ
GitHub Repository: SeleniumHQ/Selenium
Path: blob/trunk/py/selenium/webdriver/support/select.py
3991 views
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
18
19
from selenium.common.exceptions import NoSuchElementException, UnexpectedTagNameException
20
from selenium.webdriver.common.by import By
21
from selenium.webdriver.remote.webelement import WebElement
22
23
24
class Select:
25
def __init__(self, webelement: WebElement) -> None:
26
"""Constructor. A check is made that the given element is a SELECT tag.
27
28
Args:
29
webelement: SELECT element to wrap
30
31
Example:
32
from selenium.webdriver.support.ui import Select
33
Select(driver.find_element(By.TAG_NAME, "select")).select_by_index(2)
34
35
Raises:
36
UnexpectedTagNameException: If the element is not a SELECT tag
37
"""
38
if webelement.tag_name.lower() != "select":
39
raise UnexpectedTagNameException(f"Select only works on <select> elements, not on {webelement.tag_name}")
40
self._el = webelement
41
multi = self._el.get_dom_attribute("multiple")
42
self.is_multiple = multi and multi != "false"
43
44
@property
45
def options(self) -> list[WebElement]:
46
"""Returns a list of all options belonging to this select tag."""
47
return self._el.find_elements(By.TAG_NAME, "option")
48
49
@property
50
def all_selected_options(self) -> list[WebElement]:
51
"""Return a list of all selected options belonging to this select tag."""
52
return [opt for opt in self.options if opt.is_selected()]
53
54
@property
55
def first_selected_option(self) -> WebElement:
56
"""Return the first selected option or the currently selected option."""
57
for opt in self.options:
58
if opt.is_selected():
59
return opt
60
raise NoSuchElementException("No options are selected")
61
62
def select_by_value(self, value: str) -> None:
63
"""Select all options that have a value matching the argument.
64
65
Example:
66
When given "foo" this would select an option like:
67
68
`<option value="foo">Bar</option>`
69
70
Args:
71
value: The value to match against
72
73
Raises:
74
NoSuchElementException: If there is no option with specified value in SELECT
75
"""
76
css = f"option[value ={self._escape_string(value)}]"
77
opts = self._el.find_elements(By.CSS_SELECTOR, css)
78
matched = False
79
for opt in opts:
80
self._set_selected(opt)
81
if not self.is_multiple:
82
return
83
matched = True
84
if not matched:
85
raise NoSuchElementException(f"Cannot locate option with value: {value}")
86
87
def select_by_index(self, index: int) -> None:
88
"""Select the option at the given index by examining the "index" attribute.
89
90
Args:
91
index: The option at this index will be selected
92
93
Raises:
94
NoSuchElementException: If there is no option with specified index in SELECT
95
"""
96
match = str(index)
97
for opt in self.options:
98
if opt.get_attribute("index") == match:
99
self._set_selected(opt)
100
return
101
raise NoSuchElementException(f"Could not locate element with index {index}")
102
103
def select_by_visible_text(self, text: str) -> None:
104
"""Select all options that display text matching the argument.
105
106
Example:
107
When given "Bar" this would select an option like:
108
109
`<option value="foo">Bar</option>`
110
111
Args:
112
text: The visible text to match against
113
114
Raises:
115
NoSuchElementException: If there is no option with specified text in SELECT
116
"""
117
xpath = f".//option[normalize-space(.) = {self._escape_string(text)}]"
118
opts = self._el.find_elements(By.XPATH, xpath)
119
matched = False
120
for opt in opts:
121
if not self._has_css_property_and_visible(opt):
122
raise NoSuchElementException(f"Invisible option with text: {text}")
123
self._set_selected(opt)
124
if not self.is_multiple:
125
return
126
matched = True
127
128
if len(opts) == 0 and " " in text:
129
sub_string_without_space = self._get_longest_token(text)
130
if sub_string_without_space == "":
131
candidates = self.options
132
else:
133
xpath = f".//option[contains(.,{self._escape_string(sub_string_without_space)})]"
134
candidates = self._el.find_elements(By.XPATH, xpath)
135
for candidate in candidates:
136
if text == candidate.text:
137
if not self._has_css_property_and_visible(candidate):
138
raise NoSuchElementException(f"Invisible option with text: {text}")
139
self._set_selected(candidate)
140
if not self.is_multiple:
141
return
142
matched = True
143
144
if not matched:
145
raise NoSuchElementException(f"Could not locate element with visible text: {text}")
146
147
def deselect_all(self) -> None:
148
"""Clear all selected entries.
149
150
This is only valid when the SELECT supports multiple selections.
151
throws NotImplementedError If the SELECT does not support
152
multiple selections
153
"""
154
if not self.is_multiple:
155
raise NotImplementedError("You may only deselect all options of a multi-select")
156
for opt in self.options:
157
self._unset_selected(opt)
158
159
def deselect_by_value(self, value: str) -> None:
160
"""Deselect all options that have a value matching the argument.
161
162
Example:
163
When given "foo" this would deselect an option like:
164
165
`<option value="foo">Bar</option>`
166
167
Args:
168
value: The value to match against
169
170
Raises:
171
NoSuchElementException: If there is no option with specified value in SELECT
172
"""
173
if not self.is_multiple:
174
raise NotImplementedError("You may only deselect options of a multi-select")
175
matched = False
176
css = f"option[value = {self._escape_string(value)}]"
177
opts = self._el.find_elements(By.CSS_SELECTOR, css)
178
for opt in opts:
179
self._unset_selected(opt)
180
matched = True
181
if not matched:
182
raise NoSuchElementException(f"Could not locate element with value: {value}")
183
184
def deselect_by_index(self, index: int) -> None:
185
"""Deselect the option at the given index by examining the "index" attribute.
186
187
Args:
188
index: The option at this index will be deselected
189
190
Raises:
191
NoSuchElementException: If there is no option with specified index in SELECT
192
"""
193
if not self.is_multiple:
194
raise NotImplementedError("You may only deselect options of a multi-select")
195
for opt in self.options:
196
if opt.get_attribute("index") == str(index):
197
self._unset_selected(opt)
198
return
199
raise NoSuchElementException(f"Could not locate element with index {index}")
200
201
def deselect_by_visible_text(self, text: str) -> None:
202
"""Deselect all options that display text matching the argument.
203
204
Example:
205
when given "Bar" this would deselect an option like:
206
207
`<option value="foo">Bar</option>`
208
209
Args:
210
text: The visible text to match against
211
"""
212
if not self.is_multiple:
213
raise NotImplementedError("You may only deselect options of a multi-select")
214
matched = False
215
xpath = f".//option[normalize-space(.) = {self._escape_string(text)}]"
216
opts = self._el.find_elements(By.XPATH, xpath)
217
for opt in opts:
218
if not self._has_css_property_and_visible(opt):
219
raise NoSuchElementException(f"Invisible option with text: {text}")
220
self._unset_selected(opt)
221
matched = True
222
if not matched:
223
raise NoSuchElementException(f"Could not locate element with visible text: {text}")
224
225
def _set_selected(self, option) -> None:
226
if not option.is_selected():
227
if not option.is_enabled():
228
raise NotImplementedError("You may not select a disabled option")
229
option.click()
230
231
def _unset_selected(self, option) -> None:
232
if option.is_selected():
233
option.click()
234
235
def _escape_string(self, value: str) -> str:
236
if '"' in value and "'" in value:
237
substrings = value.split('"')
238
result = ["concat("]
239
for substring in substrings:
240
result.append(f'"{substring}"')
241
result.append(", '\"', ")
242
result = result[0:-1]
243
if value.endswith('"'):
244
result.append(", '\"'")
245
return "".join(result) + ")"
246
247
if '"' in value:
248
return f"'{value}'"
249
250
return f'"{value}"'
251
252
def _get_longest_token(self, value: str) -> str:
253
items = value.split(" ")
254
longest = ""
255
for item in items:
256
if len(item) > len(longest):
257
longest = item
258
return longest
259
260
def _has_css_property_and_visible(self, option) -> bool:
261
css_value_candidates = ["hidden", "none", "0", "0.0"]
262
css_property_candidates = ["visibility", "display", "opacity"]
263
264
for css_property in css_property_candidates:
265
css_value = option.value_of_css_property(css_property)
266
if css_value in css_value_candidates:
267
return False
268
return True
269
270