Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
SeleniumHQ
GitHub Repository: SeleniumHQ/Selenium
Path: blob/trunk/py/selenium/webdriver/remote/shadowroot.py
3998 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
from hashlib import md5 as md5_hash
21
from typing import TYPE_CHECKING
22
23
from selenium.common.exceptions import InvalidSelectorException
24
from selenium.webdriver.common.by import By
25
from selenium.webdriver.remote.command import Command
26
27
if TYPE_CHECKING:
28
# we only import these when the module is analyzed for type annotations
29
# to avoid a circular import when it is run normally
30
from selenium.webdriver.remote.webelement import WebElement
31
32
33
class ShadowRoot:
34
# TODO: We should look and see how we can create a search context like Java/.NET
35
36
def __init__(self, session, id_) -> None:
37
self.session = session
38
self._id = id_
39
40
def __eq__(self, other_shadowroot) -> bool:
41
return self._id == other_shadowroot._id
42
43
def __hash__(self) -> int:
44
return int(md5_hash(self._id.encode("utf-8")).hexdigest(), 16)
45
46
def __repr__(self) -> str:
47
return '<{0.__module__}.{0.__name__} (session="{1}", element="{2}")>'.format(
48
type(self), self.session.session_id, self._id
49
)
50
51
@property
52
def id(self) -> str:
53
return self._id
54
55
def find_element(self, by: str = By.ID, value: str | None = None) -> WebElement:
56
"""Find an element inside a shadow root given a By strategy and locator.
57
58
Args:
59
by: The locating strategy to use. Default is `By.ID`. Supported values include:
60
- By.ID: Locate by element ID.
61
- By.NAME: Locate by the `name` attribute.
62
- By.XPATH: Locate by an XPath expression.
63
- By.CSS_SELECTOR: Locate by a CSS selector.
64
- By.CLASS_NAME: Locate by the `class` attribute.
65
- By.TAG_NAME: Locate by the tag name (e.g., "input", "button").
66
- By.LINK_TEXT: Locate a link element by its exact text.
67
- By.PARTIAL_LINK_TEXT: Locate a link element by partial text match.
68
value: The locator value to use with the specified `by` strategy.
69
70
Returns:
71
The first matching `WebElement` found on the page.
72
73
Example:
74
>>> element = driver.find_element(By.ID, "foo")
75
"""
76
if by == By.ID:
77
by = By.CSS_SELECTOR
78
value = f'[id="{value}"]'
79
elif by == By.CLASS_NAME:
80
if value and any(char.isspace() for char in value.strip()):
81
raise InvalidSelectorException("Compound class names are not allowed.")
82
by = By.CSS_SELECTOR
83
value = f".{value}"
84
elif by == By.NAME:
85
by = By.CSS_SELECTOR
86
value = f'[name="{value}"]'
87
88
return self._execute(Command.FIND_ELEMENT_FROM_SHADOW_ROOT, {"using": by, "value": value})["value"]
89
90
def find_elements(self, by: str = By.ID, value: str | None = None) -> list[WebElement]:
91
"""Find elements inside a shadow root given a By strategy and locator.
92
93
Args:
94
by: The locating strategy to use. Default is `By.ID`. Supported values include:
95
- By.ID: Locate by element ID.
96
- By.NAME: Locate by the `name` attribute.
97
- By.XPATH: Locate by an XPath expression.
98
- By.CSS_SELECTOR: Locate by a CSS selector.
99
- By.CLASS_NAME: Locate by the `class` attribute.
100
- By.TAG_NAME: Locate by the tag name (e.g., "input", "button").
101
- By.LINK_TEXT: Locate a link element by its exact text.
102
- By.PARTIAL_LINK_TEXT: Locate a link element by partial text match.
103
value: The locator value to use with the specified `by` strategy.
104
105
Returns:
106
List of `WebElements` matching locator strategy found on the page.
107
108
Example:
109
>>> element = driver.find_elements(By.ID, "foo")
110
"""
111
if by == By.ID:
112
by = By.CSS_SELECTOR
113
value = f'[id="{value}"]'
114
elif by == By.CLASS_NAME:
115
if value and any(char.isspace() for char in value.strip()):
116
raise InvalidSelectorException("Compound class names are not allowed.")
117
by = By.CSS_SELECTOR
118
value = f".{value}"
119
elif by == By.NAME:
120
by = By.CSS_SELECTOR
121
value = f'[name="{value}"]'
122
123
return self._execute(Command.FIND_ELEMENTS_FROM_SHADOW_ROOT, {"using": by, "value": value})["value"]
124
125
# Private Methods
126
def _execute(self, command, params=None):
127
"""Executes a command against the underlying HTML element.
128
129
Args:
130
command: The name of the command to _execute as a string.
131
params: A dictionary of named parameters to send with the command.
132
133
Returns:
134
The command's JSON response loaded into a dictionary object.
135
"""
136
if not params:
137
params = {}
138
params["shadowId"] = self._id
139
return self.session.execute(command, params)
140
141