Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
SeleniumHQ
GitHub Repository: SeleniumHQ/Selenium
Path: blob/trunk/py/selenium/webdriver/remote/webelement.py
3985 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
from __future__ import annotations
19
20
import os
21
import pkgutil
22
import warnings
23
import zipfile
24
from abc import ABCMeta
25
from base64 import b64decode, encodebytes
26
from hashlib import md5 as md5_hash
27
from io import BytesIO
28
29
from selenium.common.exceptions import JavascriptException, WebDriverException
30
from selenium.webdriver.common.by import By
31
from selenium.webdriver.common.utils import keys_to_typing
32
from selenium.webdriver.remote.command import Command
33
from selenium.webdriver.remote.shadowroot import ShadowRoot
34
35
# TODO: Use built in importlib_resources.files.
36
getAttribute_js = None
37
isDisplayed_js = None
38
39
40
def _load_js():
41
global getAttribute_js
42
global isDisplayed_js
43
_pkg = ".".join(__name__.split(".")[:-1])
44
getAttribute_js = pkgutil.get_data(_pkg, "getAttribute.js").decode("utf8")
45
isDisplayed_js = pkgutil.get_data(_pkg, "isDisplayed.js").decode("utf8")
46
47
48
class BaseWebElement(metaclass=ABCMeta):
49
"""Abstract Base Class for WebElement.
50
51
ABC's will allow custom types to be registered as a WebElement to
52
pass type checks.
53
"""
54
55
pass
56
57
58
class WebElement(BaseWebElement):
59
"""Represents a DOM element.
60
61
Generally, all interesting operations that interact with a document will be
62
performed through this interface.
63
64
All method calls will do a freshness check to ensure that the element
65
reference is still valid. This essentially determines whether the
66
element is still attached to the DOM. If this test fails, then an
67
`StaleElementReferenceException` is thrown, and all future calls to this
68
instance will fail.
69
"""
70
71
def __init__(self, parent, id_) -> None:
72
self._parent = parent
73
self._id = id_
74
75
def __repr__(self):
76
return f'<{type(self).__module__}.{type(self).__name__} (session="{self.session_id}", element="{self._id}")>'
77
78
@property
79
def session_id(self) -> str:
80
return self._parent.session_id
81
82
@property
83
def tag_name(self) -> str:
84
"""This element's `tagName` property.
85
86
Returns:
87
The tag name of the element.
88
89
Example:
90
element = driver.find_element(By.ID, "foo")
91
"""
92
return self._execute(Command.GET_ELEMENT_TAG_NAME)["value"]
93
94
@property
95
def text(self) -> str:
96
"""The text of the element.
97
98
Returns:
99
The text of the element.
100
101
Example:
102
element = driver.find_element(By.ID, "foo")
103
print(element.text)
104
"""
105
return self._execute(Command.GET_ELEMENT_TEXT)["value"]
106
107
def click(self) -> None:
108
"""Clicks the element.
109
110
Example:
111
element = driver.find_element(By.ID, "foo")
112
element.click()
113
"""
114
self._execute(Command.CLICK_ELEMENT)
115
116
def submit(self) -> None:
117
"""Submits a form.
118
119
Example:
120
form = driver.find_element(By.NAME, "login")
121
form.submit()
122
"""
123
script = (
124
"/* submitForm */var form = arguments[0];\n"
125
'while (form.nodeName != "FORM" && form.parentNode) {\n'
126
" form = form.parentNode;\n"
127
"}\n"
128
"if (!form) { throw Error('Unable to find containing form element'); }\n"
129
"if (!form.ownerDocument) { throw Error('Unable to find owning document'); }\n"
130
"var e = form.ownerDocument.createEvent('Event');\n"
131
"e.initEvent('submit', true, true);\n"
132
"if (form.dispatchEvent(e)) { HTMLFormElement.prototype.submit.call(form) }\n"
133
)
134
135
try:
136
self._parent.execute_script(script, self)
137
except JavascriptException as exc:
138
raise WebDriverException("To submit an element, it must be nested inside a form element") from exc
139
140
def clear(self) -> None:
141
"""Clears the text if it's a text entry element.
142
143
Example:
144
text_field = driver.find_element(By.NAME, "username")
145
text_field.clear()
146
"""
147
self._execute(Command.CLEAR_ELEMENT)
148
149
def get_property(self, name) -> str | bool | WebElement | dict:
150
"""Gets the given property of the element.
151
152
Args:
153
name: Name of the property to retrieve.
154
155
Returns:
156
The value of the property.
157
158
Example:
159
text_length = target_element.get_property("text_length")
160
"""
161
try:
162
return self._execute(Command.GET_ELEMENT_PROPERTY, {"name": name})["value"]
163
except WebDriverException:
164
# if we hit an end point that doesn't understand getElementProperty lets fake it
165
return self.parent.execute_script("return arguments[0][arguments[1]]", self, name)
166
167
def get_dom_attribute(self, name) -> str:
168
"""Get the HTML attribute value (not reflected properties) of the element.
169
170
Returns only attributes declared in the element's HTML markup, unlike
171
`selenium.webdriver.remote.BaseWebElement.get_attribute`.
172
173
Args:
174
name: Name of the attribute to retrieve.
175
176
Returns:
177
The value of the attribute.
178
179
Example:
180
text_length = target_element.get_dom_attribute("class")
181
"""
182
return self._execute(Command.GET_ELEMENT_ATTRIBUTE, {"name": name})["value"]
183
184
def get_attribute(self, name) -> str | None:
185
"""Gets the given attribute or property of the element.
186
187
This method will first try to return the value of a property with the
188
given name. If a property with that name doesn't exist, it returns the
189
value of the attribute with the same name. If there's no attribute with
190
that name, ``None`` is returned.
191
192
Values which are considered truthy, that is equals "true" or "false",
193
are returned as booleans. All other non-``None`` values are returned
194
as strings. For attributes or properties which do not exist, ``None``
195
is returned.
196
197
To obtain the exact value of the attribute or property,
198
use :func:`~selenium.webdriver.remote.BaseWebElement.get_dom_attribute` or
199
:func:`~selenium.webdriver.remote.BaseWebElement.get_property` methods respectively.
200
201
Args:
202
name: Name of the attribute/property to retrieve.
203
204
Returns:
205
The value of the attribute/property.
206
207
Example:
208
# Check if the "active" CSS class is applied to an element.
209
is_active = "active" in target_element.get_attribute("class")
210
"""
211
if getAttribute_js is None:
212
_load_js()
213
attribute_value = self.parent.execute_script(
214
f"/* getAttribute */return ({getAttribute_js}).apply(null, arguments);", self, name
215
)
216
return attribute_value
217
218
def is_selected(self) -> bool:
219
"""Returns whether the element is selected.
220
221
This method is generally used on checkboxes, options in a select
222
and radio buttons.
223
224
Example:
225
is_selected = element.is_selected()
226
"""
227
return self._execute(Command.IS_ELEMENT_SELECTED)["value"]
228
229
def is_enabled(self) -> bool:
230
"""Returns whether the element is enabled.
231
232
Example:
233
is_enabled = element.is_enabled()
234
"""
235
return self._execute(Command.IS_ELEMENT_ENABLED)["value"]
236
237
def send_keys(self, *value: str) -> None:
238
"""Simulates typing into the element.
239
240
Use this to send simple key events or to fill out form fields.
241
This can also be used to set file inputs.
242
243
Args:
244
value: A string for typing, or setting form fields. For setting
245
file inputs, this could be a local file path.
246
247
Examples:
248
To send a simple key event::
249
250
form_textfield = driver.find_element(By.NAME, "username")
251
form_textfield.send_keys("admin")
252
253
or to set a file input field::
254
255
file_input = driver.find_element(By.NAME, "profilePic")
256
file_input.send_keys("path/to/profilepic.gif")
257
# Generally it's better to wrap the file path in one of the methods
258
# in os.path to return the actual path to support cross OS testing.
259
# file_input.send_keys(os.path.abspath("path/to/profilepic.gif"))
260
"""
261
# transfer file to another machine only if remote driver is used
262
# the same behaviour as for java binding
263
if self.parent._is_remote:
264
local_files = list(
265
map(
266
lambda keys_to_send: self.parent.file_detector.is_local_file(str(keys_to_send)),
267
"".join(map(str, value)).split("\n"),
268
)
269
)
270
if None not in local_files:
271
remote_files = []
272
for file in local_files:
273
remote_files.append(self._upload(file))
274
value = tuple("\n".join(remote_files))
275
276
self._execute(
277
Command.SEND_KEYS_TO_ELEMENT, {"text": "".join(keys_to_typing(value)), "value": keys_to_typing(value)}
278
)
279
280
@property
281
def shadow_root(self) -> ShadowRoot:
282
"""Get the shadow root attached to this element if present (Chromium, Firefox, Safari).
283
284
Returns:
285
The ShadowRoot object.
286
287
Raises:
288
NoSuchShadowRoot: If no shadow root was attached to element.
289
290
Example:
291
try:
292
shadow_root = element.shadow_root
293
except NoSuchShadowRoot:
294
print("No shadow root attached to element")
295
"""
296
return self._execute(Command.GET_SHADOW_ROOT)["value"]
297
298
# RenderedWebElement Items
299
def is_displayed(self) -> bool:
300
"""Whether the element is visible to a user.
301
302
Example:
303
is_displayed = element.is_displayed()
304
"""
305
# Only go into this conditional for browsers that don't use the atom themselves
306
if isDisplayed_js is None:
307
_load_js()
308
return self.parent.execute_script(f"/* isDisplayed */return ({isDisplayed_js}).apply(null, arguments);", self)
309
310
@property
311
def location_once_scrolled_into_view(self) -> dict:
312
"""Get the element's location on screen after scrolling it into view.
313
314
This may change without warning and scrolls the element into view
315
before calculating coordinates for clicking purposes.
316
317
Returns:
318
The top lefthand corner location on the screen, or zero
319
coordinates if the element is not visible.
320
321
Example:
322
loc = element.location_once_scrolled_into_view
323
"""
324
old_loc = self._execute(
325
Command.W3C_EXECUTE_SCRIPT,
326
{
327
"script": "arguments[0].scrollIntoView(true); return arguments[0].getBoundingClientRect()",
328
"args": [self],
329
},
330
)["value"]
331
return {"x": round(old_loc["x"]), "y": round(old_loc["y"])}
332
333
@property
334
def size(self) -> dict:
335
"""Get the size of the element.
336
337
Returns:
338
The width and height of the element.
339
340
Example:
341
size = element.size
342
"""
343
size = self._execute(Command.GET_ELEMENT_RECT)["value"]
344
new_size = {"height": size["height"], "width": size["width"]}
345
return new_size
346
347
def value_of_css_property(self, property_name) -> str:
348
"""Get the value of a CSS property.
349
350
Args:
351
property_name: The name of the CSS property to get the value of.
352
353
Returns:
354
The value of the CSS property.
355
356
Example:
357
value = element.value_of_css_property("color")
358
"""
359
return self._execute(Command.GET_ELEMENT_VALUE_OF_CSS_PROPERTY, {"propertyName": property_name})["value"]
360
361
@property
362
def location(self) -> dict:
363
"""Get the location of the element in the renderable canvas.
364
365
Returns:
366
The x and y coordinates of the element.
367
368
Example:
369
loc = element.location
370
"""
371
old_loc = self._execute(Command.GET_ELEMENT_RECT)["value"]
372
new_loc = {"x": round(old_loc["x"]), "y": round(old_loc["y"])}
373
return new_loc
374
375
@property
376
def rect(self) -> dict:
377
"""Get the size and location of the element.
378
379
Returns:
380
A dictionary with size and location of the element.
381
382
Example:
383
rect = element.rect
384
"""
385
return self._execute(Command.GET_ELEMENT_RECT)["value"]
386
387
@property
388
def aria_role(self) -> str:
389
"""Get the ARIA role of the current web element.
390
391
Returns:
392
The ARIA role of the element.
393
394
Example:
395
role = element.aria_role
396
"""
397
return self._execute(Command.GET_ELEMENT_ARIA_ROLE)["value"]
398
399
@property
400
def accessible_name(self) -> str:
401
"""Get the ARIA Level of the current webelement.
402
403
Returns:
404
The ARIA Level of the element.
405
406
Example:
407
name = element.accessible_name
408
"""
409
return self._execute(Command.GET_ELEMENT_ARIA_LABEL)["value"]
410
411
@property
412
def screenshot_as_base64(self) -> str:
413
"""Get a base64-encoded screenshot of the current element.
414
415
Returns:
416
The screenshot of the element as a base64 encoded string.
417
418
Example:
419
img_b64 = element.screenshot_as_base64
420
"""
421
return self._execute(Command.ELEMENT_SCREENSHOT)["value"]
422
423
@property
424
def screenshot_as_png(self) -> bytes:
425
"""Get the screenshot of the current element as a binary data.
426
427
Returns:
428
The screenshot of the element as binary data.
429
430
Example:
431
element_png = element.screenshot_as_png
432
"""
433
return b64decode(self.screenshot_as_base64.encode("ascii"))
434
435
def screenshot(self, filename) -> bool:
436
"""Save a PNG screenshot of the current element to a file.
437
438
Use full paths in your filename.
439
440
Args:
441
filename: The full path you wish to save your screenshot to. This
442
should end with a `.png` extension.
443
444
Returns:
445
True if the screenshot was saved successfully, False otherwise.
446
447
Example:
448
element.screenshot("/Screenshots/foo.png")
449
"""
450
if not filename.lower().endswith(".png"):
451
warnings.warn(
452
"name used for saved screenshot does not match file type. It should end with a `.png` extension",
453
UserWarning,
454
)
455
png = self.screenshot_as_png
456
try:
457
with open(filename, "wb") as f:
458
f.write(png)
459
except OSError:
460
return False
461
finally:
462
del png
463
return True
464
465
@property
466
def parent(self):
467
"""Get the WebDriver instance this element was found from.
468
469
Example:
470
element = driver.find_element(By.ID, "foo")
471
parent_element = element.parent
472
"""
473
return self._parent
474
475
@property
476
def id(self) -> str:
477
"""Get the ID used by selenium.
478
479
This is mainly for internal use. Simple use cases such as checking if 2
480
webelements refer to the same element, can be done using ``==``::
481
482
Example:
483
if element1 == element2:
484
print("These 2 are equal")
485
"""
486
return self._id
487
488
def __eq__(self, element):
489
return hasattr(element, "id") and self._id == element.id
490
491
def __ne__(self, element):
492
return not self.__eq__(element)
493
494
# Private Methods
495
def _execute(self, command, params=None):
496
"""Executes a command against the underlying HTML element.
497
498
Args:
499
command: The name of the command to _execute as a string.
500
params: A dictionary of named Parameters to send with the command.
501
502
Returns:
503
The command's JSON response loaded into a dictionary object.
504
"""
505
if not params:
506
params = {}
507
params["id"] = self._id
508
return self._parent.execute(command, params)
509
510
def find_element(self, by: str = By.ID, value: str | None = None) -> WebElement:
511
"""Find an element given a By strategy and locator.
512
513
Args:
514
by: The locating strategy to use. Default is `By.ID`. Supported values include:
515
- By.ID: Locate by element ID.
516
- By.NAME: Locate by the `name` attribute.
517
- By.XPATH: Locate by an XPath expression.
518
- By.CSS_SELECTOR: Locate by a CSS selector.
519
- By.CLASS_NAME: Locate by the `class` attribute.
520
- By.TAG_NAME: Locate by the tag name (e.g., "input", "button").
521
- By.LINK_TEXT: Locate a link element by its exact text.
522
- By.PARTIAL_LINK_TEXT: Locate a link element by partial text match.
523
value: The locator value to use with the specified `by` strategy.
524
525
Returns:
526
The first matching `WebElement` found on the page.
527
528
Example:
529
element = driver.find_element(By.ID, "foo")
530
"""
531
by, value = self._parent.locator_converter.convert(by, value)
532
return self._execute(Command.FIND_CHILD_ELEMENT, {"using": by, "value": value})["value"]
533
534
def find_elements(self, by: str = By.ID, value: str | None = None) -> list[WebElement]:
535
"""Find elements given a By strategy and locator.
536
537
Args:
538
by: The locating strategy to use. Default is `By.ID`. Supported values include:
539
- By.ID: Locate by element ID.
540
- By.NAME: Locate by the `name` attribute.
541
- By.XPATH: Locate by an XPath expression.
542
- By.CSS_SELECTOR: Locate by a CSS selector.
543
- By.CLASS_NAME: Locate by the `class` attribute.
544
- By.TAG_NAME: Locate by the tag name (e.g., "input", "button").
545
- By.LINK_TEXT: Locate a link element by its exact text.
546
- By.PARTIAL_LINK_TEXT: Locate a link element by partial text match.
547
value: The locator value to use with the specified `by` strategy.
548
549
Returns:
550
List of `WebElements` matching locator strategy found on the page.
551
552
Example:
553
element = driver.find_elements(By.ID, "foo")
554
"""
555
by, value = self._parent.locator_converter.convert(by, value)
556
return self._execute(Command.FIND_CHILD_ELEMENTS, {"using": by, "value": value})["value"]
557
558
def __hash__(self) -> int:
559
return int(md5_hash(self._id.encode("utf-8")).hexdigest(), 16)
560
561
def _upload(self, filename):
562
fp = BytesIO()
563
zipped = zipfile.ZipFile(fp, "w", zipfile.ZIP_DEFLATED)
564
zipped.write(filename, os.path.split(filename)[1])
565
zipped.close()
566
content = encodebytes(fp.getvalue())
567
if not isinstance(content, str):
568
content = content.decode("utf-8")
569
try:
570
return self._execute(Command.UPLOAD_FILE, {"file": content})["value"]
571
except WebDriverException as e:
572
if "Unrecognized command: POST" in str(e):
573
return filename
574
if "Command not found: POST " in str(e):
575
return filename
576
if '{"status":405,"value":["GET","HEAD","DELETE"]}' in str(e):
577
return filename
578
raise
579
580