From c0bb12257390fd7f3eefa4ec0497d92d48e9b80a Mon Sep 17 00:00:00 2001 From: Roland Mas Date: Tue, 16 Dec 2025 14:20:23 +0100 Subject: [PATCH 1/3] Add xvfb_windowmanager option --- pyproject.toml | 2 +- pytest_xvfb.py | 19 ++++++++++++++++++- tests/test_xvfb.py | 33 +++++++++++++++++++++++++++++++++ 3 files changed, 52 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 95395b4..64f08e1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,7 +28,7 @@ classifiers = [ "License :: OSI Approved :: MIT License", ] requires-python = ">=3.9" -dependencies = ["pytest>=2.8.1", "pyvirtualdisplay>=1.3"] +dependencies = ["pytest>=2.8.1", "pyvirtualdisplay>=1.3", "easyprocess"] [project.urls] Homepage = "https://github.com/The-Compiler/pytest-xvfb" diff --git a/pytest_xvfb.py b/pytest_xvfb.py index 7abb222..2a5f251 100644 --- a/pytest_xvfb.py +++ b/pytest_xvfb.py @@ -52,10 +52,12 @@ def __init__(self, config: pytest.Config) -> None: self.height = int(config.getini("xvfb_height")) self.colordepth = int(config.getini("xvfb_colordepth")) self.args = config.getini("xvfb_args") or [] - self.xauth = config.getini("xvfb_xauth") + self.wm = config.getini("xvfb_windowmanager") + self.xauth = True if self.wm else config.getini("xvfb_xauth") self.backend = config.getoption("--xvfb-backend") self.display: int | None = None self._virtual_display: pyvirtualdisplay.display.Display | None = None + self._wmprocess = None def start(self) -> None: self._virtual_display = pyvirtualdisplay.display.Display( @@ -66,12 +68,26 @@ def start(self) -> None: extra_args=self.args, ) self._virtual_display.start() + if self.wm: + from easyprocess import EasyProcess + import time + + self._wmprocess = EasyProcess(self.wm).start() + time.sleep(5) self.display = self._virtual_display.display assert self._virtual_display.is_alive() + def __del__(self) -> None: + if self._wmprocess: + self._wmprocess.stop() + self._wmprocess = None + def stop(self) -> None: if self.display is not None: # starting worked assert self._virtual_display is not None # mypy + if self._wmprocess: + self._wmprocess.stop() + self._wmprocess = None self._virtual_display.stop() @@ -95,6 +111,7 @@ def pytest_addoption(parser: pytest.Parser) -> None: default=False, type="bool", ) + parser.addini("xvfb_windowmanager", "Run window manager", type="string", default="") def pytest_addhooks(pluginmanager: pytest.PytestPluginManager) -> None: diff --git a/tests/test_xvfb.py b/tests/test_xvfb.py index 783256a..3c2fc14 100644 --- a/tests/test_xvfb.py +++ b/tests/test_xvfb.py @@ -8,10 +8,20 @@ import pytest_xvfb +windowmanager = "herbstluftwm" + xauth_available = any( os.access(os.path.join(path, "xauth"), os.X_OK) for path in os.environ.get("PATH", "").split(os.pathsep) ) +wm_available = any( + os.access(os.path.join(path, windowmanager), os.X_OK) + for path in os.environ.get("PATH", "").split(os.pathsep) +) +wmctrl_available = any( + os.access(os.path.join(path, "wmctrl"), os.X_OK) + for path in os.environ.get("PATH", "").split(os.pathsep) +) @pytest.fixture(autouse=True, scope="session") @@ -367,3 +377,26 @@ def test_xauth(): # Make sure the authfile is deleted assert not os.path.exists(authfile) assert os.environ.get("XAUTHORITY") == original_auth + + +@pytest.mark.skipif(not wm_available, reason="no windowmanager") +@pytest.mark.skipif(not wmctrl_available, reason="no wmctrl") +def test_xvfb_with_wm(pytester: pytest.Pytester, backend_args: list[str]) -> None: + pytester.makeini( + f""" + [pytest] + xvfb_windowmanager = {windowmanager} + """ + ) + pytester.makepyfile( + f""" + import os + from easyprocess import EasyProcess + + def test_wm(): + assert "{windowmanager}" in EasyProcess(['wmctrl', '-m']).call().stdout + """ + ) + result = pytester.runpytest("-s", *backend_args) + + assert result.ret == 0 From 667350e4a3b8585507d4765903d49bc361504de5 Mon Sep 17 00:00:00 2001 From: Roland Mas Date: Tue, 16 Dec 2025 14:20:23 +0100 Subject: [PATCH 2/3] Wait for the WM to appear --- pytest_xvfb.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/pytest_xvfb.py b/pytest_xvfb.py index 2a5f251..19e9576 100644 --- a/pytest_xvfb.py +++ b/pytest_xvfb.py @@ -73,7 +73,15 @@ def start(self) -> None: import time self._wmprocess = EasyProcess(self.wm).start() - time.sleep(5) + counter = 100 + while ( + EasyProcess(["xprop", "-root", "_NET_SUPPORTING_WM_CHECK"]) + .call() + .return_code + != 0 + ) and counter > 0: + time.sleep(0.1) + counter -= 1 self.display = self._virtual_display.display assert self._virtual_display.is_alive() From 2c2f4e14328d872f80198c1e8e5dfc576e703fcd Mon Sep 17 00:00:00 2001 From: Roland Mas Date: Fri, 19 Dec 2025 17:51:17 +0100 Subject: [PATCH 3/3] Fix detection of running WM --- pytest_xvfb.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/pytest_xvfb.py b/pytest_xvfb.py index 19e9576..401d132 100644 --- a/pytest_xvfb.py +++ b/pytest_xvfb.py @@ -75,10 +75,10 @@ def start(self) -> None: self._wmprocess = EasyProcess(self.wm).start() counter = 100 while ( - EasyProcess(["xprop", "-root", "_NET_SUPPORTING_WM_CHECK"]) + "window id" + not in EasyProcess(["xprop", "-root", "_NET_SUPPORTING_WM_CHECK"]) .call() - .return_code - != 0 + .stdout ) and counter > 0: time.sleep(0.1) counter -= 1