diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 774b001ec..eabece583 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -22,7 +22,7 @@ repos: rev: v1.5.1 hooks: - id: mypy - additional_dependencies: [types-pyOpenSSL==23.2.0.2] + additional_dependencies: [types-pyOpenSSL==23.2.0.2, types-requests==2.31.0.10] - repo: https://github.com/pycqa/flake8 rev: 6.1.0 hooks: diff --git a/local-requirements.txt b/local-requirements.txt index 68edf4cb1..4a4a27ada 100644 --- a/local-requirements.txt +++ b/local-requirements.txt @@ -20,4 +20,5 @@ service_identity==23.1.0 setuptools==68.2.2 twisted==23.10.0 types-pyOpenSSL==23.2.0.2 +types-requests==2.31.0.10 wheel==0.41.2 diff --git a/playwright/_impl/_locator.py b/playwright/_impl/_locator.py index 3f9fa5ce3..d18d0d5de 100644 --- a/playwright/_impl/_locator.py +++ b/playwright/_impl/_locator.py @@ -15,7 +15,6 @@ import json import pathlib import sys -from collections import ChainMap from typing import ( TYPE_CHECKING, Any, @@ -528,7 +527,7 @@ async def screenshot( params = locals_to_params(locals()) return await self._with_element( lambda h, timeout: h.screenshot( - **ChainMap({"timeout": timeout}, params), + **{**params, "timeout": timeout}, ), ) @@ -561,9 +560,7 @@ async def select_option( async def select_text(self, force: bool = None, timeout: float = None) -> None: params = locals_to_params(locals()) return await self._with_element( - lambda h, timeout: h.select_text( - **ChainMap({"timeout": timeout}, params), - ), + lambda h, timeout: h.select_text(**{**params, "timeout": timeout}), timeout, ) diff --git a/playwright/_impl/_network.py b/playwright/_impl/_network.py index 67bd9d48d..102767cf6 100644 --- a/playwright/_impl/_network.py +++ b/playwright/_impl/_network.py @@ -167,7 +167,7 @@ def post_data(self) -> Optional[str]: data = self._fallback_overrides.post_data_buffer if not data: return None - return data.decode() if isinstance(data, bytes) else data + return data.decode() @property def post_data_json(self) -> Optional[Any]: diff --git a/pyproject.toml b/pyproject.toml index da6e54e07..e87689aa0 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -25,17 +25,16 @@ warn_unused_configs = true check_untyped_defs = true disallow_untyped_defs = true no_implicit_optional = false - -[[tool.mypy.overrides]] -module = "tests/async.*" -ignore_errors = true +exclude = [ + "build/", + "env/", +] [tool.isort] profile = "black" [tool.pyright] -include = ["playwright", "tests/sync"] -ignore = ["tests/async/", "scripts/"] +include = ["playwright", "tests", "scripts"] pythonVersion = "3.8" reportMissingImports = false reportTypedDictNotRequiredAccess = false diff --git a/scripts/documentation_provider.py b/scripts/documentation_provider.py index a68697be1..2d03ebc04 100644 --- a/scripts/documentation_provider.py +++ b/scripts/documentation_provider.py @@ -16,16 +16,7 @@ import re import subprocess from sys import stderr -from typing import ( # type: ignore - Any, - Dict, - List, - Set, - Union, - get_args, - get_origin, - get_type_hints, -) +from typing import Any, Dict, List, Set, Union, get_args, get_origin, get_type_hints from urllib.parse import urljoin from playwright._impl._helper import to_snake_case diff --git a/tests/async/test_browsercontext_add_cookies.py b/tests/async/test_browsercontext_add_cookies.py index 744e989d1..6f457a11f 100644 --- a/tests/async/test_browsercontext_add_cookies.py +++ b/tests/async/test_browsercontext_add_cookies.py @@ -19,7 +19,7 @@ import pytest from playwright.async_api import Browser, BrowserContext, Error, Page -from tests.server import HttpRequestWithPostBody, Server +from tests.server import Server, TestServerRequest from tests.utils import must @@ -49,7 +49,7 @@ async def test_should_roundtrip_cookie( cookies = await context.cookies() await context.clear_cookies() assert await context.cookies() == [] - await context.add_cookies(cookies) + await context.add_cookies(cookies) # type: ignore assert await context.cookies() == cookies @@ -58,7 +58,7 @@ async def test_should_send_cookie_header( ) -> None: cookie: List[str] = [] - def handler(request: HttpRequestWithPostBody) -> None: + def handler(request: TestServerRequest) -> None: cookie.extend(must(request.requestHeaders.getRawHeaders("cookie"))) request.finish() @@ -154,7 +154,7 @@ async def test_should_isolate_send_cookie_header( ) -> None: cookie: List[str] = [] - def handler(request: HttpRequestWithPostBody) -> None: + def handler(request: TestServerRequest) -> None: cookie.extend(request.requestHeaders.getRawHeaders("cookie") or []) request.finish() diff --git a/tests/async/test_browsercontext_events.py b/tests/async/test_browsercontext_events.py index 9cae739dc..a0a3b90eb 100644 --- a/tests/async/test_browsercontext_events.py +++ b/tests/async/test_browsercontext_events.py @@ -20,7 +20,7 @@ from playwright.async_api import Page from tests.utils import must -from ..server import HttpRequestWithPostBody, Server +from ..server import Server, TestServerRequest async def test_console_event_should_work(page: Page) -> None: @@ -162,7 +162,7 @@ async def test_dialog_event_should_work_in_immdiately_closed_popup(page: Page) - async def test_dialog_event_should_work_with_inline_script_tag( page: Page, server: Server ) -> None: - def handle_route(request: HttpRequestWithPostBody) -> None: + def handle_route(request: TestServerRequest) -> None: request.setHeader("content-type", "text/html") request.write(b"""""") request.finish() diff --git a/tests/async/test_browsercontext_proxy.py b/tests/async/test_browsercontext_proxy.py index 07f52a562..6f2f21440 100644 --- a/tests/async/test_browsercontext_proxy.py +++ b/tests/async/test_browsercontext_proxy.py @@ -20,7 +20,7 @@ from flaky import flaky from playwright.async_api import Browser, BrowserContext -from tests.server import HttpRequestWithPostBody, Server +from tests.server import Server, TestServerRequest @pytest.fixture(scope="session") @@ -89,7 +89,7 @@ async def test_should_work_with_ip_port_notion( async def test_should_authenticate( context_factory: "Callable[..., Awaitable[BrowserContext]]", server: Server ) -> None: - def handler(req: HttpRequestWithPostBody) -> None: + def handler(req: TestServerRequest) -> None: auth = req.getHeader("proxy-authorization") if not auth: req.setHeader( @@ -120,7 +120,7 @@ def handler(req: HttpRequestWithPostBody) -> None: async def test_should_authenticate_with_empty_password( context_factory: "Callable[..., Awaitable[BrowserContext]]", server: Server ) -> None: - def handler(req: HttpRequestWithPostBody) -> None: + def handler(req: TestServerRequest) -> None: auth = req.getHeader("proxy-authorization") if not auth: req.setHeader( diff --git a/tests/async/test_browsertype_connect.py b/tests/async/test_browsertype_connect.py index 556e8eefd..34bf42245 100644 --- a/tests/async/test_browsertype_connect.py +++ b/tests/async/test_browsertype_connect.py @@ -23,7 +23,7 @@ from playwright.async_api import BrowserType, Error, Playwright, Route from tests.conftest import RemoteServer -from tests.server import HttpRequestWithPostBody, Server +from tests.server import Server, TestServerRequest from tests.utils import parse_trace @@ -168,7 +168,7 @@ async def test_browser_type_connect_should_reject_navigation_when_browser_closes async def test_should_not_allow_getting_the_path( browser_type: BrowserType, launch_server: Callable[[], RemoteServer], server: Server ) -> None: - def handle_download(request: HttpRequestWithPostBody) -> None: + def handle_download(request: TestServerRequest) -> None: request.setHeader("Content-Type", "application/octet-stream") request.setHeader("Content-Disposition", "attachment") request.write(b"Hello world") diff --git a/tests/async/test_download.py b/tests/async/test_download.py index 94a329606..96d06820e 100644 --- a/tests/async/test_download.py +++ b/tests/async/test_download.py @@ -20,7 +20,7 @@ import pytest from playwright.async_api import Browser, Download, Error, Page -from tests.server import HttpRequestWithPostBody, Server +from tests.server import Server, TestServerRequest from tests.utils import TARGET_CLOSED_ERROR_MESSAGE @@ -31,13 +31,13 @@ def assert_file_content(path: Path, content: str) -> None: @pytest.fixture(autouse=True) def after_each_hook(server: Server) -> Generator[None, None, None]: - def handle_download(request: HttpRequestWithPostBody) -> None: + def handle_download(request: TestServerRequest) -> None: request.setHeader("Content-Type", "application/octet-stream") request.setHeader("Content-Disposition", "attachment") request.write(b"Hello world") request.finish() - def handle_download_with_file_name(request: HttpRequestWithPostBody) -> None: + def handle_download_with_file_name(request: TestServerRequest) -> None: request.setHeader("Content-Type", "application/octet-stream") request.setHeader("Content-Disposition", "attachment; filename=file.txt") request.write(b"Hello world") @@ -206,7 +206,7 @@ async def test_should_report_non_navigation_downloads( browser: Browser, server: Server ) -> None: # Mac WebKit embedder does not download in this case, although Safari does. - def handle_download(request: HttpRequestWithPostBody) -> None: + def handle_download(request: TestServerRequest) -> None: request.setHeader("Content-Type", "application/octet-stream") request.write(b"Hello world") request.finish() @@ -275,7 +275,7 @@ async def test_should_report_alt_click_downloads( ) -> None: # Firefox does not download on alt-click by default. # Our WebKit embedder does not download on alt-click, although Safari does. - def handle_download(request: HttpRequestWithPostBody) -> None: + def handle_download(request: TestServerRequest) -> None: request.setHeader("Content-Type", "application/octet-stream") request.write(b"Hello world") request.finish() @@ -365,7 +365,7 @@ async def test_should_delete_downloads_on_browser_gone( async def test_download_cancel_should_work(browser: Browser, server: Server) -> None: - def handle_download(request: HttpRequestWithPostBody) -> None: + def handle_download(request: TestServerRequest) -> None: request.setHeader("Content-Type", "application/octet-stream") request.setHeader("Content-Disposition", "attachment") # Chromium requires a large enough payload to trigger the download event soon enough diff --git a/tests/async/test_fetch_browser_context.py b/tests/async/test_fetch_browser_context.py index 999becf47..2c515697b 100644 --- a/tests/async/test_fetch_browser_context.py +++ b/tests/async/test_fetch_browser_context.py @@ -14,7 +14,7 @@ import asyncio import json -from typing import Any +from typing import Any, cast from urllib.parse import parse_qs import pytest @@ -220,7 +220,9 @@ async def test_should_support_multipart_form_data( ), ) assert request.method == b"POST" - assert must(request.getHeader("Content-Type")).startswith("multipart/form-data; ") + assert cast(str, request.getHeader("Content-Type")).startswith( + "multipart/form-data; " + ) assert must(request.getHeader("Content-Length")) == str( len(must(request.post_body)) ) diff --git a/tests/async/test_har.py b/tests/async/test_har.py index b0978894b..31a34f8fa 100644 --- a/tests/async/test_har.py +++ b/tests/async/test_har.py @@ -18,12 +18,12 @@ import re import zipfile from pathlib import Path +from typing import cast import pytest from playwright.async_api import Browser, BrowserContext, Error, Page, Route, expect from tests.server import Server -from tests.utils import must async def test_should_work(browser: Browser, server: Server, tmpdir: Path) -> None: @@ -560,7 +560,7 @@ async def test_should_disambiguate_by_header( ) -> None: server.set_route( "/echo", - lambda req: (req.write(must(req.getHeader("baz")).encode()), req.finish()), + lambda req: (req.write(cast(str, req.getHeader("baz")).encode()), req.finish()), ) fetch_function = """ async (bazValue) => { diff --git a/tests/async/test_interception.py b/tests/async/test_interception.py index 6b0bf0a27..911d7ddd8 100644 --- a/tests/async/test_interception.py +++ b/tests/async/test_interception.py @@ -29,7 +29,7 @@ Request, Route, ) -from tests.server import HttpRequestWithPostBody, Server +from tests.server import Server, TestServerRequest from tests.utils import must @@ -412,7 +412,7 @@ async def test_page_route_should_work_with_equal_requests( await page.goto(server.EMPTY_PAGE) hits = [True] - def handle_request(request: HttpRequestWithPostBody, hits: List[bool]) -> None: + def handle_request(request: TestServerRequest, hits: List[bool]) -> None: request.write(str(len(hits) * 11).encode()) request.finish() hits.append(True) @@ -857,7 +857,7 @@ async def test_request_fulfill_should_not_modify_the_headers_sent_to_the_server( # this is just to enable request interception, which disables caching in chromium await page.route(server.PREFIX + "/unused", lambda route, req: None) - def _handler1(response: HttpRequestWithPostBody) -> None: + def _handler1(response: TestServerRequest) -> None: interceptedRequests.append(response) response.setHeader("Access-Control-Allow-Origin", "*") response.write(b"done") diff --git a/tests/async/test_navigation.py b/tests/async/test_navigation.py index 62cc5036f..de4a2f5e9 100644 --- a/tests/async/test_navigation.py +++ b/tests/async/test_navigation.py @@ -29,7 +29,7 @@ Route, TimeoutError, ) -from tests.server import HttpRequestWithPostBody, Server +from tests.server import Server, TestServerRequest async def test_goto_should_work(page: Page, server: Server) -> None: @@ -155,7 +155,7 @@ async def test_goto_should_return_response_when_page_changes_its_url_after_load( async def test_goto_should_work_with_subframes_return_204( page: Page, server: Server ) -> None: - def handle(request: HttpRequestWithPostBody) -> None: + def handle(request: TestServerRequest) -> None: request.setResponseCode(204) request.finish() @@ -168,7 +168,7 @@ async def test_goto_should_fail_when_server_returns_204( page: Page, server: Server, is_chromium: bool, is_webkit: bool ) -> None: # WebKit just loads an empty page. - def handle(request: HttpRequestWithPostBody) -> None: + def handle(request: TestServerRequest) -> None: request.setResponseCode(204) request.finish() @@ -897,7 +897,7 @@ async def test_wait_for_load_state_in_popup( await page.goto(server.EMPTY_PAGE) css_requests = [] - def handle_request(request: HttpRequestWithPostBody) -> None: + def handle_request(request: TestServerRequest) -> None: css_requests.append(request) request.write(b"body {}") request.finish() @@ -1080,7 +1080,7 @@ async def test_reload_should_work_with_data_url(page: Page, server: Server) -> N async def test_should_work_with__blank_target(page: Page, server: Server) -> None: - def handler(request: HttpRequestWithPostBody) -> None: + def handler(request: TestServerRequest) -> None: request.write( f'Click me'.encode() ) @@ -1095,7 +1095,7 @@ def handler(request: HttpRequestWithPostBody) -> None: async def test_should_work_with_cross_process__blank_target( page: Page, server: Server ) -> None: - def handler(request: HttpRequestWithPostBody) -> None: + def handler(request: TestServerRequest) -> None: request.write( f'Click me'.encode() ) diff --git a/tests/async/test_network.py b/tests/async/test_network.py index 015372fc0..486a98914 100644 --- a/tests/async/test_network.py +++ b/tests/async/test_network.py @@ -23,7 +23,7 @@ from twisted.web import http from playwright.async_api import Browser, Error, Page, Request, Response, Route -from tests.server import HttpRequestWithPostBody, Server +from tests.server import Server, TestServerRequest from .utils import Utils @@ -631,7 +631,7 @@ async def test_network_events_request_failed( is_mac: bool, is_win: bool, ) -> None: - def handle_request(request: HttpRequestWithPostBody) -> None: + def handle_request(request: TestServerRequest) -> None: request.setHeader("Content-Type", "text/css") request.transport.loseConnection() diff --git a/tests/async/test_page.py b/tests/async/test_page.py index 349914b6f..376df8376 100644 --- a/tests/async/test_page.py +++ b/tests/async/test_page.py @@ -28,7 +28,7 @@ Route, TimeoutError, ) -from tests.server import HttpRequestWithPostBody, Server +from tests.server import Server, TestServerRequest from tests.utils import TARGET_CLOSED_ERROR_MESSAGE, must @@ -151,7 +151,7 @@ async def test_load_should_fire_when_expected(page: Page) -> None: async def test_should_work_with_wait_for_loadstate(page: Page, server: Server) -> None: messages = [] - def _handler(request: HttpRequestWithPostBody) -> None: + def _handler(request: TestServerRequest) -> None: messages.append("route") request.setHeader("Content-Type", "text/html") request.write(b"") diff --git a/tests/async/test_page_network_response.py b/tests/async/test_page_network_response.py index 98f4aaa42..58988fabc 100644 --- a/tests/async/test_page_network_response.py +++ b/tests/async/test_page_network_response.py @@ -17,7 +17,7 @@ import pytest from playwright.async_api import Error, Page -from tests.server import HttpRequestWithPostBody, Server +from tests.server import Server, TestServerRequest async def test_should_reject_response_finished_if_page_closes( @@ -25,7 +25,7 @@ async def test_should_reject_response_finished_if_page_closes( ) -> None: await page.goto(server.EMPTY_PAGE) - def handle_get(request: HttpRequestWithPostBody) -> None: + def handle_get(request: TestServerRequest) -> None: # In Firefox, |fetch| will be hanging until it receives |Content-Type| header # from server. request.setHeader("Content-Type", "text/plain; charset=utf-8") @@ -51,7 +51,7 @@ async def test_should_reject_response_finished_if_context_closes( ) -> None: await page.goto(server.EMPTY_PAGE) - def handle_get(request: HttpRequestWithPostBody) -> None: + def handle_get(request: TestServerRequest) -> None: # In Firefox, |fetch| will be hanging until it receives |Content-Type| header # from server. request.setHeader("Content-Type", "text/plain; charset=utf-8") diff --git a/tests/async/test_page_request_intercept.py b/tests/async/test_page_request_intercept.py index 2206135be..934aed8a0 100644 --- a/tests/async/test_page_request_intercept.py +++ b/tests/async/test_page_request_intercept.py @@ -18,13 +18,13 @@ import pytest from playwright.async_api import Error, Page, Route, expect -from tests.server import HttpRequestWithPostBody, Server +from tests.server import Server, TestServerRequest async def test_should_support_timeout_option_in_route_fetch( server: Server, page: Page ) -> None: - def _handler(request: HttpRequestWithPostBody) -> None: + def _handler(request: TestServerRequest) -> None: request.responseHeaders.addRawHeader("Content-Length", "4096") request.responseHeaders.addRawHeader("Content-Type", "text/html") request.write(b"") diff --git a/tests/async/test_proxy.py b/tests/async/test_proxy.py index e1c072e9d..d85613964 100644 --- a/tests/async/test_proxy.py +++ b/tests/async/test_proxy.py @@ -19,7 +19,7 @@ import pytest from playwright.async_api import Browser, Error -from tests.server import HttpRequestWithPostBody, Server +from tests.server import Server, TestServerRequest async def test_should_throw_for_bad_server_value( @@ -86,7 +86,7 @@ async def test_should_work_with_ip_port_notion( async def test_should_authenticate( browser_factory: "Callable[..., asyncio.Future[Browser]]", server: Server ) -> None: - def handler(req: HttpRequestWithPostBody) -> None: + def handler(req: TestServerRequest) -> None: auth = req.getHeader("proxy-authorization") if not auth: req.setHeader( @@ -116,7 +116,7 @@ def handler(req: HttpRequestWithPostBody) -> None: async def test_should_authenticate_with_empty_password( browser_factory: "Callable[..., asyncio.Future[Browser]]", server: Server ) -> None: - def handler(req: HttpRequestWithPostBody) -> None: + def handler(req: TestServerRequest) -> None: auth = req.getHeader("proxy-authorization") if not auth: req.setHeader( diff --git a/tests/server.py b/tests/server.py index 37d2c2b0d..06e344653 100644 --- a/tests/server.py +++ b/tests/server.py @@ -31,18 +31,21 @@ Set, Tuple, TypeVar, + cast, ) from urllib.parse import urlparse from autobahn.twisted.websocket import WebSocketServerFactory, WebSocketServerProtocol from OpenSSL import crypto -from twisted.internet import reactor, ssl -from twisted.internet.protocol import ClientFactory +from twisted.internet import reactor as _twisted_reactor +from twisted.internet import ssl +from twisted.internet.selectreactor import SelectReactor from twisted.web import http from playwright._impl._path_utils import get_file_dirname _dirname = get_file_dirname() +reactor = cast(SelectReactor, _twisted_reactor) def find_free_port() -> int: @@ -52,10 +55,6 @@ def find_free_port() -> int: return s.getsockname()[1] -class HttpRequestWithPostBody(http.Request): - post_body: Optional[bytes] = None - - T = TypeVar("T") @@ -70,6 +69,76 @@ def value(self) -> T: return self._value +class TestServerRequest(http.Request): + __test__ = False + channel: "TestServerHTTPChannel" + post_body: Optional[bytes] = None + + def process(self) -> None: + server = self.channel.factory.server_instance + if self.content: + self.post_body = self.content.read() + self.content.seek(0, 0) + else: + self.post_body = None + uri = urlparse(self.uri.decode()) + path = uri.path + + request_subscriber = server.request_subscribers.get(path) + if request_subscriber: + request_subscriber._loop.call_soon_threadsafe( + request_subscriber.set_result, self + ) + server.request_subscribers.pop(path) + + if server.auth.get(path): + authorization_header = self.requestHeaders.getRawHeaders("authorization") + creds_correct = False + if authorization_header: + creds_correct = server.auth.get(path) == ( + self.getUser().decode(), + self.getPassword().decode(), + ) + if not creds_correct: + self.setHeader(b"www-authenticate", 'Basic realm="Secure Area"') + self.setResponseCode(HTTPStatus.UNAUTHORIZED) + self.finish() + return + if server.csp.get(path): + self.setHeader(b"Content-Security-Policy", server.csp[path]) + if server.routes.get(path): + server.routes[path](self) + return + file_content = None + try: + file_content = (server.static_path / path[1:]).read_bytes() + content_type = mimetypes.guess_type(path)[0] + if content_type and content_type.startswith("text/"): + content_type += "; charset=utf-8" + self.setHeader(b"Content-Type", content_type) + self.setHeader(b"Cache-Control", "no-cache, no-store") + if path in server.gzip_routes: + self.setHeader("Content-Encoding", "gzip") + self.write(gzip.compress(file_content)) + else: + self.setHeader(b"Content-Length", str(len(file_content))) + self.write(file_content) + self.setResponseCode(HTTPStatus.OK) + except (FileNotFoundError, IsADirectoryError, PermissionError): + self.setResponseCode(HTTPStatus.NOT_FOUND) + self.finish() + + +class TestServerHTTPChannel(http.HTTPChannel): + factory: "TestServerFactory" + requestFactory = TestServerRequest + + +class TestServerFactory(http.HTTPFactory): + server_instance: "Server" + protocol = TestServerHTTPChannel + + class Server: protocol = "http" @@ -89,103 +158,39 @@ def __repr__(self) -> str: return self.PREFIX @abc.abstractmethod - def listen(self, factory: ClientFactory) -> None: + def listen(self, factory: TestServerFactory) -> None: pass def start(self) -> None: request_subscribers: Dict[str, asyncio.Future] = {} auth: Dict[str, Tuple[str, str]] = {} csp: Dict[str, str] = {} - routes: Dict[str, Callable[[HttpRequestWithPostBody], Any]] = {} + routes: Dict[str, Callable[[TestServerRequest], Any]] = {} gzip_routes: Set[str] = set() self.request_subscribers = request_subscribers self.auth = auth self.csp = csp self.routes = routes self.gzip_routes = gzip_routes - static_path = _dirname / "assets" - - class TestServerHTTPHandler(http.Request): - def process(self) -> None: - request = self - if request.content: - self.post_body = request.content.read() - request.content.seek(0, 0) - else: - self.post_body = None - uri = urlparse(request.uri.decode()) - path = uri.path - - request_subscriber = request_subscribers.get(path) - if request_subscriber: - request_subscriber._loop.call_soon_threadsafe( - request_subscriber.set_result, request - ) - request_subscribers.pop(path) - - if auth.get(path): - authorization_header = request.requestHeaders.getRawHeaders( - "authorization" - ) - creds_correct = False - if authorization_header: - creds_correct = auth.get(path) == ( - request.getUser().decode(), - request.getPassword().decode(), - ) - if not creds_correct: - request.setHeader( - b"www-authenticate", 'Basic realm="Secure Area"' - ) - request.setResponseCode(HTTPStatus.UNAUTHORIZED) - request.finish() - return - if csp.get(path): - request.setHeader(b"Content-Security-Policy", csp[path]) - if routes.get(path): - routes[path](request) - return - file_content = None - try: - file_content = (static_path / path[1:]).read_bytes() - content_type = mimetypes.guess_type(path)[0] - if content_type and content_type.startswith("text/"): - content_type += "; charset=utf-8" - request.setHeader(b"Content-Type", content_type) - request.setHeader(b"Cache-Control", "no-cache, no-store") - if path in gzip_routes: - request.setHeader("Content-Encoding", "gzip") - request.write(gzip.compress(file_content)) - else: - request.setHeader(b"Content-Length", str(len(file_content))) - request.write(file_content) - self.setResponseCode(HTTPStatus.OK) - except (FileNotFoundError, IsADirectoryError, PermissionError): - request.setResponseCode(HTTPStatus.NOT_FOUND) - self.finish() - - class MyHttp(http.HTTPChannel): - requestFactory = TestServerHTTPHandler - - class MyHttpFactory(http.HTTPFactory): - protocol = MyHttp - - self.listen(MyHttpFactory()) + self.static_path = _dirname / "assets" + factory = TestServerFactory() + factory.server_instance = self + self.listen(factory) - async def wait_for_request(self, path: str) -> HttpRequestWithPostBody: + async def wait_for_request(self, path: str) -> TestServerRequest: if path in self.request_subscribers: return await self.request_subscribers[path] - future: asyncio.Future["HttpRequestWithPostBody"] = asyncio.Future() + future: asyncio.Future["TestServerRequest"] = asyncio.Future() self.request_subscribers[path] = future return await future @contextlib.contextmanager def expect_request( self, path: str - ) -> Generator[ExpectResponse[HttpRequestWithPostBody], None, None]: + ) -> Generator[ExpectResponse[TestServerRequest], None, None]: future = asyncio.create_task(self.wait_for_request(path)) - cb_wrapper: ExpectResponse[HttpRequestWithPostBody] = ExpectResponse() + cb_wrapper: ExpectResponse[TestServerRequest] = ExpectResponse() def done_cb(task: asyncio.Task) -> None: cb_wrapper._value = future.result() @@ -207,7 +212,7 @@ def reset(self) -> None: self.routes.clear() def set_route( - self, path: str, callback: Callable[[HttpRequestWithPostBody], Any] + self, path: str, callback: Callable[[TestServerRequest], Any] ) -> None: self.routes[path] = callback @@ -224,7 +229,7 @@ def handle_redirect(request: http.Request) -> None: class HTTPServer(Server): - def listen(self, factory: ClientFactory) -> None: + def listen(self, factory: http.HTTPFactory) -> None: reactor.listenTCP(self.PORT, factory, interface="127.0.0.1") try: reactor.listenTCP(self.PORT, factory, interface="::1") @@ -235,7 +240,7 @@ def listen(self, factory: ClientFactory) -> None: class HTTPSServer(Server): protocol = "https" - def listen(self, factory: ClientFactory) -> None: + def listen(self, factory: http.HTTPFactory) -> None: cert = ssl.PrivateCertificate.fromCertificateAndKeyPair( ssl.Certificate.loadPEM( (_dirname / "testserver" / "cert.pem").read_bytes() @@ -295,7 +300,7 @@ def start(self) -> None: self.https_server.start() self.ws_server.start() self.thread = threading.Thread( - target=lambda: reactor.run(installSignalHandlers=0) + target=lambda: reactor.run(installSignalHandlers=False) ) self.thread.start() diff --git a/tests/sync/test_browsercontext_events.py b/tests/sync/test_browsercontext_events.py index 6d0840e6a..315fff0dc 100644 --- a/tests/sync/test_browsercontext_events.py +++ b/tests/sync/test_browsercontext_events.py @@ -18,7 +18,7 @@ from playwright.sync_api import Dialog, Page -from ..server import HttpRequestWithPostBody, Server +from ..server import Server, TestServerRequest def test_console_event_should_work(page: Page) -> None: @@ -170,7 +170,7 @@ def handle_popup(p: Page) -> None: def test_dialog_event_should_work_with_inline_script_tag( page: Page, server: Server ) -> None: - def handle_route(request: HttpRequestWithPostBody) -> None: + def handle_route(request: TestServerRequest) -> None: request.setHeader("content-type", "text/html") request.write(b"""""") request.finish() diff --git a/tests/sync/test_page_request_intercept.py b/tests/sync/test_page_request_intercept.py index d62cc5f79..86cf21b63 100644 --- a/tests/sync/test_page_request_intercept.py +++ b/tests/sync/test_page_request_intercept.py @@ -15,13 +15,13 @@ import pytest from playwright.sync_api import Error, Page, Route -from tests.server import HttpRequestWithPostBody, Server +from tests.server import Server, TestServerRequest def test_should_support_timeout_option_in_route_fetch( server: Server, page: Page ) -> None: - def _handle(request: HttpRequestWithPostBody) -> None: + def _handle(request: TestServerRequest) -> None: request.responseHeaders.addRawHeader("Content-Length", "4096") request.responseHeaders.addRawHeader("Content-Type", "text/html") request.write(b"")