Path: blob/trunk/py/selenium/webdriver/remote/shadowroot.py
3998 views
# Licensed to the Software Freedom Conservancy (SFC) under one1# or more contributor license agreements. See the NOTICE file2# distributed with this work for additional information3# regarding copyright ownership. The SFC licenses this file4# to you under the Apache License, Version 2.0 (the5# "License"); you may not use this file except in compliance6# with the License. You may obtain a copy of the License at7#8# http://www.apache.org/licenses/LICENSE-2.09#10# Unless required by applicable law or agreed to in writing,11# software distributed under the License is distributed on an12# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY13# KIND, either express or implied. See the License for the14# specific language governing permissions and limitations15# under the License.1617from __future__ import annotations1819from hashlib import md5 as md5_hash20from typing import TYPE_CHECKING2122from selenium.common.exceptions import InvalidSelectorException23from selenium.webdriver.common.by import By24from selenium.webdriver.remote.command import Command2526if TYPE_CHECKING:27# we only import these when the module is analyzed for type annotations28# to avoid a circular import when it is run normally29from selenium.webdriver.remote.webelement import WebElement303132class ShadowRoot:33# TODO: We should look and see how we can create a search context like Java/.NET3435def __init__(self, session, id_) -> None:36self.session = session37self._id = id_3839def __eq__(self, other_shadowroot) -> bool:40return self._id == other_shadowroot._id4142def __hash__(self) -> int:43return int(md5_hash(self._id.encode("utf-8")).hexdigest(), 16)4445def __repr__(self) -> str:46return '<{0.__module__}.{0.__name__} (session="{1}", element="{2}")>'.format(47type(self), self.session.session_id, self._id48)4950@property51def id(self) -> str:52return self._id5354def find_element(self, by: str = By.ID, value: str | None = None) -> WebElement:55"""Find an element inside a shadow root given a By strategy and locator.5657Args:58by: The locating strategy to use. Default is `By.ID`. Supported values include:59- By.ID: Locate by element ID.60- By.NAME: Locate by the `name` attribute.61- By.XPATH: Locate by an XPath expression.62- By.CSS_SELECTOR: Locate by a CSS selector.63- By.CLASS_NAME: Locate by the `class` attribute.64- By.TAG_NAME: Locate by the tag name (e.g., "input", "button").65- By.LINK_TEXT: Locate a link element by its exact text.66- By.PARTIAL_LINK_TEXT: Locate a link element by partial text match.67value: The locator value to use with the specified `by` strategy.6869Returns:70The first matching `WebElement` found on the page.7172Example:73>>> element = driver.find_element(By.ID, "foo")74"""75if by == By.ID:76by = By.CSS_SELECTOR77value = f'[id="{value}"]'78elif by == By.CLASS_NAME:79if value and any(char.isspace() for char in value.strip()):80raise InvalidSelectorException("Compound class names are not allowed.")81by = By.CSS_SELECTOR82value = f".{value}"83elif by == By.NAME:84by = By.CSS_SELECTOR85value = f'[name="{value}"]'8687return self._execute(Command.FIND_ELEMENT_FROM_SHADOW_ROOT, {"using": by, "value": value})["value"]8889def find_elements(self, by: str = By.ID, value: str | None = None) -> list[WebElement]:90"""Find elements inside a shadow root given a By strategy and locator.9192Args:93by: The locating strategy to use. Default is `By.ID`. Supported values include:94- By.ID: Locate by element ID.95- By.NAME: Locate by the `name` attribute.96- By.XPATH: Locate by an XPath expression.97- By.CSS_SELECTOR: Locate by a CSS selector.98- By.CLASS_NAME: Locate by the `class` attribute.99- By.TAG_NAME: Locate by the tag name (e.g., "input", "button").100- By.LINK_TEXT: Locate a link element by its exact text.101- By.PARTIAL_LINK_TEXT: Locate a link element by partial text match.102value: The locator value to use with the specified `by` strategy.103104Returns:105List of `WebElements` matching locator strategy found on the page.106107Example:108>>> element = driver.find_elements(By.ID, "foo")109"""110if by == By.ID:111by = By.CSS_SELECTOR112value = f'[id="{value}"]'113elif by == By.CLASS_NAME:114if value and any(char.isspace() for char in value.strip()):115raise InvalidSelectorException("Compound class names are not allowed.")116by = By.CSS_SELECTOR117value = f".{value}"118elif by == By.NAME:119by = By.CSS_SELECTOR120value = f'[name="{value}"]'121122return self._execute(Command.FIND_ELEMENTS_FROM_SHADOW_ROOT, {"using": by, "value": value})["value"]123124# Private Methods125def _execute(self, command, params=None):126"""Executes a command against the underlying HTML element.127128Args:129command: The name of the command to _execute as a string.130params: A dictionary of named parameters to send with the command.131132Returns:133The command's JSON response loaded into a dictionary object.134"""135if not params:136params = {}137params["shadowId"] = self._id138return self.session.execute(command, params)139140141