Skip to content

[py][bidi]: support accept_insecure_certs and proxy parameters in create_user_context #15983

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: trunk
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions py/selenium/webdriver/common/bidi/browser.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,10 @@
# under the License.


from typing import Optional

from selenium.webdriver.common.bidi.common import command_builder
from selenium.webdriver.common.proxy import Proxy


class ClientWindowState:
Expand Down Expand Up @@ -182,14 +185,27 @@ class Browser:
def __init__(self, conn):
self.conn = conn

def create_user_context(self) -> str:
def create_user_context(self, accept_insecure_certs: Optional[bool] = None, proxy: Optional[Proxy] = None) -> str:
"""Creates a new user context.

Parameters:
-----------
accept_insecure_certs: Optional flag to accept insecure TLS certificates
proxy: Optional proxy configuration for the user context

Returns:
-------
str: The ID of the created user context.
"""
result = self.conn.execute(command_builder("browser.createUserContext", {}))
params = {}

if accept_insecure_certs is not None:
params["acceptInsecureCerts"] = accept_insecure_certs

if proxy is not None:
params["proxy"] = proxy.to_bidi_dict()

result = self.conn.execute(command_builder("browser.createUserContext", params))
return result["userContext"]

def get_user_contexts(self) -> list[str]:
Expand Down
36 changes: 36 additions & 0 deletions py/selenium/webdriver/common/proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -325,3 +325,39 @@ def to_capabilities(self):
if attr_value:
proxy_caps[proxy] = attr_value
return proxy_caps

def to_bidi_dict(self):
"""Convert proxy settings to BiDi format.

Returns:
-------
dict: Proxy configuration in BiDi format.
"""
proxy_type = self.proxyType["string"].lower()
result = {"proxyType": proxy_type}

if proxy_type == "manual":
if self.httpProxy:
result["httpProxy"] = self.httpProxy
if self.sslProxy:
result["sslProxy"] = self.sslProxy
if self.socksProxy:
result["socksProxy"] = self.socksProxy
if self.socksVersion is not None:
result["socksVersion"] = self.socksVersion
if self.noProxy:
# Convert comma-separated string to list
if isinstance(self.noProxy, str):
result["noProxy"] = [host.strip() for host in self.noProxy.split(",") if host.strip()]
elif isinstance(self.noProxy, list):
if not all(isinstance(h, str) for h in self.noProxy):
raise TypeError("no_proxy list must contain only strings")
result["noProxy"] = self.noProxy
else:
raise TypeError("no_proxy must be a comma-separated string or a list of strings")

elif proxy_type == "pac":
if self.proxyAutoconfigUrl:
result["proxyAutoconfigUrl"] = self.proxyAutoconfigUrl

return result
144 changes: 144 additions & 0 deletions py/test/selenium/webdriver/common/bidi_browser_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,38 @@
# specific language governing permissions and limitations
# under the License.

import http.server
import socket
import socketserver
import threading

import pytest

from selenium.webdriver.common.bidi.browser import ClientWindowInfo, ClientWindowState
from selenium.webdriver.common.by import By
from selenium.webdriver.common.proxy import Proxy, ProxyType
from selenium.webdriver.common.window import WindowTypes


def get_free_port():
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind(("", 0))
return s.getsockname()[1]


class FakeProxyHandler(http.server.SimpleHTTPRequestHandler):
def do_GET(self):
print(f"[Fake Proxy] Intercepted request to: {self.path}")
self.send_response(200)
self.end_headers()
self.wfile.write(b"proxied response")


def start_fake_proxy(port):
server = socketserver.TCPServer(("localhost", port), FakeProxyHandler)
thread = threading.Thread(target=server.serve_forever, daemon=True)
thread.start()
return server


def test_browser_initialized(driver):
Expand Down Expand Up @@ -95,3 +124,118 @@ def test_client_window_state_constants(driver):
assert ClientWindowState.MAXIMIZED == "maximized"
assert ClientWindowState.MINIMIZED == "minimized"
assert ClientWindowState.NORMAL == "normal"


def test_create_user_context_with_accept_insecure_certs(driver):
"""Test creating a user context with accept_insecure_certs parameter."""
INSECURE_TEST_SITE = "https://self-signed.badssl.com/"
user_context = driver.browser.create_user_context(accept_insecure_certs=True)

bc = driver.browsing_context.create(type=WindowTypes.WINDOW, user_context=user_context)
driver.switch_to.window(bc)
assert user_context is not None
assert bc is not None

driver.get(INSECURE_TEST_SITE)

h1 = driver.find_element(By.TAG_NAME, "h1")
assert h1.text.strip() == "self-signed.\nbadssl.com"

# Clean up
driver.browser.remove_user_context(user_context)


def test_create_user_context_with_direct_proxy(driver):
"""Test creating a user context with direct proxy configuration."""
proxy = Proxy()
proxy.proxy_type = ProxyType.DIRECT

user_context = driver.browser.create_user_context(proxy=proxy)
assert user_context is not None

bc = driver.browsing_context.create(type=WindowTypes.WINDOW, user_context=user_context)
driver.switch_to.window(bc)

# Visiting a site should load directly without proxy
driver.get("http://example.com/")
body_text = driver.find_element(By.TAG_NAME, "body").text.lower()
assert "example domain" in body_text

# Clean up
driver.browser.remove_user_context(user_context)


@pytest.mark.xfail_firefox(reason="Firefox proxy settings are different")
@pytest.mark.xfail_remote
def test_create_user_context_with_manual_proxy_all_params(driver):
"""Test creating a user context with manual proxy configuration."""
# Start a fake proxy server
port = get_free_port()
fake_proxy_server = start_fake_proxy(port=port)

proxy = Proxy()
proxy.proxy_type = ProxyType.MANUAL
proxy.http_proxy = f"localhost:{port}"
proxy.ssl_proxy = f"localhost:{port}"
proxy.socks_proxy = f"localhost:{port}"
proxy.socks_version = 5
proxy.no_proxy = ["the-internet.herokuapp.com"]

user_context = driver.browser.create_user_context(proxy=proxy)

# Create and switch to a new browsing context using this proxy
bc = driver.browsing_context.create(type=WindowTypes.WINDOW, user_context=user_context)
driver.switch_to.window(bc)

try:
# Visit no proxy site, it should bypass proxy
driver.get("http://the-internet.herokuapp.com/")
body_text = driver.find_element(By.TAG_NAME, "body").text.lower()
assert "welcome to the-internet" in body_text

# Visit a site that should be proxied
driver.get("http://example.com/")

body_text = driver.find_element("tag name", "body").text
assert "proxied response" in body_text.lower()

finally:
driver.browser.remove_user_context(user_context)
fake_proxy_server.shutdown()
fake_proxy_server.server_close()


@pytest.mark.xfail_firefox(reason="Firefox proxy settings are different")
@pytest.mark.xfail_remote
def test_create_user_context_with_both_params(driver):
"""Test creating a user context with both acceptInsecureCerts and proxy parameters."""
# Start fake proxy server
port = get_free_port()
fake_proxy_server = start_fake_proxy(port=port)

proxy = Proxy()
proxy.proxy_type = ProxyType.MANUAL
proxy.http_proxy = f"localhost:{port}"
proxy.ssl_proxy = f"localhost:{port}"
proxy.no_proxy = ["self-signed.badssl.com"]

user_context = driver.browser.create_user_context(accept_insecure_certs=True, proxy=proxy)

bc = driver.browsing_context.create(type=WindowTypes.WINDOW, user_context=user_context)
driver.switch_to.window(bc)

try:
# Visit a site with an invalid certificate
driver.get("https://self-signed.badssl.com/")
h1 = driver.find_element(By.TAG_NAME, "h1")
assert "badssl.com" in h1.text.lower()

# Visit a site that should go through the fake proxy
driver.get("http://example.com/")
body_text = driver.find_element(By.TAG_NAME, "body").text
assert "proxied response" in body_text.lower()

finally:
driver.browser.remove_user_context(user_context)
fake_proxy_server.shutdown()
fake_proxy_server.server_close()
Loading