Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
SeleniumHQ
GitHub Repository: SeleniumHQ/Selenium
Path: blob/trunk/py/selenium/webdriver/common/utils.py
4049 views
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
"""Utility functions."""
19
20
import json
21
import socket
22
import urllib.request
23
from collections.abc import Iterable
24
25
from selenium.webdriver.common.keys import Keys
26
27
_is_connectable_exceptions = (socket.error, ConnectionResetError)
28
29
30
def free_port() -> int:
31
"""Determines a free port using sockets.
32
33
First try IPv4, but use IPv6 if it can't bind (IPv6-only system).
34
"""
35
free_socket = None
36
try:
37
# IPv4
38
free_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
39
free_socket.bind(("127.0.0.1", 0))
40
except OSError:
41
if free_socket:
42
free_socket.close()
43
# IPv6
44
try:
45
free_socket = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)
46
free_socket.bind(("::1", 0))
47
except OSError:
48
if free_socket:
49
free_socket.close()
50
raise RuntimeError("Can't find free port (Unable to bind to IPv4 or IPv6)")
51
try:
52
port: int = free_socket.getsockname()[1]
53
except Exception as e:
54
raise RuntimeError(f"Can't find free port: ({e})")
55
finally:
56
free_socket.close()
57
return port
58
59
60
def find_connectable_ip(host: str | bytes | bytearray | None, port: int | None = None) -> str | None:
61
"""Resolve a hostname to an IP, preferring IPv4 addresses.
62
63
We prefer IPv4 so that we don't change behavior from previous IPv4-only
64
implementations, and because some drivers (e.g., FirefoxDriver) do not
65
support IPv6 connections.
66
67
If the optional port number is provided, only IPs that listen on the given
68
port are considered.
69
70
Args:
71
host: hostname
72
port: port number
73
74
Returns:
75
A single IP address, as a string. If any IPv4 address is found, one is
76
returned. Otherwise, if any IPv6 address is found, one is returned. If
77
neither, then None is returned.
78
"""
79
try:
80
addrinfos = socket.getaddrinfo(host, None)
81
except socket.gaierror:
82
return None
83
84
ip = None
85
for family, _, _, _, sockaddr in addrinfos:
86
connectable = True
87
if port:
88
connectable = is_connectable(port, str(sockaddr[0]))
89
90
if connectable and family == socket.AF_INET:
91
return str(sockaddr[0])
92
if connectable and not ip and family == socket.AF_INET6:
93
ip = str(sockaddr[0])
94
return ip
95
96
97
def join_host_port(host: str, port: int) -> str:
98
"""Joins a hostname and port together.
99
100
This is a minimal implementation intended to cope with IPv6 literals. For
101
example, _join_host_port('::1', 80) == '[::1]:80'.
102
103
Args:
104
host: hostname or IP
105
port: port number
106
"""
107
if ":" in host and not host.startswith("["):
108
return f"[{host}]:{port}"
109
return f"{host}:{port}"
110
111
112
def is_connectable(port: int, host: str | None = "localhost") -> bool:
113
"""Tries to connect to the server at port to see if it is running.
114
115
Args:
116
port: port number
117
host: hostname or IP
118
"""
119
socket_ = None
120
try:
121
socket_ = socket.create_connection((host, port), 1)
122
result = True
123
except _is_connectable_exceptions:
124
result = False
125
finally:
126
if socket_:
127
try:
128
socket_.shutdown(socket.SHUT_RDWR)
129
except Exception:
130
pass
131
socket_.close()
132
return result
133
134
135
def is_url_connectable(
136
port: int | str,
137
host: str = "localhost",
138
scheme: str = "http",
139
) -> bool:
140
"""Send a request to the HTTP server at the /status endpoint to verify connectivity.
141
142
Args:
143
port: port number
144
host: hostname or IP
145
scheme: URL scheme
146
147
Returns:
148
True if the service is ready to accept new sessions, False otherwise.
149
"""
150
try:
151
# Disable proxy for localhost connections
152
proxy_handler = urllib.request.ProxyHandler({})
153
opener = urllib.request.build_opener(proxy_handler)
154
155
request = urllib.request.Request(f"{scheme}://{host}:{port}/status")
156
with opener.open(request, timeout=1) as res:
157
if res.getcode() != 200:
158
return False
159
160
body = res.read().decode("utf-8")
161
data = json.loads(body)
162
163
# Check top-level and value.ready, some browsers wrap it under 'value', e.g., ChromeDriver
164
ready = data.get("ready")
165
if ready is None:
166
ready = data.get("value", {}).get("ready")
167
return ready is True
168
except Exception:
169
return False
170
171
172
def keys_to_typing(value: Iterable[str | int | float]) -> list[str]:
173
"""Processes the values that will be typed in the element."""
174
characters: list[str] = []
175
for val in value:
176
if isinstance(val, Keys):
177
# Todo: Does this even work?
178
characters.append(str(val))
179
elif isinstance(val, (int, float)):
180
characters.extend(str(val))
181
else:
182
characters.extend(val)
183
return characters
184
185