Path: blob/trunk/py/selenium/webdriver/common/utils.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.16"""The Utils methods."""1718import socket19from collections.abc import Iterable20from typing import Optional, Union2122from selenium.types import AnyKey23from selenium.webdriver.common.keys import Keys2425_is_connectable_exceptions = (socket.error, ConnectionResetError)262728def free_port() -> int:29"""Determines a free port using sockets.3031First try IPv4, but use IPv6 if it can't bind (IPv6-only system).32"""33free_socket = None34try:35# IPv436free_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)37free_socket.bind(("127.0.0.1", 0))38except OSError:39if free_socket:40free_socket.close()41# IPv642try:43free_socket = socket.socket(socket.AF_INET6, socket.SOCK_STREAM)44free_socket.bind(("::1", 0))45except OSError:46if free_socket:47free_socket.close()48raise RuntimeError("Can't find free port (Unable to bind to IPv4 or IPv6)")49try:50port: int = free_socket.getsockname()[1]51except Exception as e:52raise RuntimeError(f"Can't find free port: ({e})")53finally:54free_socket.close()55return port565758def find_connectable_ip(host: Union[str, bytes, bytearray, None], port: Optional[int] = None) -> Optional[str]:59"""Resolve a hostname to an IP, preferring IPv4 addresses.6061We prefer IPv4 so that we don't change behavior from previous IPv4-only62implementations, and because some drivers (e.g., FirefoxDriver) do not63support IPv6 connections.6465If the optional port number is provided, only IPs that listen on the given66port are considered.6768:Args:69- host - A hostname.70- port - Optional port number.7172:Returns:73A single IP address, as a string. If any IPv4 address is found, one is74returned. Otherwise, if any IPv6 address is found, one is returned. If75neither, then None is returned.76"""77try:78addrinfos = socket.getaddrinfo(host, None)79except socket.gaierror:80return None8182ip = None83for family, _, _, _, sockaddr in addrinfos:84connectable = True85if port:86connectable = is_connectable(port, str(sockaddr[0]))8788if connectable and family == socket.AF_INET:89return str(sockaddr[0])90if connectable and not ip and family == socket.AF_INET6:91ip = str(sockaddr[0])92return ip939495def join_host_port(host: str, port: int) -> str:96"""Joins a hostname and port together.9798This is a minimal implementation intended to cope with IPv6 literals. For99example, _join_host_port('::1', 80) == '[::1]:80'.100101:Args:102- host - A hostname.103- port - An integer port.104"""105if ":" in host and not host.startswith("["):106return f"[{host}]:{port}"107return f"{host}:{port}"108109110def is_connectable(port: int, host: Optional[str] = "localhost") -> bool:111"""Tries to connect to the server at port to see if it is running.112113:Args:114- port - The port to connect.115"""116socket_ = None117try:118socket_ = socket.create_connection((host, port), 1)119result = True120except _is_connectable_exceptions:121result = False122finally:123if socket_:124try:125socket_.shutdown(socket.SHUT_RDWR)126except Exception:127pass128socket_.close()129return result130131132def is_url_connectable(port: Union[int, str]) -> bool:133"""Tries to connect to the HTTP server at /status path and specified port134to see if it responds successfully.135136:Args:137- port - The port to connect.138"""139from urllib import request as url_request140141try:142res = url_request.urlopen(f"http://127.0.0.1:{port}/status")143return res.getcode() == 200144except Exception:145return False146147148def keys_to_typing(value: Iterable[AnyKey]) -> list[str]:149"""Processes the values that will be typed in the element."""150characters: list[str] = []151for val in value:152if isinstance(val, Keys):153# Todo: Does this even work?154characters.append(str(val))155elif isinstance(val, (int, float)):156characters.extend(str(val))157else:158characters.extend(val)159return characters160161162