Skip to content
Merged
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
68 changes: 50 additions & 18 deletions py/private/_network_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -355,24 +355,42 @@ def provide_response(self, status=None, headers=None, body=None, reason_phrase=N
self._stub = stub
self._execute_provide_response()

def continue_request(self, **kwargs) -> None:
def continue_request(
self,
*,
url: str | None = None,
method: str | None = None,
headers: dict[str, Any] | None = None,
cookies: list | None = None,
body: str | None = None,
) -> None:
"""Continue the intercepted request, applying any recorded mutations.

Keyword arguments are passed through to ``network.continueRequest`` and
override recorded mutations. Data URLs (``data:``) are skipped silently
because browsers do not create an interceptable request entry for them,
so calling ``network.continueRequest`` would raise "no such request".
Each keyword argument overrides the corresponding mutation recorded via
``set_url``/``set_method``/``set_headers``/``set_cookies``/``set_body``.
Arguments use the same Python types as those setters and are translated
to the BiDi wire format automatically. Data URLs (``data:``) are
skipped silently because browsers do not create an interceptable request
entry for them, so calling ``network.continueRequest`` would raise
"no such request".

Args:
url: Replacement request URL.
method: Replacement HTTP method.
headers: Replacement request headers as a name β†’ value dict.
cookies: Replacement request cookies as a list of dicts.
body: Replacement request body string.
"""
self._handled = True
if self.url.startswith("data:"):
return
params = self._continue_params()
params.update(kwargs)
overrides = {"url": url, "method": method, "headers": headers, "cookies": cookies, "body": body}
params = self._continue_params({k: v for k, v in overrides.items() if v is not None})
self._conn.execute(command_builder("network.continueRequest", params))

def _continue_params(self) -> dict:
def _continue_params(self, overrides: dict | None = None) -> dict:
params: dict[str, Any] = {"request": self._request_id}
mutations = self._mutations
mutations = {**self._mutations, **(overrides or {})}
if "url" in mutations:
params["url"] = mutations["url"]
if "method" in mutations:
Expand Down Expand Up @@ -483,24 +501,38 @@ def set_body(self, body: str) -> None:
self.body = body
self._mutations["body"] = body

def continue_response(self, **kwargs) -> None:
def continue_response(
self,
*,
status: int | None = None,
reason_phrase: str | None = None,
headers: dict[str, Any] | None = None,
cookies: list | None = None,
) -> None:
"""Continue the intercepted response, applying any recorded mutations.

Keyword arguments are passed through to ``network.continueResponse``
and override recorded mutations. Data URLs (``data:``) are skipped
silently because browsers do not create an interceptable entry for
them.
Each keyword argument overrides the corresponding mutation recorded via
``set_status``/``set_headers``/``set_cookies``. Arguments use the same
Python types as those setters and are translated to the BiDi wire format
automatically. Data URLs (``data:``) are skipped silently because
browsers do not create an interceptable entry for them.

Args:
status: Replacement HTTP status code.
reason_phrase: Replacement HTTP reason phrase.
headers: Replacement response headers as a name β†’ value dict.
cookies: Replacement set-cookie entries as a list of dicts.
"""
self._handled = True
if self.url.startswith("data:"):
return
params = self._continue_params()
params.update(kwargs)
overrides = {"status": status, "reason_phrase": reason_phrase, "headers": headers, "cookies": cookies}
params = self._continue_params({k: v for k, v in overrides.items() if v is not None})
self._conn.execute(command_builder("network.continueResponse", params))

def _continue_params(self) -> dict:
def _continue_params(self, overrides: dict | None = None) -> dict:
params: dict[str, Any] = {"request": self._request_id}
mutations = self._mutations
mutations = {**self._mutations, **(overrides or {})}
if "status" in mutations:
params["statusCode"] = mutations["status"]
if "reason_phrase" in mutations:
Expand Down
67 changes: 67 additions & 0 deletions py/test/unit/selenium/webdriver/common/bidi_network_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,73 @@ def test_continue_request_sends_command_for_regular_urls():
assert conn.commands[0]["params"]["request"] == "request-id-2"


def test_continue_request_translates_explicit_args_to_wire_format():
conn = FakeConnection()
params = {"request": {"url": "https://example.com/api", "request": "request-id-3"}}
request = Request(conn, params)

request.continue_request(
url="https://example.com/redirected",
method="POST",
headers={"x-test": "1"},
cookies=[{"name": "sid", "value": "abc"}],
body="payload",
)

sent = conn.commands_named("network.continueRequest")[0]["params"]
assert sent["url"] == "https://example.com/redirected"
assert sent["method"] == "POST"
assert sent["headers"] == [{"name": "x-test", "value": {"type": "string", "value": "1"}}]
assert sent["cookies"] == [{"name": "sid", "value": {"type": "string", "value": "abc"}}]
assert sent["body"] == {"type": "string", "value": "payload"}


def test_continue_request_explicit_args_override_recorded_mutations():
conn = FakeConnection()
params = {"request": {"url": "https://example.com/api", "request": "request-id-4"}}
request = Request(conn, params)

# A recorded mutation on a different field must survive when only one
# field is overridden via the keyword argument.
request.set_url("https://example.com/recorded")
request.set_method("GET")
request.continue_request(method="DELETE")

sent = conn.commands_named("network.continueRequest")[0]["params"]
assert sent["method"] == "DELETE"
assert sent["url"] == "https://example.com/recorded"


def test_continue_request_keeps_falsy_body_override():
conn = FakeConnection()
params = {"request": {"url": "https://example.com/api", "request": "request-id-6"}}
request = Request(conn, params)

# An empty-string body is a valid value, not "unset": it must not be
# dropped by the None filter.
request.continue_request(body="")

sent = conn.commands_named("network.continueRequest")[0]["params"]
assert sent["body"] == {"type": "string", "value": ""}


def test_continue_response_translates_explicit_args_to_wire_format():
conn = FakeConnection()
params = {"request": {"request": "request-id-5"}, "response": {"url": "https://example.com/api"}}
response = Response(conn, params)

response.continue_response(
status=503,
reason_phrase="Service Unavailable",
headers={"x-test": "1"},
)

sent = conn.commands_named("network.continueResponse")[0]["params"]
assert sent["statusCode"] == 503
assert sent["reasonPhrase"] == "Service Unavailable"
assert sent["headers"] == [{"name": "x-test", "value": {"type": "string", "value": "1"}}]


def test_request_parses_event_properties():
request = Request(FakeConnection(), make_before_request_event())

Expand Down
Loading