Path: blob/trunk/py/selenium/webdriver/common/bidi/network.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.1617from selenium.webdriver.common.bidi.common import command_builder181920class NetworkEvent:21"""Represents a network event."""2223def __init__(self, event_class, **kwargs):24self.event_class = event_class25self.params = kwargs2627@classmethod28def from_json(cls, json):29return cls(event_class=json.get("event_class"), **json)303132class Network:33EVENTS = {34"before_request": "network.beforeRequestSent",35"response_started": "network.responseStarted",36"response_completed": "network.responseCompleted",37"auth_required": "network.authRequired",38"fetch_error": "network.fetchError",39"continue_request": "network.continueRequest",40"continue_auth": "network.continueWithAuth",41}4243PHASES = {44"before_request": "beforeRequestSent",45"response_started": "responseStarted",46"auth_required": "authRequired",47}4849def __init__(self, conn):50self.conn = conn51self.intercepts = []52self.callbacks = {}53self.subscriptions = {}5455def _add_intercept(self, phases=[], contexts=None, url_patterns=None):56"""Add an intercept to the network.5758Parameters:59----------60phases (list, optional): A list of phases to intercept.61Default is empty list.62contexts (list, optional): A list of contexts to intercept.63Default is None.64url_patterns (list, optional): A list of URL patterns to intercept.65Default is None.6667Returns:68-------69str : intercept id70"""71params = {}72if contexts is not None:73params["contexts"] = contexts74if url_patterns is not None:75params["urlPatterns"] = url_patterns76if len(phases) > 0:77params["phases"] = phases78else:79params["phases"] = ["beforeRequestSent"]80cmd = command_builder("network.addIntercept", params)8182result = self.conn.execute(cmd)83self.intercepts.append(result["intercept"])84return result8586def _remove_intercept(self, intercept=None):87"""Remove a specific intercept, or all intercepts.8889Parameters:90----------91intercept (str, optional): The intercept to remove.92Default is None.9394Raises:95------96Exception: If intercept is not found.9798Notes:99-----100If intercept is None, all intercepts will be removed.101"""102if intercept is None:103intercepts_to_remove = self.intercepts.copy() # create a copy before iterating104for intercept_id in intercepts_to_remove: # remove all intercepts105self.conn.execute(command_builder("network.removeIntercept", {"intercept": intercept_id}))106self.intercepts.remove(intercept_id)107else:108try:109self.conn.execute(command_builder("network.removeIntercept", {"intercept": intercept}))110self.intercepts.remove(intercept)111except Exception as e:112raise Exception(f"Exception: {e}")113114def _on_request(self, event_name, callback):115"""Set a callback function to subscribe to a network event.116117Parameters:118----------119event_name (str): The event to subscribe to.120callback (function): The callback function to execute on event.121Takes Request object as argument.122123Returns:124-------125int : callback id126"""127128event = NetworkEvent(event_name)129130def _callback(event_data):131request = Request(132network=self,133request_id=event_data.params["request"].get("request", None),134body_size=event_data.params["request"].get("bodySize", None),135cookies=event_data.params["request"].get("cookies", None),136resource_type=event_data.params["request"].get("goog:resourceType", None),137headers=event_data.params["request"].get("headers", None),138headers_size=event_data.params["request"].get("headersSize", None),139timings=event_data.params["request"].get("timings", None),140url=event_data.params["request"].get("url", None),141)142callback(request)143144callback_id = self.conn.add_callback(event, _callback)145146if event_name in self.callbacks:147self.callbacks[event_name].append(callback_id)148else:149self.callbacks[event_name] = [callback_id]150151return callback_id152153def add_request_handler(self, event, callback, url_patterns=None, contexts=None):154"""Add a request handler to the network.155156Parameters:157----------158event (str): The event to subscribe to.159url_patterns (list, optional): A list of URL patterns to intercept.160Default is None.161contexts (list, optional): A list of contexts to intercept.162Default is None.163callback (function): The callback function to execute on request interception164Takes Request object as argument.165166Returns:167-------168int : callback id169"""170171try:172event_name = self.EVENTS[event]173phase_name = self.PHASES[event]174except KeyError:175raise Exception(f"Event {event} not found")176177result = self._add_intercept(phases=[phase_name], url_patterns=url_patterns, contexts=contexts)178callback_id = self._on_request(event_name, callback)179180if event_name in self.subscriptions:181self.subscriptions[event_name].append(callback_id)182else:183params = {}184params["events"] = [event_name]185self.conn.execute(command_builder("session.subscribe", params))186self.subscriptions[event_name] = [callback_id]187188self.callbacks[callback_id] = result["intercept"]189return callback_id190191def remove_request_handler(self, event, callback_id):192"""Remove a request handler from the network.193194Parameters:195----------196event_name (str): The event to unsubscribe from.197callback_id (int): The callback id to remove.198"""199try:200event_name = self.EVENTS[event]201except KeyError:202raise Exception(f"Event {event} not found")203204net_event = NetworkEvent(event_name)205206self.conn.remove_callback(net_event, callback_id)207self._remove_intercept(self.callbacks[callback_id])208del self.callbacks[callback_id]209self.subscriptions[event_name].remove(callback_id)210if len(self.subscriptions[event_name]) == 0:211params = {}212params["events"] = [event_name]213self.conn.execute(command_builder("session.unsubscribe", params))214del self.subscriptions[event_name]215216def clear_request_handlers(self):217"""Clear all request handlers from the network."""218219for event_name in self.subscriptions:220net_event = NetworkEvent(event_name)221for callback_id in self.subscriptions[event_name]:222self.conn.remove_callback(net_event, callback_id)223self._remove_intercept(self.callbacks[callback_id])224del self.callbacks[callback_id]225params = {}226params["events"] = [event_name]227self.conn.execute(command_builder("session.unsubscribe", params))228self.subscriptions = {}229230def add_auth_handler(self, username, password):231"""Add an authentication handler to the network.232233Parameters:234----------235username (str): The username to authenticate with.236password (str): The password to authenticate with.237238Returns:239-------240int : callback id241"""242event = "auth_required"243244def _callback(request):245request._continue_with_auth(username, password)246247return self.add_request_handler(event, _callback)248249def remove_auth_handler(self, callback_id):250"""Remove an authentication handler from the network.251252Parameters:253----------254callback_id (int): The callback id to remove.255"""256event = "auth_required"257self.remove_request_handler(event, callback_id)258259260class Request:261"""Represents an intercepted network request."""262263def __init__(264self,265network: Network,266request_id,267body_size=None,268cookies=None,269resource_type=None,270headers=None,271headers_size=None,272method=None,273timings=None,274url=None,275):276self.network = network277self.request_id = request_id278self.body_size = body_size279self.cookies = cookies280self.resource_type = resource_type281self.headers = headers282self.headers_size = headers_size283self.method = method284self.timings = timings285self.url = url286287def fail_request(self):288"""Fail this request."""289290if not self.request_id:291raise ValueError("Request not found.")292293params = {"request": self.request_id}294self.network.conn.execute(command_builder("network.failRequest", params))295296def continue_request(self, body=None, method=None, headers=None, cookies=None, url=None):297"""Continue after intercepting this request."""298299if not self.request_id:300raise ValueError("Request not found.")301302params = {"request": self.request_id}303if body is not None:304params["body"] = body305if method is not None:306params["method"] = method307if headers is not None:308params["headers"] = headers309if cookies is not None:310params["cookies"] = cookies311if url is not None:312params["url"] = url313314self.network.conn.execute(command_builder("network.continueRequest", params))315316def _continue_with_auth(self, username=None, password=None):317"""Continue with authentication.318319Parameters:320----------321request (Request): The request to continue with.322username (str): The username to authenticate with.323password (str): The password to authenticate with.324325Notes:326-----327If username or password is None, it attempts auth with no credentials328"""329330params = {}331params["request"] = self.request_id332333if not username or not password: # no credentials is valid option334params["action"] = "default"335else:336params["action"] = "provideCredentials"337params["credentials"] = {"type": "password", "username": username, "password": password}338339self.network.conn.execute(command_builder("network.continueWithAuth", params))340341342