From 36ee721727b5623d6b8909248ca5ed078256b99d Mon Sep 17 00:00:00 2001 From: Max Schmitt Date: Thu, 30 Jan 2025 09:10:58 +0100 Subject: [PATCH] feat: allow connecting via connect_options (#269) --- .github/workflows/ci.yml | 5 +- .github/workflows/python-publish.yml | 5 +- .../pytest_playwright.py | 22 +++++++- .../pytest_playwright/pytest_playwright.py | 22 +++++++- tests/test_asyncio.py | 50 +++++++++++++++++++ tests/test_sync.py | 46 +++++++++++++++++ 6 files changed, 146 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0224acf..ed3ed73 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -72,7 +72,10 @@ jobs: python-version: 3.9 channels: microsoft,conda-forge - name: Prepare - run: conda install conda-build conda-verify + run: | + conda install conda-build conda-verify + # Until https://github.com/anaconda/conda-anaconda-telemetry/issues/87 has been fixed + conda remove --name base conda-anaconda-telemetry - name: Build pytest-playwright run: conda build . - name: Build pytest-playwright-asyncio diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 400212f..4d9c598 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -18,7 +18,10 @@ jobs: python-version: 3.9 channels: microsoft,conda-forge - name: Prepare - run: conda install anaconda-client conda-build conda-verify + run: | + conda install anaconda-client conda-build conda-verify + # Until https://github.com/anaconda/conda-anaconda-telemetry/issues/87 has been fixed + conda remove --name base conda-anaconda-telemetry - name: Build and Upload env: ANACONDA_API_TOKEN: ${{ secrets.ANACONDA_API_TOKEN }} diff --git a/pytest-playwright-asyncio/pytest_playwright_asyncio/pytest_playwright.py b/pytest-playwright-asyncio/pytest_playwright_asyncio/pytest_playwright.py index f457a30..0a47afb 100644 --- a/pytest-playwright-asyncio/pytest_playwright_asyncio/pytest_playwright.py +++ b/pytest-playwright-asyncio/pytest_playwright_asyncio/pytest_playwright.py @@ -13,6 +13,7 @@ # limitations under the License. import hashlib +import json import shutil import os import sys @@ -247,14 +248,33 @@ def browser_type(playwright: Playwright, browser_name: str) -> BrowserType: return getattr(playwright, browser_name) +@pytest.fixture(scope="session") +def connect_options() -> Optional[Dict]: + return None + + @pytest.fixture(scope="session") def launch_browser( browser_type_launch_args: Dict, browser_type: BrowserType, + connect_options: Optional[Dict], ) -> Callable[..., Awaitable[Browser]]: async def launch(**kwargs: Dict) -> Browser: launch_options = {**browser_type_launch_args, **kwargs} - browser = await browser_type.launch(**launch_options) + if connect_options: + browser = await browser_type.connect( + **( + { + **connect_options, + "headers": { + "x-playwright-launch-options": json.dumps(launch_options), + **(connect_options.get("headers") or {}), + }, + } + ) + ) + else: + browser = await browser_type.launch(**launch_options) return browser return launch diff --git a/pytest-playwright/pytest_playwright/pytest_playwright.py b/pytest-playwright/pytest_playwright/pytest_playwright.py index 6dfc0b8..fcca953 100644 --- a/pytest-playwright/pytest_playwright/pytest_playwright.py +++ b/pytest-playwright/pytest_playwright/pytest_playwright.py @@ -13,6 +13,7 @@ # limitations under the License. import hashlib +import json import shutil import os import sys @@ -244,14 +245,33 @@ def browser_type(playwright: Playwright, browser_name: str) -> BrowserType: return getattr(playwright, browser_name) +@pytest.fixture(scope="session") +def connect_options() -> Optional[Dict]: + return None + + @pytest.fixture(scope="session") def launch_browser( browser_type_launch_args: Dict, browser_type: BrowserType, + connect_options: Optional[Dict], ) -> Callable[..., Browser]: def launch(**kwargs: Dict) -> Browser: launch_options = {**browser_type_launch_args, **kwargs} - browser = browser_type.launch(**launch_options) + if connect_options: + browser = browser_type.connect( + **( + { + **connect_options, + "headers": { + "x-playwright-launch-options": json.dumps(launch_options), + **(connect_options.get("headers") or {}), + }, + } + ) + ) + else: + browser = browser_type.launch(**launch_options) return browser return launch diff --git a/tests/test_asyncio.py b/tests/test_asyncio.py index a7036dc..e8f2cbd 100644 --- a/tests/test_asyncio.py +++ b/tests/test_asyncio.py @@ -13,6 +13,8 @@ # limitations under the License. import os +import signal +import subprocess import sys import pytest @@ -1019,3 +1021,51 @@ def test_with_page(page): "test-results/test-output-path-via-pytest-runtest-makereport-hook-py-test-with-page-chromium" ).strpath, ] + + +def test_connect_options_should_work(testdir: pytest.Testdir) -> None: + server_process = None + try: + testdir.makeconftest( + """ + import pytest + + @pytest.fixture(scope="session") + def connect_options(): + return { + "ws_endpoint": "ws://localhost:1234", + } + """ + ) + testdir.makepyfile( + """ + import pytest + + @pytest.mark.asyncio(loop_scope="session") + async def test_connect_options(page): + assert await page.evaluate("1 + 1") == 2 + """ + ) + result = testdir.runpytest() + assert "connect ECONNREFUSED" in "".join(result.outlines) + server_process = subprocess.Popen( + ["playwright", "run-server", "--port=1234"], + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + ) + while True: + stdout = server_process.stdout + assert stdout + if "Listening on" in str(stdout.readline()): + break + result = testdir.runpytest() + result.assert_outcomes(passed=1) + finally: + assert server_process + # TODO: Playwright CLI on Windows via Python does not forward the signal + # hence we need to send it to the whole process group. + if sys.platform == "win32": + subprocess.run(["taskkill", "/F", "/T", "/PID", str(server_process.pid)]) + else: + os.kill(server_process.pid, signal.SIGINT) + server_process.wait() diff --git a/tests/test_sync.py b/tests/test_sync.py index 655b7bf..0f640b6 100644 --- a/tests/test_sync.py +++ b/tests/test_sync.py @@ -14,6 +14,8 @@ import os from pathlib import Path +import signal +import subprocess import sys import pytest @@ -982,3 +984,47 @@ def test_with_page(page): "test-results/test-output-path-via-pytest-runtest-makereport-hook-py-test-with-page-chromium" ).strpath, ] + + +def test_connect_options_should_work(testdir: pytest.Testdir) -> None: + server_process = None + try: + testdir.makeconftest( + """ + import pytest + + @pytest.fixture(scope="session") + def connect_options(): + return { + "ws_endpoint": "ws://localhost:1234", + } + """ + ) + testdir.makepyfile( + """ + def test_connect_options(page): + assert page.evaluate("1 + 1") == 2 + """ + ) + result = testdir.runpytest() + assert "connect ECONNREFUSED" in "".join(result.outlines) + server_process = subprocess.Popen( + ["playwright", "run-server", "--port=1234"], + stdout=subprocess.PIPE, + ) + while True: + stdout = server_process.stdout + assert stdout + if "Listening on" in str(stdout.readline()): + break + result = testdir.runpytest() + result.assert_outcomes(passed=1) + finally: + assert server_process + # TODO: Playwright CLI on Windows via Python does not forward the signal + # hence we need to send it to the whole process group. + if sys.platform == "win32": + subprocess.run(["taskkill", "/F", "/T", "/PID", str(server_process.pid)]) + else: + os.kill(server_process.pid, signal.SIGINT) + server_process.wait()