Source code for soupsavvy.implementation.selenium

"""
Module with `selenium` implementations compatible with soupsavvy interfaces.
- `SeleniumElement` class is an adapter making `selenium` tree,
compatible with `IElement` interface and usable across the library.
- `SeleniumBrowser` class is an adapter making `selenium` WebDriver
compatible with `IBrowser`.
"""

from __future__ import annotations

from itertools import islice
from typing import Iterable, Optional, Pattern, Union

from selenium.webdriver.common.by import By
from selenium.webdriver.remote.webdriver import WebDriver
from selenium.webdriver.remote.webelement import WebElement
from typing_extensions import Self

import soupsavvy.exceptions as exc
import soupsavvy.implementation.snippets.js.selenium as js
from soupsavvy.implementation.snippets import css, xpath
from soupsavvy.interfaces import IBrowser, IElement
from soupsavvy.selectors.css.api import SeleniumCSSApi
from soupsavvy.selectors.xpath.api import SeleniumXPathApi


[docs] class SeleniumElement(IElement[WebElement]): """ Implementation of `IElement` for `selenium` tree. Adapter for `selenium` objects, that makes them usable across the library. Example ------- >>> from soupsavvy.implementation.selenium import SeleniumElement ... from selenium.ISeleniumDriver.common.by import By ... node = driver.find_element(By.TAG_NAME, "div") ... element = SeleniumElement(node) """ _NODE_TYPE = WebElement
[docs] def find_all( self, name: Optional[str] = None, attrs: Optional[dict[str, Union[str, Pattern[str]]]] = None, recursive: bool = True, limit: Optional[int] = None, ) -> list[Self]: attrs = attrs or {} js_attrs = {k: None if isinstance(v, Pattern) else v for k, v in attrs.items()} driver: WebDriver = self.node.parent matched_elements: list[WebElement] = driver.execute_script( js.FILTER_NODES_SCRIPT, self.node, name, js_attrs, recursive, ) def match(element: WebElement) -> bool: return all( value.search(element.get_attribute(attr) or "") for attr, value in attrs.items() if isinstance(value, Pattern) ) return list(islice(self._map(filter(match, matched_elements)), limit))
[docs] def find_subsequent_siblings(self, limit: Optional[int] = None) -> list[Self]: iterator = self.node.find_elements( By.XPATH, xpath.FIND_SUBSEQUENT_SIBLINGS_SELECTOR ) return list(islice(self._map(iterator), limit))
[docs] def find_ancestors(self, limit: Optional[int] = None) -> list[Self]: driver: WebDriver = self.node.parent iterator = driver.execute_script( js.FIND_ANCESTORS_SCRIPT, self.node, limit, ) return list(self._map(iterator))
@property def children(self) -> Iterable[Self]: iterator = self.node.find_elements(By.XPATH, xpath.FIND_ALL_CHILDREN_SELECTOR) return self._map(iterator) @property def descendants(self) -> Iterable[Self]: iterator = self.node.find_elements( By.CSS_SELECTOR, css.FIND_ALL_DESCENDANTS_SELECTOR ) return self._map(iterator) @property def parent(self) -> Optional[Self]: driver: WebDriver = self.node.parent element = driver.execute_script(js.FIND_PARENT_NODE_SCRIPT, self.node) return self.from_node(element) if element is not None else None
[docs] def get_attribute(self, name: str) -> Optional[str]: return self.node.get_attribute(name)
@property def name(self) -> str: return self.node.tag_name def __str__(self) -> str: return self.node.get_attribute("outerHTML") or "" @property def text(self) -> str: return self.node.text
[docs] def css(self, selector: str) -> SeleniumCSSApi: return SeleniumCSSApi(selector)
[docs] def xpath(self, selector: str) -> SeleniumXPathApi: return SeleniumXPathApi(selector)
[docs] class SeleniumBrowser(IBrowser[WebDriver, SeleniumElement]): """ Implementation of `IBrowser` for `selenium` WebDriver. Adapter for `selenium` WebDriver, that makes them usable across the library. Example ------- >>> from soupsavvy.implementation.selenium import SeleniumBrowser ... from selenium import webdriver ... driver = webdriver.Chrome() ... browser = SeleniumBrowser(driver) """
[docs] def navigate(self, url: str) -> None: self.browser.get(url)
[docs] def click(self, element: SeleniumElement) -> None: self.browser.execute_script(js.CLICK_ELEMENT_SCRIPT, element.node)
[docs] def send_keys( self, element: SeleniumElement, value: str, clear: bool = True ) -> None: if clear: element.node.clear() element.node.send_keys(value)
[docs] def get_document(self) -> SeleniumElement: elements = self.browser.find_elements(by=By.TAG_NAME, value="html") if not elements: raise exc.TagNotFoundException("Could not find <html> element on the page.") return SeleniumElement(elements[0])
[docs] def close(self) -> None: self.browser.quit()
[docs] def get_current_url(self) -> str: return self.browser.current_url