Path: blob/trunk/py/selenium/webdriver/remote/server.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.1617import collections18import os19import re20import shutil21import socket22import subprocess23import time24import urllib2526from selenium.webdriver.common.selenium_manager import SeleniumManager272829class Server:30"""Manage a Selenium Grid (Remote) Server in standalone mode.3132This class contains functionality for downloading the server and starting/stopping it.3334For more information on Selenium Grid, see:35- https://www.selenium.dev/documentation/grid/getting_started/3637Args:38host: Hostname or IP address to bind to (determined automatically if not specified).39port: Port to listen on (4444 if not specified).40path: Path/filename of existing server .jar file (Selenium Manager is used if not specified).41version: Version of server to download (latest version if not specified).42log_level: Logging level to control logging output ("INFO" if not specified).43Available levels: "SEVERE", "WARNING", "INFO", "CONFIG", "FINE", "FINER", "FINEST".44env: Mapping that defines the environment variables for the server process.45java_path: Path to the java executable to run the server.46"""4748def __init__(49self,50host=None,51port=4444,52path=None,53version=None,54log_level="INFO",55env=None,56java_path=None,57startup_timeout=10,58):59if path and version:60raise TypeError("Not allowed to specify a version when using an existing server path")6162self.host = host63self.port = port64self.path = path65self.version = version66self.log_level = log_level67self.env = env68self.java_path = java_path69self.startup_timeout = startup_timeout70self.process = None7172@property73def startup_timeout(self):74return self._startup_timeout7576@startup_timeout.setter77def startup_timeout(self, timeout):78self._startup_timeout = int(timeout)7980@property81def status_url(self):82host = self.host if self.host is not None else "localhost"83return f"http://{host}:{self.port}/status"8485@property86def path(self):87return self._path8889@path.setter90def path(self, path):91if path and not os.path.exists(path):92raise OSError(f"Can't find server .jar located at {path}")93self._path = path9495@property96def port(self):97return self._port9899@port.setter100def port(self, port):101try:102port = int(port)103except ValueError:104raise TypeError(f"{__class__.__name__}.__init__() got an invalid port: '{port}'")105if not (0 <= port <= 65535):106raise ValueError("port must be 0-65535")107self._port = port108109@property110def version(self):111return self._version112113@version.setter114def version(self, version):115if version:116if not re.match(r"^\d+\.\d+\.\d+$", str(version)):117raise TypeError(f"{__class__.__name__}.__init__() got an invalid version: '{version}'")118self._version = version119120@property121def log_level(self):122return self._log_level123124@log_level.setter125def log_level(self, log_level):126levels = ("SEVERE", "WARNING", "INFO", "CONFIG", "FINE", "FINER", "FINEST")127if log_level not in levels:128raise TypeError(f"log_level must be one of: {', '.join(levels)}")129self._log_level = log_level130131@property132def env(self):133return self._env134135@env.setter136def env(self, env):137if env is not None and not isinstance(env, collections.abc.Mapping):138raise TypeError("env must be a mapping of environment variables")139self._env = env140141@property142def java_path(self):143return self._java_path144145@java_path.setter146def java_path(self, java_path):147if java_path and not os.path.exists(java_path):148raise OSError(f"Can't find java executable located at {java_path}")149self._java_path = java_path150151def _wait_for_server(self, timeout=10):152start = time.time()153while time.time() - start < timeout:154try:155urllib.request.urlopen(self.status_url)156return True157except urllib.error.URLError:158time.sleep(0.2)159return False160161def download_if_needed(self, version=None):162"""Download the server if it doesn't already exist.163164Latest version is downloaded unless specified.165"""166args = ["--grid"]167if version is not None:168args.append(version)169return SeleniumManager().binary_paths(args)["driver_path"]170171def start(self):172"""Start the server.173174Selenium Manager will detect the server location and download it if necessary,175unless an existing server path was specified.176"""177path = self.download_if_needed(self.version) if self.path is None else self.path178179java_path = self.java_path or shutil.which("java")180if java_path is None:181raise OSError("Can't find java on system PATH. JRE is required to run the Selenium server")182183command = [184java_path,185"-jar",186path,187"standalone",188"--port",189str(self.port),190"--log-level",191self.log_level,192"--selenium-manager",193"true",194"--enable-managed-downloads",195"true",196]197if self.host is not None:198command.extend(["--host", self.host])199200host = self.host if self.host is not None else "localhost"201202try:203with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:204sock.connect((host, self.port))205raise ConnectionError(f"Selenium server is already running, or something else is using port {self.port}")206except ConnectionRefusedError:207print("Starting Selenium server...")208self.process = subprocess.Popen(command, env=self.env)209print(f"Selenium server running as process: {self.process.pid}")210if not self._wait_for_server(timeout=self.startup_timeout):211raise TimeoutError(f"Timed out waiting for Selenium server at {self.status_url}")212print("Selenium server is ready")213return self.process214215def stop(self):216"""Stop the server."""217if self.process is None:218raise RuntimeError("Selenium server isn't running")219else:220if self.process.poll() is None:221self.process.terminate()222self.process.wait()223self.process = None224print("Selenium server has been terminated")225226227