Path: blob/trunk/py/selenium/webdriver/support/event_firing_webdriver.py
3983 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 typing import Any1819from selenium.common.exceptions import WebDriverException20from selenium.webdriver.common.by import By21from selenium.webdriver.remote.webdriver import WebDriver22from selenium.webdriver.remote.webelement import WebElement23from selenium.webdriver.support.abstract_event_listener import AbstractEventListener242526def _wrap_elements(result, ef_driver):27# handle the case if another wrapper wraps EventFiringWebElement28if isinstance(result, EventFiringWebElement):29return result30if isinstance(result, WebElement):31return EventFiringWebElement(result, ef_driver)32if isinstance(result, list):33return [_wrap_elements(item, ef_driver) for item in result]34return result353637class EventFiringWebDriver:38"""Wrap an arbitrary WebDriver instance and support firing events.3940This wrapper allows you to hook into various WebDriver events through an41AbstractEventListener implementation.42"""4344def __init__(self, driver: WebDriver, event_listener: AbstractEventListener) -> None:45"""Creates a new instance of the EventFiringWebDriver.4647Args:48driver: A WebDriver instance49event_listener: Instance of a class that subclasses AbstractEventListener and implements it fully50or partially5152Example:53from selenium.webdriver import Firefox54from selenium.webdriver.support.events import EventFiringWebDriver, AbstractEventListener555657class MyListener(AbstractEventListener):58def before_navigate_to(self, url, driver):59print("Before navigate to %s" % url)6061def after_navigate_to(self, url, driver):62print("After navigate to %s" % url)636465driver = Firefox()66ef_driver = EventFiringWebDriver(driver, MyListener())67ef_driver.get("http://www.google.co.in/")68"""69if not isinstance(driver, WebDriver):70raise WebDriverException("A WebDriver instance must be supplied")71if not isinstance(event_listener, AbstractEventListener):72raise WebDriverException("Event listener must be a subclass of AbstractEventListener")73self._driver = driver74# this is valid, but type checkers don't like dynamically assigning to a method75self._driver._wrap_value = self._wrap_value # type: ignore76self._listener = event_listener7778@property79def wrapped_driver(self) -> WebDriver:80"""Returns the WebDriver instance wrapped by this EventsFiringWebDriver."""81return self._driver8283def get(self, url: str) -> None:84self._dispatch("navigate_to", (url, self._driver), "get", (url,))8586def back(self) -> None:87self._dispatch("navigate_back", (self._driver,), "back", ())8889def forward(self) -> None:90self._dispatch("navigate_forward", (self._driver,), "forward", ())9192def execute_script(self, script: str, *args):93unwrapped_args = (script,) + self._unwrap_element_args(args)94return self._dispatch("execute_script", (script, self._driver), "execute_script", unwrapped_args)9596def execute_async_script(self, script, *args):97unwrapped_args = (script,) + self._unwrap_element_args(args)98return self._dispatch("execute_script", (script, self._driver), "execute_async_script", unwrapped_args)99100def close(self) -> None:101self._dispatch("close", (self._driver,), "close", ())102103def quit(self) -> None:104self._dispatch("quit", (self._driver,), "quit", ())105106def find_element(self, by=By.ID, value=None) -> WebElement:107return self._dispatch("find", (by, value, self._driver), "find_element", (by, value))108109def find_elements(self, by=By.ID, value=None) -> list[WebElement]:110return self._dispatch("find", (by, value, self._driver), "find_elements", (by, value))111112def _dispatch(self, l_call: str, l_args: tuple[Any, ...], d_call: str, d_args: tuple[Any, ...]):113getattr(self._listener, f"before_{l_call}")(*l_args)114try:115result = getattr(self._driver, d_call)(*d_args)116except Exception as exc:117self._listener.on_exception(exc, self._driver)118raise119getattr(self._listener, f"after_{l_call}")(*l_args)120return _wrap_elements(result, self)121122def _unwrap_element_args(self, args):123if isinstance(args, EventFiringWebElement):124return args.wrapped_element125if isinstance(args, tuple):126return tuple(self._unwrap_element_args(item) for item in args)127if isinstance(args, list):128return [self._unwrap_element_args(item) for item in args]129return args130131def _wrap_value(self, value):132if isinstance(value, EventFiringWebElement):133return WebDriver._wrap_value(self._driver, value.wrapped_element)134return WebDriver._wrap_value(self._driver, value)135136def __setattr__(self, item, value):137if item.startswith("_") or not hasattr(self._driver, item):138object.__setattr__(self, item, value)139else:140try:141object.__setattr__(self._driver, item, value)142except Exception as exc:143self._listener.on_exception(exc, self._driver)144raise145146def __getattr__(self, name):147def _wrap(*args, **kwargs):148try:149result = attrib(*args, **kwargs)150return _wrap_elements(result, self)151except Exception as exc:152self._listener.on_exception(exc, self._driver)153raise154155try:156attrib = getattr(self._driver, name)157return _wrap if callable(attrib) else attrib158except Exception as exc:159self._listener.on_exception(exc, self._driver)160raise161162163class EventFiringWebElement:164"""A wrapper around WebElement instance which supports firing events."""165166def __init__(self, webelement: WebElement, ef_driver: EventFiringWebDriver) -> None:167"""Creates a new instance of the EventFiringWebElement."""168self._webelement = webelement169self._ef_driver = ef_driver170self._driver = ef_driver.wrapped_driver171self._listener = ef_driver._listener172173@property174def wrapped_element(self) -> WebElement:175"""Returns the WebElement wrapped by this EventFiringWebElement instance."""176return self._webelement177178def click(self) -> None:179self._dispatch("click", (self._webelement, self._driver), "click", ())180181def clear(self) -> None:182self._dispatch("change_value_of", (self._webelement, self._driver), "clear", ())183184def send_keys(self, *value) -> None:185self._dispatch("change_value_of", (self._webelement, self._driver), "send_keys", value)186187def find_element(self, by=By.ID, value=None) -> WebElement:188return self._dispatch("find", (by, value, self._driver), "find_element", (by, value))189190def find_elements(self, by=By.ID, value=None) -> list[WebElement]:191return self._dispatch("find", (by, value, self._driver), "find_elements", (by, value))192193def _dispatch(self, l_call, l_args, d_call, d_args):194getattr(self._listener, f"before_{l_call}")(*l_args)195try:196result = getattr(self._webelement, d_call)(*d_args)197except Exception as exc:198self._listener.on_exception(exc, self._driver)199raise200getattr(self._listener, f"after_{l_call}")(*l_args)201return _wrap_elements(result, self._ef_driver)202203def __setattr__(self, item, value):204if item.startswith("_") or not hasattr(self._webelement, item):205object.__setattr__(self, item, value)206else:207try:208object.__setattr__(self._webelement, item, value)209except Exception as exc:210self._listener.on_exception(exc, self._driver)211raise212213def __getattr__(self, name):214def _wrap(*args, **kwargs):215try:216result = attrib(*args, **kwargs)217return _wrap_elements(result, self._ef_driver)218except Exception as exc:219self._listener.on_exception(exc, self._driver)220raise221222try:223attrib = getattr(self._webelement, name)224return _wrap if callable(attrib) else attrib225except Exception as exc:226self._listener.on_exception(exc, self._driver)227raise228229230# Register a virtual subclass.231WebElement.register(EventFiringWebElement)232233234