From 1c1025286c9eb4ced32fd2549e5a5e4b874d4912 Mon Sep 17 00:00:00 2001 From: Ramona T Date: Sun, 18 Jan 2026 08:21:16 -0500 Subject: [PATCH 1/9] Update ensure_valid_session to only run if session is used in test Refactor ensure_valid_session to check fixture usage. --- tests/conftest.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index cfc66f9..e6f8fa7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -81,7 +81,7 @@ def session(request: FixtureRequest) -> Generator[requestium.Session, None, None assert driver.name in browser session = requestium.Session(driver=cast("DriverMixin", driver)) assert session.driver.name in browser - + yield session finally: with contextlib.suppress(WebDriverException, OSError): @@ -89,8 +89,12 @@ def session(request: FixtureRequest) -> Generator[requestium.Session, None, None @pytest.fixture(autouse=True) -def ensure_valid_session(session: requestium.Session) -> None: +def ensure_valid_session(request: FixtureRequest, session: requestium.Session) -> None: """Skip test if browser context is discarded.""" + # Only run validation if the test actually uses the session fixture + if "session" not in request.fixturenames: + return + try: _ = session.driver.current_url _ = session.driver.window_handles From 84b9475465e6e9e2620b8792d7a0392b3dd033e8 Mon Sep 17 00:00:00 2001 From: Ramona T Date: Sun, 18 Jan 2026 08:37:49 -0500 Subject: [PATCH 2/9] get session via getfixturevalue in ensure_valid_session --- tests/conftest.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index e6f8fa7..5775658 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -89,12 +89,13 @@ def session(request: FixtureRequest) -> Generator[requestium.Session, None, None @pytest.fixture(autouse=True) -def ensure_valid_session(request: FixtureRequest, session: requestium.Session) -> None: +def ensure_valid_session(request: FixtureRequest) -> None: """Skip test if browser context is discarded.""" - # Only run validation if the test actually uses the session fixture + # Only run if the test actually uses the session fixture if "session" not in request.fixturenames: return + session = request.getfixturevalue("session") try: _ = session.driver.current_url _ = session.driver.window_handles From 7b8cd4430a7988783e647829ca7bbf84646ad7da Mon Sep 17 00:00:00 2001 From: Ramona T Date: Sun, 18 Jan 2026 08:44:07 -0500 Subject: [PATCH 3/9] include passed tests in pytest output in github actions --- .github/workflows/python.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 8e75963..06342eb 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -64,7 +64,7 @@ jobs: env: COVERAGE_FILE: .coverage.${{ matrix.os }}.${{ matrix.python-version }}.${{ matrix.resolution }} with: - run: pytest + run: pytest -r A - uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f with: From 25511c7ae448cdd85fd2d9e113cb6f52fa7a9571 Mon Sep 17 00:00:00 2001 From: Ramona T Date: Sun, 18 Jan 2026 08:53:18 -0500 Subject: [PATCH 4/9] ruff fixes for boolean positions in conftest --- tests/conftest.py | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 5775658..a29de8d 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,16 +6,14 @@ from _pytest.fixtures import FixtureRequest from selenium import webdriver from selenium.common import WebDriverException +from selenium.webdriver.support import expected_conditions as EC # noqa: N812 from selenium.webdriver.support.wait import WebDriverWait -from selenium.webdriver.support import expected_conditions as EC import requestium if TYPE_CHECKING: from requestium.requestium_mixin import DriverMixin -# ruff: noqa FBT003 - @pytest.fixture(scope="module") def example_html() -> str: @@ -35,7 +33,7 @@ def example_html() -> str: """ -def _create_chrome_driver(headless: bool) -> webdriver.Chrome: +def _create_chrome_driver(*, headless: bool) -> webdriver.Chrome: options = webdriver.ChromeOptions() options.add_argument("--no-sandbox") options.add_argument("--disable-dev-shm-usage") @@ -46,12 +44,12 @@ def _create_chrome_driver(headless: bool) -> webdriver.Chrome: return driver -def _create_firefox_driver(headless: bool) -> webdriver.Firefox: +def _create_firefox_driver(*, headless: bool) -> webdriver.Firefox: options = webdriver.FirefoxOptions() - options.set_preference("browser.cache.disk.enable", False) - options.set_preference("browser.cache.memory.enable", False) - options.set_preference("browser.cache.offline.enable", False) - options.set_preference("network.http.use-cache", False) + options.set_preference("browser.cache.disk.enable", value=False) + options.set_preference("browser.cache.memory.enable", value=False) + options.set_preference("browser.cache.offline.enable", value=False) + options.set_preference("network.http.use-cache", value=False) if headless: options.add_argument("--headless") driver = webdriver.Firefox(options=options) @@ -70,9 +68,9 @@ def session(request: FixtureRequest) -> Generator[requestium.Session, None, None driver: webdriver.Chrome | webdriver.Firefox if browser == "chrome": - driver = _create_chrome_driver(headless) + driver = _create_chrome_driver(headless=headless) elif browser == "firefox": - driver = _create_firefox_driver(headless) + driver = _create_firefox_driver(headless=headless) else: msg = f"Unknown driver type: {browser}" raise ValueError(msg) From d0fa6494458cd36389bf75f1f849a8d9a9dd543e Mon Sep 17 00:00:00 2001 From: Ramona T Date: Sun, 18 Jan 2026 09:01:35 -0500 Subject: [PATCH 5/9] consolidate session verification into session fixture with more robust error catching --- tests/conftest.py | 66 ++++++++++++++++++++++------------------------- 1 file changed, 31 insertions(+), 35 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index a29de8d..0c05c02 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,6 +3,7 @@ from typing import TYPE_CHECKING, cast import pytest +import urllib3.exceptions from _pytest.fixtures import FixtureRequest from selenium import webdriver from selenium.common import WebDriverException @@ -39,9 +40,14 @@ def _create_chrome_driver(*, headless: bool) -> webdriver.Chrome: options.add_argument("--disable-dev-shm-usage") if headless: options.add_argument("--headless=new") - driver = webdriver.Chrome(options=options) - WebDriverWait(driver, 5).until(EC.number_of_windows_to_be(1)) - return driver + + try: + driver = webdriver.Chrome(options=options) + WebDriverWait(driver, 5).until(EC.number_of_windows_to_be(1)) + return driver + except (urllib3.exceptions.ReadTimeoutError, TimeoutError, WebDriverException) as e: + error_msg = f"Chrome driver initialization failed: {e}" + raise RuntimeError(error_msg) from e def _create_firefox_driver(*, headless: bool) -> webdriver.Firefox: @@ -52,9 +58,14 @@ def _create_firefox_driver(*, headless: bool) -> webdriver.Firefox: options.set_preference("network.http.use-cache", value=False) if headless: options.add_argument("--headless") - driver = webdriver.Firefox(options=options) - WebDriverWait(driver, 5).until(EC.number_of_windows_to_be(1)) - return driver + + try: + driver = webdriver.Firefox(options=options) + WebDriverWait(driver, 5).until(EC.number_of_windows_to_be(1)) + return driver + except (urllib3.exceptions.ReadTimeoutError, TimeoutError, WebDriverException) as e: + error_msg = f"Firefox driver initialization failed: {e}" + raise RuntimeError(error_msg) from e @pytest.fixture( @@ -67,41 +78,26 @@ def session(request: FixtureRequest) -> Generator[requestium.Session, None, None headless = mode == "headless" driver: webdriver.Chrome | webdriver.Firefox - if browser == "chrome": - driver = _create_chrome_driver(headless=headless) - elif browser == "firefox": - driver = _create_firefox_driver(headless=headless) - else: - msg = f"Unknown driver type: {browser}" - raise ValueError(msg) try: + if browser == "chrome": + driver = _create_chrome_driver(headless=headless) + elif browser == "firefox": + driver = _create_firefox_driver(headless=headless) + else: + msg = f"Unknown driver type: {browser}" + raise ValueError(msg) + assert driver.name in browser session = requestium.Session(driver=cast("DriverMixin", driver)) assert session.driver.name in browser yield session - finally: - with contextlib.suppress(WebDriverException, OSError): - driver.quit() - -@pytest.fixture(autouse=True) -def ensure_valid_session(request: FixtureRequest) -> None: - """Skip test if browser context is discarded.""" - # Only run if the test actually uses the session fixture - if "session" not in request.fixturenames: - return + except RuntimeError as e: + # Driver creation failed - skip all tests using this session + pytest.skip(str(e)) - session = request.getfixturevalue("session") - try: - _ = session.driver.current_url - _ = session.driver.window_handles - except WebDriverException as e: - if "Browsing context has been discarded" not in str(e): - raise - - try: - session.driver.switch_to.new_window("tab") - except WebDriverException: - pytest.skip("Browser context discarded and cannot be recovered") + finally: + with contextlib.suppress(WebDriverException, OSError, Exception): + driver.quit() From 20287099e5eb159305105b25c8e7b41f0f567447 Mon Sep 17 00:00:00 2001 From: Ramona T Date: Sun, 18 Jan 2026 10:09:12 -0500 Subject: [PATCH 6/9] auto update Chrome driver in workflow --- .github/workflows/python.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml index 06342eb..403cd7d 100644 --- a/.github/workflows/python.yml +++ b/.github/workflows/python.yml @@ -62,6 +62,7 @@ jobs: - uses: GabrielBB/xvfb-action@5bcda06da84ba084708898801da79736b88e00a9 env: + SE_AUTO_UPDATE: true # opt-in to selenium 5 behavior COVERAGE_FILE: .coverage.${{ matrix.os }}.${{ matrix.python-version }}.${{ matrix.resolution }} with: run: pytest -r A From 7345697a109a066ed9c8176c8f0922e4f74c75ed Mon Sep 17 00:00:00 2001 From: Ramona T Date: Sun, 18 Jan 2026 10:14:40 -0500 Subject: [PATCH 7/9] add back context discarded error handling + attempt recovery in conftest.py --- tests/conftest.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index 0c05c02..8450992 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -92,6 +92,18 @@ def session(request: FixtureRequest) -> Generator[requestium.Session, None, None session = requestium.Session(driver=cast("DriverMixin", driver)) assert session.driver.name in browser + try: + _ = session.driver.current_url + _ = session.driver.window_handles + except WebDriverException as e: + if "Browsing context has been discarded" not in str(e): + raise + + try: + session.driver.switch_to.new_window("tab") + except WebDriverException: + pytest.skip("Browser context discarded and cannot be recovered") + yield session except RuntimeError as e: From 081d1a72b2f5b652a1ae98dfd72d2b4a08e5d64a Mon Sep 17 00:00:00 2001 From: Ramona T Date: Sun, 18 Jan 2026 10:18:24 -0500 Subject: [PATCH 8/9] fix indentation --- tests/conftest.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 8450992..e0f3a27 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -92,17 +92,17 @@ def session(request: FixtureRequest) -> Generator[requestium.Session, None, None session = requestium.Session(driver=cast("DriverMixin", driver)) assert session.driver.name in browser - try: - _ = session.driver.current_url - _ = session.driver.window_handles - except WebDriverException as e: - if "Browsing context has been discarded" not in str(e): - raise - try: - session.driver.switch_to.new_window("tab") - except WebDriverException: - pytest.skip("Browser context discarded and cannot be recovered") + _ = session.driver.current_url + _ = session.driver.window_handles + except WebDriverException as e: + if "Browsing context has been discarded" not in str(e): + raise + + try: + session.driver.switch_to.new_window("tab") + except WebDriverException: + pytest.skip("Browser context discarded and cannot be recovered") yield session From e3b7277682c888c912d47b30762933ec6481afa1 Mon Sep 17 00:00:00 2001 From: Ramona T Date: Sun, 18 Jan 2026 13:28:29 -0500 Subject: [PATCH 9/9] re-use session validation across tests that don't use session fixture --- tests/conftest.py | 39 +++++++++++++++++++++++++-------------- tests/test_session.py | 7 +++++++ 2 files changed, 32 insertions(+), 14 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index e0f3a27..dcd9b8b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -68,6 +68,26 @@ def _create_firefox_driver(*, headless: bool) -> webdriver.Firefox: raise RuntimeError(error_msg) from e +def validate_session(session: requestium.Session) -> None: + """ + Check basic validity of requestium Session object. + + If browser context is missing, try recovering. + If attempted recovery raises WebDriverException, skip test. + """ + try: + _ = session.driver.current_url + _ = session.driver.window_handles + except WebDriverException as e: + if "Browsing context has been discarded" not in str(e): + raise + + try: + session.driver.switch_to.new_window("tab") + except WebDriverException as e: + pytest.skip(f"Browser context discarded and cannot be recovered: {e!s}") + + @pytest.fixture( params=["chrome-headless", "chrome", "firefox-headless", "firefox"], scope="module", @@ -77,7 +97,7 @@ def session(request: FixtureRequest) -> Generator[requestium.Session, None, None browser, _, mode = driver_type.partition("-") headless = mode == "headless" - driver: webdriver.Chrome | webdriver.Firefox + driver: webdriver.Chrome | webdriver.Firefox | None = None try: if browser == "chrome": @@ -92,17 +112,7 @@ def session(request: FixtureRequest) -> Generator[requestium.Session, None, None session = requestium.Session(driver=cast("DriverMixin", driver)) assert session.driver.name in browser - try: - _ = session.driver.current_url - _ = session.driver.window_handles - except WebDriverException as e: - if "Browsing context has been discarded" not in str(e): - raise - - try: - session.driver.switch_to.new_window("tab") - except WebDriverException: - pytest.skip("Browser context discarded and cannot be recovered") + validate_session(session) yield session @@ -111,5 +121,6 @@ def session(request: FixtureRequest) -> Generator[requestium.Session, None, None pytest.skip(str(e)) finally: - with contextlib.suppress(WebDriverException, OSError, Exception): - driver.quit() + if driver: + with contextlib.suppress(WebDriverException, OSError, Exception): + driver.quit() diff --git a/tests/test_session.py b/tests/test_session.py index d8985cf..a059c82 100644 --- a/tests/test_session.py +++ b/tests/test_session.py @@ -7,6 +7,8 @@ import requestium.requestium +from .conftest import validate_session + @pytest.mark.parametrize( "headless", @@ -19,6 +21,7 @@ ) def test_initialize_session_without_explicit_driver(example_html: str, headless: bool) -> None: # noqa: FBT001 session = requestium.Session(headless=headless) + validate_session(session) session.driver.get(f"data:text/html,{example_html}") session.driver.ensure_element(By.TAG_NAME, "h1") @@ -30,6 +33,7 @@ def test_initialize_session_without_explicit_driver(example_html: str, headless: def test_initialize_session_with_webdriver_options(example_html: str) -> None: session = requestium.Session(webdriver_options={"arguments": ["headless=new"]}) + validate_session(session) session.driver.get(f"data:text/html,{example_html}") session.driver.ensure_element(By.TAG_NAME, "h1") @@ -41,6 +45,7 @@ def test_initialize_session_with_webdriver_options(example_html: str) -> None: def test_initialize_session_with_experimental_options(example_html: str) -> None: session = requestium.Session(webdriver_options={"experimental_options": {"useAutomationExtension": False}}) + validate_session(session) session.driver.get(f"data:text/html,{example_html}") session.driver.ensure_element(By.TAG_NAME, "h1") @@ -52,6 +57,7 @@ def test_initialize_session_with_experimental_options(example_html: str) -> None def test_initialize_session_with_webdriver_prefs(example_html: str) -> None: session = requestium.Session(webdriver_options={"prefs": {"plugins.always_open_pdf_externally": True}}) + validate_session(session) session.driver.get(f"data:text/html,{example_html}") session.driver.ensure_element(By.TAG_NAME, "h1") @@ -64,6 +70,7 @@ def test_initialize_session_with_webdriver_prefs(example_html: str) -> None: def test_initialize_session_with_extension(example_html: str) -> None: test_extension_path = Path(__file__).parent / "resources/test_extension.crx" session = requestium.Session(webdriver_options={"extensions": [str(test_extension_path)]}) + validate_session(session) session.driver.get(f"data:text/html,{example_html}") session.driver.ensure_element(By.TAG_NAME, "h1")