Path: blob/trunk/py/selenium/webdriver/common/log.py
1864 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.1617import json18import pkgutil19from collections.abc import AsyncGenerator20from contextlib import asynccontextmanager21from importlib import import_module22from typing import Any, Optional2324from selenium.webdriver.common.by import By2526cdp = None272829def import_cdp():30global cdp31if not cdp:32cdp = import_module("selenium.webdriver.common.bidi.cdp")333435class Log:36"""This class allows access to logging APIs that use the new WebDriver Bidi37protocol.3839This class is not to be used directly and should be used from the40webdriver base classes.41"""4243def __init__(self, driver, bidi_session) -> None:44self.driver = driver45self.session = bidi_session.session46self.cdp = bidi_session.cdp47self.devtools = bidi_session.devtools48_pkg = ".".join(__name__.split(".")[:-1])49# Ensure _mutation_listener_js is not None before decoding50_mutation_listener_js_bytes: Optional[bytes] = pkgutil.get_data(_pkg, "mutation-listener.js")51if _mutation_listener_js_bytes is None:52raise ValueError("Failed to load mutation-listener.js")53self._mutation_listener_js = _mutation_listener_js_bytes.decode("utf8").strip()5455@asynccontextmanager56async def mutation_events(self) -> AsyncGenerator[dict[str, Any], None]:57"""Listen for mutation events and emit them as they are found.5859:Usage:60::6162async with driver.log.mutation_events() as event:63pages.load("dynamic.html")64driver.find_element(By.ID, "reveal").click()65WebDriverWait(driver, 5)\66.until(EC.visibility_of(driver.find_element(By.ID, "revealed")))6768assert event["attribute_name"] == "style"69assert event["current_value"] == ""70assert event["old_value"] == "display:none;"71"""7273page = self.cdp.get_session_context("page.enable")74await page.execute(self.devtools.page.enable())75runtime = self.cdp.get_session_context("runtime.enable")76await runtime.execute(self.devtools.runtime.enable())77await runtime.execute(self.devtools.runtime.add_binding("__webdriver_attribute"))78self.driver.pin_script(self._mutation_listener_js)79script_key = await page.execute(80self.devtools.page.add_script_to_evaluate_on_new_document(self._mutation_listener_js)81)82self.driver.pin_script(self._mutation_listener_js, script_key)83self.driver.execute_script(f"return {self._mutation_listener_js}")8485event: dict[str, Any] = {}86async with runtime.wait_for(self.devtools.runtime.BindingCalled) as evnt:87yield event8889payload = json.loads(evnt.value.payload)90elements: list = self.driver.find_elements(By.CSS_SELECTOR, f'*[data-__webdriver_id="{payload["target"]}"]')91if not elements:92elements.append(None)93event["element"] = elements[0]94event["attribute_name"] = payload["name"]95event["current_value"] = payload["value"]96event["old_value"] = payload["oldValue"]9798@asynccontextmanager99async def add_js_error_listener(self) -> AsyncGenerator[dict[str, Any], None]:100"""Listen for JS errors and when the contextmanager exits check if101there were JS Errors.102103:Usage:104::105106async with driver.log.add_js_error_listener() as error:107driver.find_element(By.ID, "throwing-mouseover").click()108assert bool(error)109assert error.exception_details.stack_trace.call_frames[0].function_name == "onmouseover"110"""111112session = self.cdp.get_session_context("page.enable")113await session.execute(self.devtools.page.enable())114session = self.cdp.get_session_context("runtime.enable")115await session.execute(self.devtools.runtime.enable())116js_exception = self.devtools.runtime.ExceptionThrown(None, None)117async with session.wait_for(self.devtools.runtime.ExceptionThrown) as exception:118yield js_exception119js_exception.timestamp = exception.value.timestamp120js_exception.exception_details = exception.value.exception_details121122@asynccontextmanager123async def add_listener(self, event_type) -> AsyncGenerator[dict[str, Any], None]:124"""Listen for certain events that are passed in.125126:Args:127- event_type: The type of event that we want to look at.128129:Usage:130::131132async with driver.log.add_listener(Console.log) as messages:133driver.execute_script("console.log('I like cheese')")134assert messages["message"] == "I love cheese"135"""136137from selenium.webdriver.common.bidi.console import Console138139session = self.cdp.get_session_context("page.enable")140await session.execute(self.devtools.page.enable())141session = self.cdp.get_session_context("runtime.enable")142await session.execute(self.devtools.runtime.enable())143console: dict[str, Any] = {"message": None, "level": None}144async with session.wait_for(self.devtools.runtime.ConsoleAPICalled) as messages:145yield console146147if event_type == Console.ALL or event_type.value == messages.value.type_:148console["message"] = messages.value.args[0].value149console["level"] = messages.value.args[0].type_150151152