From 227d06999ffe67d2155475550528e195c380b6cb Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Mon, 12 Oct 2026 15:32:06 -0700 Subject: [PATCH 1/2] fix(network): do not add missing headers when looking them up Fixes: microsoft/playwright#43306 --- playwright/_impl/_network.py | 8 ++++---- tests/async/test_network.py | 18 ++++++++++++++++++ tests/sync/test_network.py | 18 ++++++++++++++++++ 3 files changed, 40 insertions(+), 4 deletions(-) diff --git a/playwright/_impl/_network.py b/playwright/_impl/_network.py index 3e5b8fe89..229883657 100644 --- a/playwright/_impl/_network.py +++ b/playwright/_impl/_network.py @@ -18,7 +18,6 @@ import json as json_utils import mimetypes import re -from collections import defaultdict from pathlib import Path from types import SimpleNamespace from typing import ( @@ -1031,9 +1030,10 @@ def _on_close(self) -> None: class RawHeaders: def __init__(self, headers: HeadersArray) -> None: self._headers_array = headers - self._headers_map: Dict[str, Dict[str, bool]] = defaultdict(dict) + self._headers_map: Dict[str, Dict[str, bool]] = {} for header in headers: - self._headers_map[header["name"].lower()][header["value"]] = True + values = self._headers_map.setdefault(header["name"].lower(), {}) + values[header["value"]] = True @staticmethod def _from_headers_dict_lossy(headers: Dict[str, str]) -> "RawHeaders": @@ -1047,7 +1047,7 @@ def get(self, name: str) -> Optional[str]: return separator.join(values) def get_all(self, name: str) -> List[str]: - return list(self._headers_map[name.lower()].keys()) + return list(self._headers_map.get(name.lower(), {}).keys()) def headers(self) -> Dict[str, str]: result = {} diff --git a/tests/async/test_network.py b/tests/async/test_network.py index e88b24c24..9a2f5d388 100644 --- a/tests/async/test_network.py +++ b/tests/async/test_network.py @@ -371,6 +371,24 @@ async def test_response_headers_should_work(page: Page, server: Server) -> None: assert (await response.all_headers())["foo"] == "bar" +async def test_header_lookup_should_not_add_missing_headers( + page: Page, server: Server +) -> None: + server.set_route("/empty.html", lambda r: (r.setHeader("foo", "bar"), r.finish())) + + response = await page.goto(server.EMPTY_PAGE) + assert response + assert await response.header_value("x-missing") is None + assert await response.header_values("x-missing-too") == [] + headers = await response.all_headers() + assert headers["foo"] == "bar" + assert "x-missing" not in headers + assert "x-missing-too" not in headers + + assert await response.request.header_value("x-missing") is None + assert "x-missing" not in await response.request.all_headers() + + async def test_request_post_data_should_work(page: Page, server: Server) -> None: await page.goto(server.EMPTY_PAGE) server.set_route("/post", lambda r: r.finish()) diff --git a/tests/sync/test_network.py b/tests/sync/test_network.py index 8c196affb..dd716552b 100644 --- a/tests/sync/test_network.py +++ b/tests/sync/test_network.py @@ -114,3 +114,21 @@ def test_should_report_service_worker_request(page: Page, server: Server) -> Non ) as request_info: page.evaluate("() => fetch('/example.txt')") assert request_info.value.service_worker == worker_info.value + + +def test_header_lookup_should_not_add_missing_headers( + page: Page, server: Server +) -> None: + server.set_route("/empty.html", lambda r: (r.setHeader("foo", "bar"), r.finish())) + + response = page.goto(server.EMPTY_PAGE) + assert response + assert response.header_value("x-missing") is None + assert response.header_values("x-missing-too") == [] + headers = response.all_headers() + assert headers["foo"] == "bar" + assert "x-missing" not in headers + assert "x-missing-too" not in headers + + assert response.request.header_value("x-missing") is None + assert "x-missing" not in response.request.all_headers() From 6b3a7c95377cf5701dccdbe7368637e6f59f8072 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Mon, 12 Oct 2026 16:52:02 -0700 Subject: [PATCH 2/2] test: fold the missing header check into the existing test --- tests/async/test_network.py | 19 ++----------------- tests/sync/test_network.py | 18 ------------------ 2 files changed, 2 insertions(+), 35 deletions(-) diff --git a/tests/async/test_network.py b/tests/async/test_network.py index 9a2f5d388..b03490ef6 100644 --- a/tests/async/test_network.py +++ b/tests/async/test_network.py @@ -369,24 +369,9 @@ async def test_response_headers_should_work(page: Page, server: Server) -> None: assert response assert response.headers["foo"] == "bar" assert (await response.all_headers())["foo"] == "bar" - - -async def test_header_lookup_should_not_add_missing_headers( - page: Page, server: Server -) -> None: - server.set_route("/empty.html", lambda r: (r.setHeader("foo", "bar"), r.finish())) - - response = await page.goto(server.EMPTY_PAGE) - assert response + # Looking up a missing header must not add it. assert await response.header_value("x-missing") is None - assert await response.header_values("x-missing-too") == [] - headers = await response.all_headers() - assert headers["foo"] == "bar" - assert "x-missing" not in headers - assert "x-missing-too" not in headers - - assert await response.request.header_value("x-missing") is None - assert "x-missing" not in await response.request.all_headers() + assert "x-missing" not in await response.all_headers() async def test_request_post_data_should_work(page: Page, server: Server) -> None: diff --git a/tests/sync/test_network.py b/tests/sync/test_network.py index dd716552b..8c196affb 100644 --- a/tests/sync/test_network.py +++ b/tests/sync/test_network.py @@ -114,21 +114,3 @@ def test_should_report_service_worker_request(page: Page, server: Server) -> Non ) as request_info: page.evaluate("() => fetch('/example.txt')") assert request_info.value.service_worker == worker_info.value - - -def test_header_lookup_should_not_add_missing_headers( - page: Page, server: Server -) -> None: - server.set_route("/empty.html", lambda r: (r.setHeader("foo", "bar"), r.finish())) - - response = page.goto(server.EMPTY_PAGE) - assert response - assert response.header_value("x-missing") is None - assert response.header_values("x-missing-too") == [] - headers = response.all_headers() - assert headers["foo"] == "bar" - assert "x-missing" not in headers - assert "x-missing-too" not in headers - - assert response.request.header_value("x-missing") is None - assert "x-missing" not in response.request.all_headers()