Skip to content

Commit 77eb0a6

Browse files
committed
[py] Add class to manage remote/grid server
1 parent 6c18b0f commit 77eb0a6

File tree

1 file changed

+128
-0
lines changed

1 file changed

+128
-0
lines changed
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
# Licensed to the Software Freedom Conservancy (SFC) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The SFC licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
18+
import os
19+
import re
20+
import shutil
21+
import socket
22+
import subprocess
23+
import time
24+
import urllib
25+
26+
from selenium.webdriver.common.selenium_manager import SeleniumManager
27+
28+
29+
class Server:
30+
"""Manage the Selenium Remote (Grid) Server.
31+
32+
Parameters:
33+
-----------
34+
host : str
35+
Hostname or IP address to bind to.
36+
port : str
37+
Port to listen on.
38+
path : str
39+
Path/filename of existing server .jar file (if not specified, server will be downloaded).
40+
version : str
41+
Version of server to download (if not specified, latest will be downloaded).
42+
env: dict
43+
Environment variables passed to server environment.
44+
"""
45+
46+
def __init__(self, host="localhost", port="4444", path=None, version=None, env=None):
47+
if path and version:
48+
raise TypeError("Not allowed to specify a version when using an existing server path")
49+
50+
self.host = host
51+
self.port = port
52+
self.path = self._validate_path(path)
53+
self.version = self._validate_version(version)
54+
self.env = env
55+
56+
self.process = None
57+
self.status_url = f"http://{self.host}:{self.port}/status"
58+
59+
def _validate_path(self, path):
60+
if path and not os.path.exists(path):
61+
raise OSError(f"Can't find server .jar located at {path}")
62+
return path
63+
64+
def _validate_version(self, version):
65+
if version:
66+
if not re.match(r"^\d+\.\d+\.\d+$", str(version)):
67+
raise TypeError(f"{__class__}.__init__() invalid version argument: '{version}'")
68+
return version
69+
70+
def _wait_for_server(self, timeout=10):
71+
start = time.time()
72+
while time.time() - start < timeout:
73+
try:
74+
urllib.request.urlopen(self.status_url)
75+
return True
76+
except urllib.error.URLError:
77+
time.sleep(0.2)
78+
return False
79+
80+
def start(self):
81+
"""Start the server."""
82+
if not self.path:
83+
selenium_manager = SeleniumManager()
84+
args = ["--grid"]
85+
if self.version:
86+
args.append(self.version)
87+
self.path = selenium_manager.binary_paths(args)["driver_path"]
88+
java = shutil.which("java")
89+
if java is None:
90+
raise OSError("Can't find java on system PATH. JRE is required to run the Selenium server")
91+
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
92+
try:
93+
sock.connect((self.host, int(self.port)))
94+
raise ConnectionError(
95+
f"The remote driver server is already running or something else is using port {self.port}"
96+
)
97+
except ConnectionRefusedError:
98+
print("Starting Selenium server")
99+
self.process = subprocess.Popen(
100+
[
101+
"java",
102+
"-jar",
103+
self.path,
104+
"standalone",
105+
"--port",
106+
self.port,
107+
"--selenium-manager",
108+
"true",
109+
"--enable-managed-downloads",
110+
"true",
111+
],
112+
env=self.env,
113+
)
114+
print(f"Selenium server running as process: {self.process.pid}")
115+
if not self._wait_for_server():
116+
f"Timed out waiting for Selenium server at {self.status_url}"
117+
print("Selenium server is ready")
118+
return self.process
119+
120+
def stop(self):
121+
"""Stop the server."""
122+
if self.process is None:
123+
raise RuntimeError("Selenium server isn't running")
124+
else:
125+
self.process.terminate()
126+
self.process.wait()
127+
self.process = None
128+
print("Selenium server has been terminated")

0 commit comments

Comments
 (0)