Path: blob/trunk/py/selenium/webdriver/remote/server.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 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/3637Parameters:38-----------39host : str40Hostname or IP address to bind to (determined automatically if not specified)41port : int or str42Port to listen on (4444 if not specified)43path : str44Path/filename of existing server .jar file (Selenium Manager is used if not specified)45version : str46Version of server to download (latest version if not specified)47log_level : str48Logging level to control logging output ("INFO" if not specified)49Available levels: "SEVERE", "WARNING", "INFO", "CONFIG", "FINE", "FINER", "FINEST"50env: collections.abc.Mapping51Mapping that defines the environment variables for the server process52"""5354def __init__(self, host=None, port=4444, path=None, version=None, log_level="INFO", env=None):55if path and version:56raise TypeError("Not allowed to specify a version when using an existing server path")5758self.host = host59self.port = port60self.path = path61self.version = version62self.log_level = log_level63self.env = env64self.process = None6566@property67def status_url(self):68host = self.host if self.host is not None else "localhost"69return f"http://{host}:{self.port}/status"7071@property72def path(self):73return self._path7475@path.setter76def path(self, path):77if path and not os.path.exists(path):78raise OSError(f"Can't find server .jar located at {path}")79self._path = path8081@property82def port(self):83return self._port8485@port.setter86def port(self, port):87try:88port = int(port)89except ValueError:90raise TypeError(f"{__class__.__name__}.__init__() got an invalid port: '{port}'")91if not (0 <= port <= 65535):92raise ValueError("port must be 0-65535")93self._port = port9495@property96def version(self):97return self._version9899@version.setter100def version(self, version):101if version:102if not re.match(r"^\d+\.\d+\.\d+$", str(version)):103raise TypeError(f"{__class__.__name__}.__init__() got an invalid version: '{version}'")104self._version = version105106@property107def log_level(self):108return self._log_level109110@log_level.setter111def log_level(self, log_level):112levels = ("SEVERE", "WARNING", "INFO", "CONFIG", "FINE", "FINER", "FINEST")113if log_level not in levels:114raise TypeError(f"log_level must be one of: {', '.join(levels)}")115self._log_level = log_level116117@property118def env(self):119return self._env120121@env.setter122def env(self, env):123if env is not None and not isinstance(env, collections.abc.Mapping):124raise TypeError("env must be a mapping of environment variables")125self._env = env126127def _wait_for_server(self, timeout=10):128start = time.time()129while time.time() - start < timeout:130try:131urllib.request.urlopen(self.status_url)132return True133except urllib.error.URLError:134time.sleep(0.2)135return False136137def download_if_needed(self, version=None):138"""Download the server if it doesn't already exist.139140Latest version is downloaded unless specified.141"""142args = ["--grid"]143if version is not None:144args.append(version)145return SeleniumManager().binary_paths(args)["driver_path"]146147def start(self):148"""Start the server.149150Selenium Manager will detect the server location and download it if necessary,151unless an existing server path was specified.152"""153path = self.download_if_needed(self.version) if self.path is None else self.path154155java_path = shutil.which("java")156if java_path is None:157raise OSError("Can't find java on system PATH. JRE is required to run the Selenium server")158159command = [160java_path,161"-jar",162path,163"standalone",164"--port",165str(self.port),166"--log-level",167self.log_level,168"--selenium-manager",169"true",170"--enable-managed-downloads",171"true",172]173if self.host is not None:174command.extend(["--host", self.host])175176host = self.host if self.host is not None else "localhost"177178try:179with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:180sock.connect((host, self.port))181raise ConnectionError(f"Selenium server is already running, or something else is using port {self.port}")182except ConnectionRefusedError:183print("Starting Selenium server...")184self.process = subprocess.Popen(command, env=self.env)185print(f"Selenium server running as process: {self.process.pid}")186if not self._wait_for_server():187raise TimeoutError(f"Timed out waiting for Selenium server at {self.status_url}")188print("Selenium server is ready")189return self.process190191def stop(self):192"""Stop the server."""193if self.process is None:194raise RuntimeError("Selenium server isn't running")195else:196if self.process.poll() is None:197self.process.terminate()198self.process.wait()199self.process = None200print("Selenium server has been terminated")201202203