From 557d3401d45065e4a429483a7c34076c124c7b0f Mon Sep 17 00:00:00 2001 From: Ramona T Date: Tue, 2 Dec 2025 10:48:42 -0500 Subject: [PATCH 01/31] using local html instead of relying on external server in testing --- tests/conftest.py | 10 ++++++++++ tests/test__start_chrome_driver.py | 7 ++----- tests/test_ensure_elements_deprecation.py | 6 +++--- tests/test_session.py | 10 +++++----- tests/test_user_agent.py | 4 ++-- 5 files changed, 22 insertions(+), 15 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index a71a00f..7ddf003 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -11,6 +11,16 @@ from requestium.requestium_mixin import DriverMixin +@pytest.fixture +def example_html() -> str: + return """ + + The Internet +

Test Page

+ + """ + + @pytest.fixture( params=[ "chrome-headless", diff --git a/tests/test__start_chrome_driver.py b/tests/test__start_chrome_driver.py index 21cc7a0..2ff77c7 100644 --- a/tests/test__start_chrome_driver.py +++ b/tests/test__start_chrome_driver.py @@ -1,14 +1,11 @@ -import time - import pytest import requestium.requestium -def test__start_chrome_driver(session: requestium.Session) -> None: +def test__start_chrome_driver(session: requestium.Session, example_html: str) -> None: session._start_chrome_browser() - session.driver.get("http://the-internet.herokuapp.com") - time.sleep(1) + session.driver.get(f"data:text/html,{example_html}") title = session.driver.title assert title == "The Internet" diff --git a/tests/test_ensure_elements_deprecation.py b/tests/test_ensure_elements_deprecation.py index 431277f..1c6318e 100644 --- a/tests/test_ensure_elements_deprecation.py +++ b/tests/test_ensure_elements_deprecation.py @@ -3,7 +3,7 @@ import requestium.requestium -def test_deprecation_warning_for_ensure_element_locators_with_underscores(session: requestium.Session) -> None: - session.driver.get("http://the-internet.herokuapp.com") +def test_deprecation_warning_for_ensure_element_locators_with_underscores(session: requestium.Session, example_html: str) -> None: + session.driver.get(f"data:text/html,{example_html}") with pytest.warns(DeprecationWarning): - session.driver.ensure_element("class_name", "no-js") + session.driver.ensure_element("tag_name", "h1") diff --git a/tests/test_session.py b/tests/test_session.py index 2226ee3..c89aafa 100644 --- a/tests/test_session.py +++ b/tests/test_session.py @@ -3,12 +3,12 @@ import requestium.requestium -def test_simple_page_load(session: requestium.Session) -> None: - session.driver.get("http://the-internet.herokuapp.com") - session.driver.ensure_element(By.ID, "content") +def test_simple_page_load(session: requestium.Session, example_html: str) -> None: + session.driver.get(f"data:text/html,{example_html}") + session.driver.ensure_element(By.TAG_NAME, "h1") title = session.driver.title - heading = session.driver.find_element(By.XPATH, '//*[@id="content"]/h1') + heading = session.driver.find_element(By.TAG_NAME, "h1") assert title == "The Internet" - assert heading.text == "Welcome to the-internet" + assert heading.text == "Test Page" diff --git a/tests/test_user_agent.py b/tests/test_user_agent.py index 7c59fdf..2dd2eab 100644 --- a/tests/test_user_agent.py +++ b/tests/test_user_agent.py @@ -1,13 +1,13 @@ import requestium.requestium -def test_copy_user_agent_from_driver(session: requestium.Session) -> None: +def test_copy_user_agent_from_driver(session: requestium.Session, example_html: str) -> None: """Ensure that requests user-agent header has been changed after calling session.copy_user_agent_from_driver().""" pre_copy_requests_useragent = session.headers["user-agent"] assert pre_copy_requests_useragent assert pre_copy_requests_useragent != "" - session.driver.get("http://the-internet.herokuapp.com") + session.driver.get(f"data:text/html,{example_html}") session.copy_user_agent_from_driver() post_copy_requests_useragent = session.headers["user-agent"] From cd20aa59dc43940ee1b8e1c032b71a5b784520c9 Mon Sep 17 00:00:00 2001 From: Ramona T Date: Tue, 2 Dec 2025 10:48:52 -0500 Subject: [PATCH 02/31] fix firefox browser remaining open after testing --- tests/conftest.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index 7ddf003..2973765 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -57,3 +57,5 @@ def session(request): # noqa: ANN001, ANN201 with requestium.Session(driver=cast("DriverMixin", driver)) as session: yield session + + driver.close() From b9a50d5d3b1305e78ebff8525e13ab68a1c887a7 Mon Sep 17 00:00:00 2001 From: Ramona T Date: Tue, 2 Dec 2025 10:46:13 -0500 Subject: [PATCH 03/31] don't rely on google cookies for cookie testing --- tests/test_cookies.py | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/tests/test_cookies.py b/tests/test_cookies.py index 3e6271a..d202af9 100644 --- a/tests/test_cookies.py +++ b/tests/test_cookies.py @@ -4,12 +4,29 @@ import requestium.requestium -def test_transfer_session_cookies_to_driver(session: requestium.Session) -> None: - assert session.cookies.keys() == [] - response = session.get("http://google.com/") - assert response.cookies.keys().sort() == ["AEC", "NID"].sort() +@pytest.fixture( + params=[ + {"name": "session_id", "value": "abc123", "domain": "example.com", "path": "/"}, + {"name": "user_token", "value": "xyz789", "domain": "example.com", "path": "/"}, + ], + ids=["session_id", "user_token"], +) +def cookie_data(request) -> dict[str, str]: # noqa: ANN001 + return request.param + + +def test_transfer_session_cookies_to_driver(session: requestium.Session, cookie_data: dict[str, str]) -> None: + session.get(f"http://{cookie_data['domain']}") + session.cookies.set(name=cookie_data["name"], value=cookie_data["value"], domain=cookie_data["domain"], path=cookie_data["path"]) + + assert session.driver.get_cookies() == [] session.transfer_session_cookies_to_driver() - assert session.cookies.keys() == ["AEC", "NID"] + driver_cookies = session.driver.get_cookies() + assert len(driver_cookies) == 1 + assert driver_cookies[0]["name"] == cookie_data["name"] + assert driver_cookies[0]["value"] == cookie_data["value"] + assert driver_cookies[0]["domain"] in [cookie_data["domain"], f".{cookie_data['domain']}"] + assert driver_cookies[0]["path"] == cookie_data["path"] def test_transfer_session_cookies_to_driver_no_domain_error(session: requestium.Session) -> None: From d1491214369d7edaa67d11a5b9d2c4afb8dfa4fb Mon Sep 17 00:00:00 2001 From: Ramona T Date: Tue, 2 Dec 2025 10:47:30 -0500 Subject: [PATCH 04/31] add driver to session cookie transfer test --- tests/test_cookies.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/tests/test_cookies.py b/tests/test_cookies.py index d202af9..8e071e0 100644 --- a/tests/test_cookies.py +++ b/tests/test_cookies.py @@ -15,6 +15,15 @@ def cookie_data(request) -> dict[str, str]: # noqa: ANN001 return request.param +def test_transfer_driver_cookies_to_session(session: requestium.Session, cookie_data: dict[str, str]) -> None: + session.driver.get(f"https://{cookie_data['domain']}") + session.driver.add_cookie(cookie_data) + + assert session.cookies.keys() == [] + session.transfer_driver_cookies_to_session() + assert session.cookies.keys() == [cookie_data["name"]] + + def test_transfer_session_cookies_to_driver(session: requestium.Session, cookie_data: dict[str, str]) -> None: session.get(f"http://{cookie_data['domain']}") session.cookies.set(name=cookie_data["name"], value=cookie_data["value"], domain=cookie_data["domain"], path=cookie_data["path"]) From fa7d12d59b524e37f5e3ab1f571a2f2385e9375b Mon Sep 17 00:00:00 2001 From: Ramona T Date: Tue, 2 Dec 2025 11:09:39 -0500 Subject: [PATCH 05/31] better solution to fix browsers remaining open after testing --- tests/conftest.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 2973765..8eb0947 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,9 +1,11 @@ # import os # import shutil +import contextlib from typing import TYPE_CHECKING, cast import pytest from selenium import webdriver +from selenium.common import WebDriverException import requestium @@ -55,7 +57,9 @@ def session(request): # noqa: ANN001, ANN201 msg = f"Unknown driver type: {driver_type}" raise ValueError(msg) - with requestium.Session(driver=cast("DriverMixin", driver)) as session: - yield session + session = requestium.Session(driver=cast("DriverMixin", driver)) + yield session - driver.close() + # Close all windows and end the session + with contextlib.suppress(WebDriverException, OSError): + driver.quit() From f5405a714c1f9f2dfb72ed49020affe6f4868ace Mon Sep 17 00:00:00 2001 From: Ramona T Date: Tue, 2 Dec 2025 00:37:09 -0500 Subject: [PATCH 06/31] update codeql workflow to fix failing check --- .github/workflows/codeql.yml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml index 3980b5f..948f83a 100644 --- a/.github/workflows/codeql.yml +++ b/.github/workflows/codeql.yml @@ -27,7 +27,7 @@ jobs: - python steps: - - uses: step-security/harden-runner@f4a75cfd619ee5ce8d5b864b0d183aff3c69b55a + - uses: step-security/harden-runner@95d9a5deda9de15063e7595e9719c11c38c90ae2 with: disable-sudo: true egress-policy: block @@ -36,15 +36,16 @@ jobs: github.com:443 objects.githubusercontent.com:443 uploads.github.com:443 + release-assets.githubusercontent.com - - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 + - uses: actions/checkout@1af3b93b6815bc44a9784bd300feb67ff0d1eeb3 - - uses: github/codeql-action/init@0499de31b99561a6d14a36a5f662c2a54f91beee + - uses: github/codeql-action/init@e12f0178983d466f2f6028f5cc7a6d786fd97f4b with: languages: ${{ matrix.language }} - - uses: github/codeql-action/autobuild@0499de31b99561a6d14a36a5f662c2a54f91beee + - uses: github/codeql-action/autobuild@e12f0178983d466f2f6028f5cc7a6d786fd97f4b - - uses: github/codeql-action/analyze@0499de31b99561a6d14a36a5f662c2a54f91beee + - uses: github/codeql-action/analyze@e12f0178983d466f2f6028f5cc7a6d786fd97f4b with: category: /language:${{matrix.language}} From bbd85750cf1bf51c5cfec65e2273ecefd9d5dbe0 Mon Sep 17 00:00:00 2001 From: Ramona T Date: Tue, 2 Dec 2025 14:19:32 -0500 Subject: [PATCH 07/31] add test for creating session object headless --- tests/test__start_chrome_driver.py | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/tests/test__start_chrome_driver.py b/tests/test__start_chrome_driver.py index 2ff77c7..70f97d1 100644 --- a/tests/test__start_chrome_driver.py +++ b/tests/test__start_chrome_driver.py @@ -1,16 +1,27 @@ + import pytest +from selenium.webdriver.common.by import By import requestium.requestium -def test__start_chrome_driver(session: requestium.Session, example_html: str) -> None: - session._start_chrome_browser() +@pytest.mark.parametrize( + "headless", + [ + False, + True, + ], + ids=["headless=false", "headless=true"], +) +def test__start_chrome_driver(session: requestium.Session, example_html: str, headless: bool) -> None: # noqa: FBT001 + session._start_chrome_browser(headless=headless) session.driver.get(f"data:text/html,{example_html}") - title = session.driver.title - assert title == "The Internet" + session.driver.ensure_element(By.TAG_NAME, "h1") + + assert session.driver.title == "The Internet" -def test__start_chrome_driver_options_typeerror() -> None: +def test__start_chrome_driver_webdriver_options_typeerror() -> None: invalid_webdriver_options = {"arguments": "invalid_string"} with ( requestium.Session(webdriver_options=invalid_webdriver_options) as session, From 705412f01c21a6770e58825798470e36e18318fc Mon Sep 17 00:00:00 2001 From: Ramona T Date: Tue, 2 Dec 2025 14:20:12 -0500 Subject: [PATCH 08/31] add test for creating session object with webdriver options --- tests/test__start_chrome_driver.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/test__start_chrome_driver.py b/tests/test__start_chrome_driver.py index 70f97d1..0d49643 100644 --- a/tests/test__start_chrome_driver.py +++ b/tests/test__start_chrome_driver.py @@ -1,5 +1,7 @@ +import contextlib import pytest +from selenium.common import WebDriverException from selenium.webdriver.common.by import By import requestium.requestium @@ -21,6 +23,18 @@ def test__start_chrome_driver(session: requestium.Session, example_html: str, he assert session.driver.title == "The Internet" +def test__start_chrome_driver_webdriver_options(example_html: str) -> None: + webdriver_options = {"arguments": ["headless=new"]} + session = requestium.Session(webdriver_options=webdriver_options) + session.driver.get(f"data:text/html,{example_html}") + session.driver.ensure_element(By.TAG_NAME, "h1") + + assert session.driver.title == "The Internet" + + with contextlib.suppress(WebDriverException, OSError): + session.driver.quit() + + def test__start_chrome_driver_webdriver_options_typeerror() -> None: invalid_webdriver_options = {"arguments": "invalid_string"} with ( From e26e7970835d5b212b083e4598dbe49d5c018e58 Mon Sep 17 00:00:00 2001 From: Ramona T Date: Tue, 2 Dec 2025 14:20:36 -0500 Subject: [PATCH 09/31] add test for domain filter for session to driver cookie transfer --- tests/test_cookies.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/test_cookies.py b/tests/test_cookies.py index 8e071e0..cea0c49 100644 --- a/tests/test_cookies.py +++ b/tests/test_cookies.py @@ -38,6 +38,21 @@ def test_transfer_session_cookies_to_driver(session: requestium.Session, cookie_ assert driver_cookies[0]["path"] == cookie_data["path"] +def test_transfer_session_cookies_to_driver_domain_filter(session: requestium.Session, cookie_data: dict[str, str]) -> None: + session.get(f"http://{cookie_data['domain']}") + session.cookies.set(name="junk_cookie", value="sfkjn782", domain="google.com", path=cookie_data["path"]) + session.cookies.set(name=cookie_data["name"], value=cookie_data["value"], domain=cookie_data["domain"], path=cookie_data["path"]) + + assert session.driver.get_cookies() == [] + session.transfer_session_cookies_to_driver(domain=cookie_data["domain"]) + driver_cookies = session.driver.get_cookies() + assert len(driver_cookies) == 1 + assert driver_cookies[0]["name"] == cookie_data["name"] + assert driver_cookies[0]["value"] == cookie_data["value"] + assert driver_cookies[0]["domain"] in [cookie_data["domain"], f".{cookie_data['domain']}"] + assert driver_cookies[0]["path"] == cookie_data["path"] + + def test_transfer_session_cookies_to_driver_no_domain_error(session: requestium.Session) -> None: with ( pytest.raises( From 7ece85058383b79ce6a5f0e1b5bf32c23bcbf16a Mon Sep 17 00:00:00 2001 From: Ramona T Date: Tue, 2 Dec 2025 14:20:49 -0500 Subject: [PATCH 10/31] add test for creating session without explicit driver selection --- tests/test_session.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/tests/test_session.py b/tests/test_session.py index c89aafa..23bfd89 100644 --- a/tests/test_session.py +++ b/tests/test_session.py @@ -1,3 +1,6 @@ +import contextlib + +from selenium.common import WebDriverException from selenium.webdriver.common.by import By import requestium.requestium @@ -12,3 +15,14 @@ def test_simple_page_load(session: requestium.Session, example_html: str) -> Non assert title == "The Internet" assert heading.text == "Test Page" + + +def test_session_without_explicit_driver(example_html: str) -> None: + session = requestium.Session() + session.driver.get(f"data:text/html,{example_html}") + session.driver.ensure_element(By.TAG_NAME, "h1") + + assert session.driver.title == "The Internet" + + with contextlib.suppress(WebDriverException, OSError): + session.driver.quit() From 5664cb5603fe12a63920a80c3424300b855d3f4b Mon Sep 17 00:00:00 2001 From: Ramona T Date: Tue, 2 Dec 2025 14:36:08 -0500 Subject: [PATCH 11/31] retry creation of firefox driver to work around timeouts when run in github actions workflow --- tests/conftest.py | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 8eb0947..5d9b12b 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,11 +1,14 @@ # import os # import shutil +from __future__ import annotations + import contextlib +import time from typing import TYPE_CHECKING, cast import pytest from selenium import webdriver -from selenium.common import WebDriverException +from selenium.common.exceptions import WebDriverException import requestium @@ -23,6 +26,23 @@ def example_html() -> str: """ +def create_firefox_driver(*, headless: bool = False, max_retries: int = 3) -> webdriver.Firefox | None: + """Create Firefox driver with retry logic.""" + for attempt in range(max_retries): + try: + options = webdriver.FirefoxOptions() + if headless: + options.add_argument("--headless") + + service = webdriver.FirefoxService(timeout=300) + return webdriver.Firefox(options=options, service=service) + except (WebDriverException, TimeoutError) as e: + if attempt == max_retries - 1: + raise e + time.sleep(2) # Brief delay before retry + return None + + @pytest.fixture( params=[ "chrome-headless", @@ -52,7 +72,7 @@ def session(request): # noqa: ANN001, ANN201 options.add_argument("--headless") driver = webdriver.Firefox(options=options) elif driver_type == "firefox": - driver = webdriver.Firefox() + driver = create_firefox_driver(headless=False) else: msg = f"Unknown driver type: {driver_type}" raise ValueError(msg) From ec014bf02f21a53c999a7f0650fdf143832b79e4 Mon Sep 17 00:00:00 2001 From: Ramona T Date: Tue, 2 Dec 2025 15:28:15 -0500 Subject: [PATCH 12/31] add more session initialization tests --- tests/resources/test_extension.crx | Bin 0 -> 342 bytes tests/test__start_chrome_driver.py | 47 ------------------- tests/test_session.py | 72 ++++++++++++++++++++++++++++- 3 files changed, 70 insertions(+), 49 deletions(-) create mode 100644 tests/resources/test_extension.crx delete mode 100644 tests/test__start_chrome_driver.py diff --git a/tests/resources/test_extension.crx b/tests/resources/test_extension.crx new file mode 100644 index 0000000000000000000000000000000000000000..feadf74146009557bccea3e4107e3ecc6a046def GIT binary patch literal 342 zcmWIWW@Zs#U|`^2*jv>UEq!wHd?g@H0f>2lI4LnXJH05sG%rOjtJwS8dF^xOJ+GcV z>3=<-e65D(S?zOY&Ig}3>F?v|8+d&S8$$q!VR70etAl`sKgEj>jTnh|1<$cCCbW>0Om#3~?U;)S$X75)T8j4pmC0|K4 zxC8}tPwqD2_FAxL-^|LHJ3B48R29~)ELgi0XgMR32m@}v09^qF+ZsU>iqFusqPrZT dk%3`bqc4z&;*S7tRyL3{6A+dG=~EyM0|0j}V<7+l literal 0 HcmV?d00001 diff --git a/tests/test__start_chrome_driver.py b/tests/test__start_chrome_driver.py deleted file mode 100644 index 0d49643..0000000 --- a/tests/test__start_chrome_driver.py +++ /dev/null @@ -1,47 +0,0 @@ -import contextlib - -import pytest -from selenium.common import WebDriverException -from selenium.webdriver.common.by import By - -import requestium.requestium - - -@pytest.mark.parametrize( - "headless", - [ - False, - True, - ], - ids=["headless=false", "headless=true"], -) -def test__start_chrome_driver(session: requestium.Session, example_html: str, headless: bool) -> None: # noqa: FBT001 - session._start_chrome_browser(headless=headless) - session.driver.get(f"data:text/html,{example_html}") - session.driver.ensure_element(By.TAG_NAME, "h1") - - assert session.driver.title == "The Internet" - - -def test__start_chrome_driver_webdriver_options(example_html: str) -> None: - webdriver_options = {"arguments": ["headless=new"]} - session = requestium.Session(webdriver_options=webdriver_options) - session.driver.get(f"data:text/html,{example_html}") - session.driver.ensure_element(By.TAG_NAME, "h1") - - assert session.driver.title == "The Internet" - - with contextlib.suppress(WebDriverException, OSError): - session.driver.quit() - - -def test__start_chrome_driver_webdriver_options_typeerror() -> None: - invalid_webdriver_options = {"arguments": "invalid_string"} - with ( - requestium.Session(webdriver_options=invalid_webdriver_options) as session, - pytest.raises( - TypeError, - match=f"'arguments' option must be a list, but got {type(invalid_webdriver_options['arguments']).__name__}", - ), - ): - session._start_chrome_browser() diff --git a/tests/test_session.py b/tests/test_session.py index 23bfd89..398f706 100644 --- a/tests/test_session.py +++ b/tests/test_session.py @@ -1,5 +1,7 @@ import contextlib +from pathlib import Path +import pytest from selenium.common import WebDriverException from selenium.webdriver.common.by import By @@ -17,8 +19,17 @@ def test_simple_page_load(session: requestium.Session, example_html: str) -> Non assert heading.text == "Test Page" -def test_session_without_explicit_driver(example_html: str) -> None: - session = requestium.Session() +@pytest.mark.parametrize( + "headless", + [ + None, + False, + True, + ], + ids=["no_headless_arg", "headless=false", "headless=true"], +) +def test_session_without_explicit_driver(example_html: str, headless: bool) -> None: # noqa: FBT001 + session = requestium.Session(headless=headless) session.driver.get(f"data:text/html,{example_html}") session.driver.ensure_element(By.TAG_NAME, "h1") @@ -26,3 +37,60 @@ def test_session_without_explicit_driver(example_html: str) -> None: with contextlib.suppress(WebDriverException, OSError): session.driver.quit() + + +def test__start_chrome_driver_webdriver_options(example_html: str) -> None: + session = requestium.Session(webdriver_options={"arguments": ["headless=new"]}) + session.driver.get(f"data:text/html,{example_html}") + session.driver.ensure_element(By.TAG_NAME, "h1") + + assert session.driver.title == "The Internet" + + with contextlib.suppress(WebDriverException, OSError): + session.driver.quit() + + +def test__start_chrome_driver_webdriver_experimental_options(example_html: str) -> None: + session = requestium.Session(webdriver_options={"experimental_options": {"useAutomationExtension": False}}) + session.driver.get(f"data:text/html,{example_html}") + session.driver.ensure_element(By.TAG_NAME, "h1") + + assert session.driver.title == "The Internet" + + with contextlib.suppress(WebDriverException, OSError): + session.driver.quit() + + +def test__start_chrome_driver_webdriver_prefs(example_html: str) -> None: + session = requestium.Session(webdriver_options={"prefs": {"plugins.always_open_pdf_externally": True}}) + session.driver.get(f"data:text/html,{example_html}") + session.driver.ensure_element(By.TAG_NAME, "h1") + + assert session.driver.title == "The Internet" + + with contextlib.suppress(WebDriverException, OSError): + session.driver.quit() + + +def test__start_chrome_driver_webdriver_extensions(example_html: str) -> None: + test_extension_path = Path(__file__).parent / "resources/test_extension.crx" + session = requestium.Session(webdriver_options={"extensions": [str(test_extension_path)]}) + session.driver.get(f"data:text/html,{example_html}") + session.driver.ensure_element(By.TAG_NAME, "h1") + + assert session.driver.title == "The Internet" + + with contextlib.suppress(WebDriverException, OSError): + session.driver.quit() + + +def test__start_chrome_driver_webdriver_options_typeerror() -> None: + invalid_webdriver_options = {"arguments": "invalid_string"} + with ( + requestium.Session(webdriver_options=invalid_webdriver_options) as session, + pytest.raises( + TypeError, + match="'arguments' option must be a list, but got str", + ), + ): + session._start_chrome_browser() From 73f0ce057937e67412b540b4070e5f1dc8fe3250 Mon Sep 17 00:00:00 2001 From: Ramona T Date: Tue, 2 Dec 2025 17:14:26 -0500 Subject: [PATCH 13/31] update naming of test_session tests --- tests/test_session.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/tests/test_session.py b/tests/test_session.py index 398f706..b8994c6 100644 --- a/tests/test_session.py +++ b/tests/test_session.py @@ -28,7 +28,7 @@ def test_simple_page_load(session: requestium.Session, example_html: str) -> Non ], ids=["no_headless_arg", "headless=false", "headless=true"], ) -def test_session_without_explicit_driver(example_html: str, headless: bool) -> None: # noqa: FBT001 +def test_initialize_session_without_explicit_driver(example_html: str, headless: bool) -> None: # noqa: FBT001 session = requestium.Session(headless=headless) session.driver.get(f"data:text/html,{example_html}") session.driver.ensure_element(By.TAG_NAME, "h1") @@ -39,7 +39,7 @@ def test_session_without_explicit_driver(example_html: str, headless: bool) -> N session.driver.quit() -def test__start_chrome_driver_webdriver_options(example_html: str) -> None: +def test_initialize_session_with_webdriver_options(example_html: str) -> None: session = requestium.Session(webdriver_options={"arguments": ["headless=new"]}) session.driver.get(f"data:text/html,{example_html}") session.driver.ensure_element(By.TAG_NAME, "h1") @@ -50,7 +50,7 @@ def test__start_chrome_driver_webdriver_options(example_html: str) -> None: session.driver.quit() -def test__start_chrome_driver_webdriver_experimental_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}}) session.driver.get(f"data:text/html,{example_html}") session.driver.ensure_element(By.TAG_NAME, "h1") @@ -61,7 +61,7 @@ def test__start_chrome_driver_webdriver_experimental_options(example_html: str) session.driver.quit() -def test__start_chrome_driver_webdriver_prefs(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}}) session.driver.get(f"data:text/html,{example_html}") session.driver.ensure_element(By.TAG_NAME, "h1") @@ -72,7 +72,7 @@ def test__start_chrome_driver_webdriver_prefs(example_html: str) -> None: session.driver.quit() -def test__start_chrome_driver_webdriver_extensions(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)]}) session.driver.get(f"data:text/html,{example_html}") From 68cdd415fa7cdbd9767ac94d7121ba078f752b24 Mon Sep 17 00:00:00 2001 From: Ramona T Date: Tue, 2 Dec 2025 17:14:42 -0500 Subject: [PATCH 14/31] create test_mixin --- tests/conftest.py | 9 +++++- tests/test_mixin.py | 74 +++++++++++++++++++++++++++++++++++++++++++ tests/test_session.py | 11 ------- 3 files changed, 82 insertions(+), 12 deletions(-) create mode 100644 tests/test_mixin.py diff --git a/tests/conftest.py b/tests/conftest.py index 5d9b12b..c618423 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -21,7 +21,14 @@ def example_html() -> str: return """ The Internet -

Test Page

+ +

Test Header 1

+

Test Header 2

+

Test Header 3

+

Test Paragraph 1

+

Test Link 1

+

Test Link 2

+ """ diff --git a/tests/test_mixin.py b/tests/test_mixin.py new file mode 100644 index 0000000..0873200 --- /dev/null +++ b/tests/test_mixin.py @@ -0,0 +1,74 @@ +from selenium.webdriver.remote.webelement import WebElement + +import requestium.requestium + + +def test_ensure_element_by_id(session: requestium.Session, example_html: str) -> None: + session.driver.get(f"data:text/html,{example_html}") + + element = session.driver.ensure_element_by_id("test-header") + assert isinstance(element, WebElement) + assert element.text == "Test Header 2" + + +def test_ensure_element_by_name(session: requestium.Session, example_html: str) -> None: + session.driver.get(f"data:text/html,{example_html}") + + element = session.driver.ensure_element_by_name("link-paragraph") + assert isinstance(element, WebElement) + assert element.text == "Test Link 1" + + +def test_ensure_element_by_xpath(session: requestium.Session, example_html: str) -> None: + session.driver.get(f"data:text/html,{example_html}") + + element = session.driver.ensure_element_by_xpath("//a[text()='Test Link 2']") + assert isinstance(element, WebElement) + assert element.text == "Test Link 2" + + +def test_ensure_element_by_link_text(session: requestium.Session, example_html: str) -> None: + session.driver.get(f"data:text/html,{example_html}") + + element = session.driver.ensure_element_by_link_text("Test Link 1") + assert isinstance(element, WebElement) + assert element.text == "Test Link 1" + + +def test_ensure_element_by_partial_link_text(session: requestium.Session, example_html: str) -> None: + session.driver.get(f"data:text/html,{example_html}") + + element = session.driver.ensure_element_by_partial_link_text("Link 2") + assert isinstance(element, WebElement) + assert element.text == "Test Link 2" + + +def test_ensure_element_by_tag_name(session: requestium.Session, example_html: str) -> None: + session.driver.get(f"data:text/html,{example_html}") + + element = session.driver.ensure_element_by_tag_name("h1") + assert isinstance(element, WebElement) + assert element.text == "Test Header 1" + + +def test_ensure_element_by_class_name(session: requestium.Session, example_html: str) -> None: + session.driver.get(f"data:text/html,{example_html}") + + element = session.driver.ensure_element_by_class_name("body-text") + assert isinstance(element, WebElement) + assert element.text == "Test Paragraph 1" + + +def test_ensure_element_by_css_selector(session: requestium.Session, example_html: str) -> None: + session.driver.get(f"data:text/html,{example_html}") + + element = session.driver.ensure_element_by_css_selector(".body-text") + assert isinstance(element, WebElement) + assert element.text == "Test Paragraph 1" + + +def test_simple_page_load(session: requestium.Session, example_html: str) -> None: + session.driver.get(f"data:text/html,{example_html}") + + title = session.driver.title + assert title == "The Internet" diff --git a/tests/test_session.py b/tests/test_session.py index b8994c6..d8985cf 100644 --- a/tests/test_session.py +++ b/tests/test_session.py @@ -8,17 +8,6 @@ import requestium.requestium -def test_simple_page_load(session: requestium.Session, example_html: str) -> None: - session.driver.get(f"data:text/html,{example_html}") - session.driver.ensure_element(By.TAG_NAME, "h1") - - title = session.driver.title - heading = session.driver.find_element(By.TAG_NAME, "h1") - - assert title == "The Internet" - assert heading.text == "Test Page" - - @pytest.mark.parametrize( "headless", [ From 59f95d2204fb852911fcc7cb232c75eb8a12e9dc Mon Sep 17 00:00:00 2001 From: Ramona T Date: Tue, 2 Dec 2025 17:39:12 -0500 Subject: [PATCH 15/31] fix type hint in requestium_mixin.py --- requestium/requestium_mixin.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requestium/requestium_mixin.py b/requestium/requestium_mixin.py index c627496..880eb70 100644 --- a/requestium/requestium_mixin.py +++ b/requestium/requestium_mixin.py @@ -8,7 +8,7 @@ import tldextract from parsel.selector import Selector, SelectorList from selenium.common.exceptions import WebDriverException -from selenium.webdriver.common.by import By +from selenium.webdriver.common.by import By, ByType from selenium.webdriver.remote.webdriver import WebDriver as RemoteWebDriver from selenium.webdriver.support import expected_conditions from selenium.webdriver.support.ui import WebDriverWait @@ -130,7 +130,7 @@ def ensure_element_by_class_name(self, selector: str, state: str | None = "prese def ensure_element_by_css_selector(self, selector: str, state: str | None = "present", timeout: float | None = None) -> WebElement | None: return self.ensure_element(By.CSS_SELECTOR, selector, state, timeout) - def ensure_element(self, locator: str, selector: str, state: str | None = "present", timeout: float | None = None) -> WebElement | None: + def ensure_element(self, locator: ByType | str, selector: str, state: str | None = "present", timeout: float | None = None) -> WebElement | None: """ Wait until an element appears or disappears in the browser. From 566b7325e5ee7f31e3785ce5c9e570c3b91c8a53 Mon Sep 17 00:00:00 2001 From: Ramona T Date: Tue, 2 Dec 2025 17:39:34 -0500 Subject: [PATCH 16/31] add parameterized test_ensure_element --- tests/test_ensure_elements_deprecation.py | 9 ------ tests/test_mixin.py | 37 +++++++++++++++++++++++ 2 files changed, 37 insertions(+), 9 deletions(-) delete mode 100644 tests/test_ensure_elements_deprecation.py diff --git a/tests/test_ensure_elements_deprecation.py b/tests/test_ensure_elements_deprecation.py deleted file mode 100644 index 1c6318e..0000000 --- a/tests/test_ensure_elements_deprecation.py +++ /dev/null @@ -1,9 +0,0 @@ -import pytest - -import requestium.requestium - - -def test_deprecation_warning_for_ensure_element_locators_with_underscores(session: requestium.Session, example_html: str) -> None: - session.driver.get(f"data:text/html,{example_html}") - with pytest.warns(DeprecationWarning): - session.driver.ensure_element("tag_name", "h1") diff --git a/tests/test_mixin.py b/tests/test_mixin.py index 0873200..ede7ec5 100644 --- a/tests/test_mixin.py +++ b/tests/test_mixin.py @@ -1,3 +1,5 @@ +import pytest +from selenium.webdriver.common.by import By, ByType from selenium.webdriver.remote.webelement import WebElement import requestium.requestium @@ -67,6 +69,41 @@ def test_ensure_element_by_css_selector(session: requestium.Session, example_htm assert element.text == "Test Paragraph 1" +@pytest.mark.parametrize( + ("locator", "selector", "result"), + [ + (By.ID, "test-header", "Test Header 2"), + (By.NAME, "link-paragraph", "Test Link 1"), + (By.XPATH, "//a[text()='Test Link 2']", "Test Link 2"), + (By.LINK_TEXT, "Test Link 1", "Test Link 1"), + (By.PARTIAL_LINK_TEXT, "Link 2", "Test Link 2"), + (By.TAG_NAME, "h1", "Test Header 1"), + (By.CLASS_NAME, "body-text", "Test Paragraph 1"), + (By.CSS_SELECTOR, ".body-text", "Test Paragraph 1"), + (By.CSS_SELECTOR, "#test-header", "Test Header 2"), + ], + ids=["id", "name", "xpath", "link_text", "partial_link_text", "tag_name", "class_name", "css_selector_class", "css_selector_id"], +) +def test_ensure_element(session: requestium.Session, example_html: str, locator: ByType, selector: str, result: str) -> None: + session.driver.get(f"data:text/html,{example_html}") + + element = session.driver.ensure_element(locator=locator, selector=selector) + assert isinstance(element, WebElement) + assert element.text == result + + element = session.driver.ensure_element(locator, selector) + assert isinstance(element, WebElement) + assert element.text == result + + +def test_deprecation_warning_for_ensure_element_locators_with_underscores(session: requestium.Session, example_html: str) -> None: + session.driver.get(f"data:text/html,{example_html}") + with pytest.warns(DeprecationWarning, match="Support for locator strategy names with underscores is deprecated"): + session.driver.ensure_element(locator="tag_name", selector="h1") + with pytest.warns(DeprecationWarning, match="Support for locator strategy names with underscores is deprecated"): + session.driver.ensure_element("tag_name", "h1") + + def test_simple_page_load(session: requestium.Session, example_html: str) -> None: session.driver.get(f"data:text/html,{example_html}") From 670bf57004506395286bb5c4c198143dcb59cb5c Mon Sep 17 00:00:00 2001 From: Ramona T Date: Tue, 2 Dec 2025 17:39:41 -0500 Subject: [PATCH 17/31] Revert "retry creation of firefox driver to work around timeouts when run in github actions workflow" This reverts commit 5664cb5603fe12a63920a80c3424300b855d3f4b. --- tests/conftest.py | 24 ++---------------------- 1 file changed, 2 insertions(+), 22 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index c618423..74cf719 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,14 +1,11 @@ # import os # import shutil -from __future__ import annotations - import contextlib -import time from typing import TYPE_CHECKING, cast import pytest from selenium import webdriver -from selenium.common.exceptions import WebDriverException +from selenium.common import WebDriverException import requestium @@ -33,23 +30,6 @@ def example_html() -> str: """ -def create_firefox_driver(*, headless: bool = False, max_retries: int = 3) -> webdriver.Firefox | None: - """Create Firefox driver with retry logic.""" - for attempt in range(max_retries): - try: - options = webdriver.FirefoxOptions() - if headless: - options.add_argument("--headless") - - service = webdriver.FirefoxService(timeout=300) - return webdriver.Firefox(options=options, service=service) - except (WebDriverException, TimeoutError) as e: - if attempt == max_retries - 1: - raise e - time.sleep(2) # Brief delay before retry - return None - - @pytest.fixture( params=[ "chrome-headless", @@ -79,7 +59,7 @@ def session(request): # noqa: ANN001, ANN201 options.add_argument("--headless") driver = webdriver.Firefox(options=options) elif driver_type == "firefox": - driver = create_firefox_driver(headless=False) + driver = webdriver.Firefox() else: msg = f"Unknown driver type: {driver_type}" raise ValueError(msg) From 51625fbee6e22fe8d87c5641a28873eb5bec4f49 Mon Sep 17 00:00:00 2001 From: Ramona T Date: Tue, 2 Dec 2025 17:40:40 -0500 Subject: [PATCH 18/31] skip non-headless firefox testing in CI workflows --- tests/conftest.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index 74cf719..f8923c7 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,6 +1,7 @@ # import os # import shutil import contextlib +import os from typing import TYPE_CHECKING, cast import pytest @@ -36,7 +37,7 @@ def example_html() -> str: "chrome", # "chrome-custom-path", "firefox-headless", - "firefox", + pytest.param("firefox", marks=pytest.mark.skipif(os.getenv("CI") == "true", reason="Non-headless Firefox unreliable in CI")), ] ) def session(request): # noqa: ANN001, ANN201 From 24ce87eb82273000317320415f40246240960005 Mon Sep 17 00:00:00 2001 From: Ramona T Date: Tue, 2 Dec 2025 17:59:28 -0500 Subject: [PATCH 19/31] run chrome tests with CI compatibility tweaks --- tests/conftest.py | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index f8923c7..07915ab 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,3 @@ -# import os -# import shutil import contextlib import os from typing import TYPE_CHECKING, cast @@ -35,7 +33,6 @@ def example_html() -> str: params=[ "chrome-headless", "chrome", - # "chrome-custom-path", "firefox-headless", pytest.param("firefox", marks=pytest.mark.skipif(os.getenv("CI") == "true", reason="Non-headless Firefox unreliable in CI")), ] @@ -46,15 +43,14 @@ def session(request): # noqa: ANN001, ANN201 if driver_type == "chrome-headless": options = webdriver.ChromeOptions() options.add_argument("--headless=new") + options.add_argument("--no-sandbox") # Helps when running on Github Actions + options.add_argument("--disable-dev-shm-usage") # Helps when running on Github Actions driver = webdriver.Chrome(options=options) elif driver_type == "chrome": - driver = webdriver.Chrome() - # elif driver_type == "chrome-custom-path": - # chromedriver_name = "chromedriver" - # custom_path = shutil.which(chromedriver_name) - # assert custom_path, f"'{chromedriver_name}' not found in PATH." - # assert os.path.exists(custom_path), f"Custom chromedriver not found at {custom_path}." - # driver = webdriver.Chrome(service=webdriver.ChromeService(executable_path=custom_path)) + options = webdriver.ChromeOptions() + options.add_argument("--no-sandbox") # Helps when running on Github Actions + options.add_argument("--disable-dev-shm-usage") # Helps when running on Github Actions + driver = webdriver.Chrome(options=options) elif driver_type == "firefox-headless": options = webdriver.FirefoxOptions() options.add_argument("--headless") From 0a3ad83f9330fbb670d07a73126744e07448e468 Mon Sep 17 00:00:00 2001 From: Ramona T Date: Tue, 2 Dec 2025 17:59:48 -0500 Subject: [PATCH 20/31] wait for page load before continuing in test_simple_page_load --- tests/test_mixin.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_mixin.py b/tests/test_mixin.py index ede7ec5..ac82b9c 100644 --- a/tests/test_mixin.py +++ b/tests/test_mixin.py @@ -107,5 +107,6 @@ def test_deprecation_warning_for_ensure_element_locators_with_underscores(sessio def test_simple_page_load(session: requestium.Session, example_html: str) -> None: session.driver.get(f"data:text/html,{example_html}") + session.driver.ensure_element_by_tag_name("h1") # wait for page load title = session.driver.title assert title == "The Internet" From 005af3f28ff02b04cf2b91308df9293ea02865cb Mon Sep 17 00:00:00 2001 From: Ramona T Date: Tue, 2 Dec 2025 18:33:30 -0500 Subject: [PATCH 21/31] reusable session fixture across tests for speed and consistency --- tests/conftest.py | 47 +++++++++++++++++++++++-------------------- tests/test_cookies.py | 10 +++++++++ 2 files changed, 35 insertions(+), 22 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 07915ab..6f4e9ee 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1,5 +1,5 @@ import contextlib -import os +from collections.abc import Generator from typing import TYPE_CHECKING, cast import pytest @@ -11,8 +11,10 @@ if TYPE_CHECKING: from requestium.requestium_mixin import DriverMixin +# ruff: noqa FBT003 -@pytest.fixture + +@pytest.fixture(scope="module") def example_html() -> str: return """ @@ -34,29 +36,30 @@ def example_html() -> str: "chrome-headless", "chrome", "firefox-headless", - pytest.param("firefox", marks=pytest.mark.skipif(os.getenv("CI") == "true", reason="Non-headless Firefox unreliable in CI")), - ] + "firefox", + ], + scope="module", ) -def session(request): # noqa: ANN001, ANN201 +def session(request) -> Generator[requestium.Session]: # noqa: ANN001 driver_type = request.param - if driver_type == "chrome-headless": - options = webdriver.ChromeOptions() - options.add_argument("--headless=new") - options.add_argument("--no-sandbox") # Helps when running on Github Actions - options.add_argument("--disable-dev-shm-usage") # Helps when running on Github Actions - driver = webdriver.Chrome(options=options) - elif driver_type == "chrome": - options = webdriver.ChromeOptions() - options.add_argument("--no-sandbox") # Helps when running on Github Actions - options.add_argument("--disable-dev-shm-usage") # Helps when running on Github Actions - driver = webdriver.Chrome(options=options) - elif driver_type == "firefox-headless": - options = webdriver.FirefoxOptions() - options.add_argument("--headless") - driver = webdriver.Firefox(options=options) - elif driver_type == "firefox": - driver = webdriver.Firefox() + driver: webdriver.Chrome | webdriver.Firefox + if "chrome" in driver_type: + chrome_options: webdriver.ChromeOptions = webdriver.ChromeOptions() + chrome_options.add_argument("--no-sandbox") # Helps when running on Github Actions + chrome_options.add_argument("--disable-dev-shm-usage") # Helps when running on Github Actions + if driver_type == "chrome-headless": + chrome_options.add_argument("--headless=new") + driver = webdriver.Chrome(options=chrome_options) + elif "firefox" in driver_type: + firefox_options: webdriver.FirefoxOptions = webdriver.FirefoxOptions() + firefox_options.set_preference("browser.cache.disk.enable", False) + firefox_options.set_preference("browser.cache.memory.enable", False) + firefox_options.set_preference("browser.cache.offline.enable", False) + firefox_options.set_preference("network.http.use-cache", False) + if driver_type == "firefox-headless": + firefox_options.add_argument("--headless") + driver = webdriver.Firefox(options=firefox_options) else: msg = f"Unknown driver type: {driver_type}" raise ValueError(msg) diff --git a/tests/test_cookies.py b/tests/test_cookies.py index cea0c49..395d8d6 100644 --- a/tests/test_cookies.py +++ b/tests/test_cookies.py @@ -10,6 +10,7 @@ {"name": "user_token", "value": "xyz789", "domain": "example.com", "path": "/"}, ], ids=["session_id", "user_token"], + scope="module", ) def cookie_data(request) -> dict[str, str]: # noqa: ANN001 return request.param @@ -17,8 +18,10 @@ def cookie_data(request) -> dict[str, str]: # noqa: ANN001 def test_transfer_driver_cookies_to_session(session: requestium.Session, cookie_data: dict[str, str]) -> None: session.driver.get(f"https://{cookie_data['domain']}") + session.driver.delete_all_cookies() session.driver.add_cookie(cookie_data) + session.cookies.clear() assert session.cookies.keys() == [] session.transfer_driver_cookies_to_session() assert session.cookies.keys() == [cookie_data["name"]] @@ -26,8 +29,10 @@ def test_transfer_driver_cookies_to_session(session: requestium.Session, cookie_ def test_transfer_session_cookies_to_driver(session: requestium.Session, cookie_data: dict[str, str]) -> None: session.get(f"http://{cookie_data['domain']}") + session.cookies.clear() session.cookies.set(name=cookie_data["name"], value=cookie_data["value"], domain=cookie_data["domain"], path=cookie_data["path"]) + session.driver.delete_all_cookies() assert session.driver.get_cookies() == [] session.transfer_session_cookies_to_driver() driver_cookies = session.driver.get_cookies() @@ -40,9 +45,11 @@ def test_transfer_session_cookies_to_driver(session: requestium.Session, cookie_ def test_transfer_session_cookies_to_driver_domain_filter(session: requestium.Session, cookie_data: dict[str, str]) -> None: session.get(f"http://{cookie_data['domain']}") + session.cookies.clear() session.cookies.set(name="junk_cookie", value="sfkjn782", domain="google.com", path=cookie_data["path"]) session.cookies.set(name=cookie_data["name"], value=cookie_data["value"], domain=cookie_data["domain"], path=cookie_data["path"]) + session.driver.delete_all_cookies() assert session.driver.get_cookies() == [] session.transfer_session_cookies_to_driver(domain=cookie_data["domain"]) driver_cookies = session.driver.get_cookies() @@ -54,6 +61,9 @@ def test_transfer_session_cookies_to_driver_domain_filter(session: requestium.Se def test_transfer_session_cookies_to_driver_no_domain_error(session: requestium.Session) -> None: + session.cookies.clear() + session.driver.delete_all_cookies() + session._last_requests_url = None with ( pytest.raises( InvalidCookieDomainException, From 63893ae6606ae8fb1e0252399e5f10caf177f670 Mon Sep 17 00:00:00 2001 From: Ramona T Date: Tue, 2 Dec 2025 18:49:04 -0500 Subject: [PATCH 22/31] more readable session fixture --- tests/conftest.py | 54 +++++++++++++++++++++++++---------------------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 6f4e9ee..5574c63 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -31,42 +31,46 @@ def example_html() -> str: """ +def _create_chrome_driver(headless: bool) -> webdriver.Chrome: + options = webdriver.ChromeOptions() + options.add_argument("--no-sandbox") + options.add_argument("--disable-dev-shm-usage") + if headless: + options.add_argument("--headless=new") + return webdriver.Chrome(options=options) + + +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) + if headless: + options.add_argument("--headless") + return webdriver.Firefox(options=options) + + @pytest.fixture( - params=[ - "chrome-headless", - "chrome", - "firefox-headless", - "firefox", - ], + params=["chrome-headless", "chrome", "firefox-headless", "firefox"], scope="module", ) -def session(request) -> Generator[requestium.Session]: # noqa: ANN001 +def session(request) -> Generator[requestium.Session, None, None]: driver_type = request.param + browser, _, mode = driver_type.partition("-") + headless = mode == "headless" driver: webdriver.Chrome | webdriver.Firefox - if "chrome" in driver_type: - chrome_options: webdriver.ChromeOptions = webdriver.ChromeOptions() - chrome_options.add_argument("--no-sandbox") # Helps when running on Github Actions - chrome_options.add_argument("--disable-dev-shm-usage") # Helps when running on Github Actions - if driver_type == "chrome-headless": - chrome_options.add_argument("--headless=new") - driver = webdriver.Chrome(options=chrome_options) - elif "firefox" in driver_type: - firefox_options: webdriver.FirefoxOptions = webdriver.FirefoxOptions() - firefox_options.set_preference("browser.cache.disk.enable", False) - firefox_options.set_preference("browser.cache.memory.enable", False) - firefox_options.set_preference("browser.cache.offline.enable", False) - firefox_options.set_preference("network.http.use-cache", False) - if driver_type == "firefox-headless": - firefox_options.add_argument("--headless") - driver = webdriver.Firefox(options=firefox_options) + if browser == "chrome": + driver = _create_chrome_driver(headless) + elif browser == "firefox": + driver = _create_firefox_driver(headless) else: - msg = f"Unknown driver type: {driver_type}" + msg = f"Unknown driver type: {browser}" raise ValueError(msg) session = requestium.Session(driver=cast("DriverMixin", driver)) yield session - # Close all windows and end the session with contextlib.suppress(WebDriverException, OSError): driver.quit() From fcfc2aa33be945159f6f17bbe9c796175afd4f02 Mon Sep 17 00:00:00 2001 From: Ramona T Date: Tue, 2 Dec 2025 19:09:12 -0500 Subject: [PATCH 23/31] add missing type to session fixture --- tests/conftest.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/conftest.py b/tests/conftest.py index 5574c63..e3fedb4 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -3,6 +3,7 @@ from typing import TYPE_CHECKING, cast import pytest +from _pytest.fixtures import FixtureRequest from selenium import webdriver from selenium.common import WebDriverException @@ -55,7 +56,7 @@ def _create_firefox_driver(headless: bool) -> webdriver.Firefox: params=["chrome-headless", "chrome", "firefox-headless", "firefox"], scope="module", ) -def session(request) -> Generator[requestium.Session, None, None]: +def session(request: FixtureRequest) -> Generator[requestium.Session, None, None]: driver_type = request.param browser, _, mode = driver_type.partition("-") headless = mode == "headless" From c273d972ba80fc044a9b558a3a6a9c8130dbf183 Mon Sep 17 00:00:00 2001 From: Ramona T Date: Tue, 2 Dec 2025 19:14:28 -0500 Subject: [PATCH 24/31] reset user agent state in user agent tests --- tests/test_user_agent.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/tests/test_user_agent.py b/tests/test_user_agent.py index 2dd2eab..f1c6761 100644 --- a/tests/test_user_agent.py +++ b/tests/test_user_agent.py @@ -1,8 +1,28 @@ +from collections.abc import Generator + +import pytest + import requestium.requestium -def test_copy_user_agent_from_driver(session: requestium.Session, example_html: str) -> None: +@pytest.fixture +def reset_session_headers(session: requestium.Session) -> Generator[requestium.Session, None, None]: + """Reset session headers before each test.""" + # Store original headers - convert to dict to copy + original_headers = dict(session.headers) + session.headers.clear() + session.headers.update(original_headers) # Restore to clean state at start + + yield session + + # Restore original headers after test + session.headers.clear() + session.headers.update(original_headers) + + +def test_copy_user_agent_from_driver(reset_session_headers: requestium.Session, example_html: str) -> None: """Ensure that requests user-agent header has been changed after calling session.copy_user_agent_from_driver().""" + session = reset_session_headers pre_copy_requests_useragent = session.headers["user-agent"] assert pre_copy_requests_useragent assert pre_copy_requests_useragent != "" From af40acc67c716f82fda6c0a85f3b97c4915f766e Mon Sep 17 00:00:00 2001 From: Ramona T Date: Tue, 2 Dec 2025 19:14:40 -0500 Subject: [PATCH 25/31] reset cookie state in cookie test fixture --- tests/test_cookies.py | 84 +++++++++++++++++++++++-------------------- 1 file changed, 46 insertions(+), 38 deletions(-) diff --git a/tests/test_cookies.py b/tests/test_cookies.py index 395d8d6..ae23bc9 100644 --- a/tests/test_cookies.py +++ b/tests/test_cookies.py @@ -1,4 +1,5 @@ import pytest +from _pytest.fixtures import FixtureRequest from selenium.common import InvalidCookieDomainException import requestium.requestium @@ -12,62 +13,69 @@ ids=["session_id", "user_token"], scope="module", ) -def cookie_data(request) -> dict[str, str]: # noqa: ANN001 +def cookie_data(request: FixtureRequest) -> dict[str, str]: return request.param -def test_transfer_driver_cookies_to_session(session: requestium.Session, cookie_data: dict[str, str]) -> None: - session.driver.get(f"https://{cookie_data['domain']}") +@pytest.fixture +def clean_session(session: requestium.Session) -> requestium.Session: + """Fixture that ensures cookies are cleared before each test.""" + session.cookies.clear() session.driver.delete_all_cookies() - session.driver.add_cookie(cookie_data) + return session - session.cookies.clear() - assert session.cookies.keys() == [] - session.transfer_driver_cookies_to_session() - assert session.cookies.keys() == [cookie_data["name"]] +def test_transfer_driver_cookies_to_session(clean_session: requestium.Session, cookie_data: dict[str, str]) -> None: + clean_session.driver.get(f"https://{cookie_data['domain']}") + clean_session.driver.add_cookie(cookie_data) -def test_transfer_session_cookies_to_driver(session: requestium.Session, cookie_data: dict[str, str]) -> None: - session.get(f"http://{cookie_data['domain']}") - session.cookies.clear() - session.cookies.set(name=cookie_data["name"], value=cookie_data["value"], domain=cookie_data["domain"], path=cookie_data["path"]) + assert not clean_session.cookies.keys() + clean_session.transfer_driver_cookies_to_session() + assert clean_session.cookies.keys() == [cookie_data["name"]] - session.driver.delete_all_cookies() - assert session.driver.get_cookies() == [] - session.transfer_session_cookies_to_driver() - driver_cookies = session.driver.get_cookies() + +def test_transfer_session_cookies_to_driver(clean_session: requestium.Session, cookie_data: dict[str, str]) -> None: + clean_session.get(f"http://{cookie_data['domain']}") + clean_session.cookies.set(name=cookie_data["name"], value=cookie_data["value"], domain=cookie_data["domain"], path=cookie_data["path"]) + + assert not clean_session.driver.get_cookies() + clean_session.transfer_session_cookies_to_driver() + + driver_cookies = clean_session.driver.get_cookies() assert len(driver_cookies) == 1 - assert driver_cookies[0]["name"] == cookie_data["name"] - assert driver_cookies[0]["value"] == cookie_data["value"] - assert driver_cookies[0]["domain"] in [cookie_data["domain"], f".{cookie_data['domain']}"] - assert driver_cookies[0]["path"] == cookie_data["path"] + cookie = driver_cookies[0] + assert cookie["name"] == cookie_data["name"] + assert cookie["value"] == cookie_data["value"] + assert cookie["domain"] in {cookie_data["domain"], f".{cookie_data['domain']}"} + assert cookie["path"] == cookie_data["path"] -def test_transfer_session_cookies_to_driver_domain_filter(session: requestium.Session, cookie_data: dict[str, str]) -> None: - session.get(f"http://{cookie_data['domain']}") - session.cookies.clear() - session.cookies.set(name="junk_cookie", value="sfkjn782", domain="google.com", path=cookie_data["path"]) - session.cookies.set(name=cookie_data["name"], value=cookie_data["value"], domain=cookie_data["domain"], path=cookie_data["path"]) - session.driver.delete_all_cookies() - assert session.driver.get_cookies() == [] - session.transfer_session_cookies_to_driver(domain=cookie_data["domain"]) - driver_cookies = session.driver.get_cookies() +def test_transfer_session_cookies_to_driver_domain_filter(clean_session: requestium.Session, cookie_data: dict[str, str]) -> None: + clean_session.get(f"http://{cookie_data['domain']}") + clean_session.cookies.set(name="junk_cookie", value="sfkjn782", domain="google.com", path=cookie_data["path"]) + clean_session.cookies.set(name=cookie_data["name"], value=cookie_data["value"], domain=cookie_data["domain"], path=cookie_data["path"]) + + assert not clean_session.driver.get_cookies() + clean_session.transfer_session_cookies_to_driver(domain=cookie_data["domain"]) + + driver_cookies = clean_session.driver.get_cookies() assert len(driver_cookies) == 1 - assert driver_cookies[0]["name"] == cookie_data["name"] - assert driver_cookies[0]["value"] == cookie_data["value"] - assert driver_cookies[0]["domain"] in [cookie_data["domain"], f".{cookie_data['domain']}"] - assert driver_cookies[0]["path"] == cookie_data["path"] + + cookie = driver_cookies[0] + assert cookie["name"] == cookie_data["name"] + assert cookie["value"] == cookie_data["value"] + assert cookie["domain"] in {cookie_data["domain"], f".{cookie_data['domain']}"} + assert cookie["path"] == cookie_data["path"] def test_transfer_session_cookies_to_driver_no_domain_error(session: requestium.Session) -> None: session.cookies.clear() session.driver.delete_all_cookies() session._last_requests_url = None - with ( - pytest.raises( - InvalidCookieDomainException, - match="Trying to transfer cookies to selenium without specifying a domain and without having visited any page in the current session", - ), + + with pytest.raises( + InvalidCookieDomainException, + match="Trying to transfer cookies to selenium without specifying a domain and without having visited any page in the current session", ): session.transfer_session_cookies_to_driver() From 43c2169820091612cdec073769e68920f0bcbe44 Mon Sep 17 00:00:00 2001 From: Ramona T Date: Tue, 2 Dec 2025 21:02:13 -0500 Subject: [PATCH 26/31] add test_ensure_click --- tests/conftest.py | 1 + tests/test_mixin.py | 8 ++++++++ 2 files changed, 9 insertions(+) diff --git a/tests/conftest.py b/tests/conftest.py index e3fedb4..4eeee9c 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -25,6 +25,7 @@ def example_html() -> str:

Test Header 2

Test Header 3

Test Paragraph 1

+

Test Link 1

Test Link 2

diff --git a/tests/test_mixin.py b/tests/test_mixin.py index ac82b9c..7d6c695 100644 --- a/tests/test_mixin.py +++ b/tests/test_mixin.py @@ -110,3 +110,11 @@ def test_simple_page_load(session: requestium.Session, example_html: str) -> Non session.driver.ensure_element_by_tag_name("h1") # wait for page load title = session.driver.title assert title == "The Internet" + + +def test_ensure_click(session: requestium.Session, example_html: str) -> None: + session.driver.get(f"data:text/html,{example_html}") + + element = session.driver.ensure_element_by_tag_name("button") + assert isinstance(element, WebElement) + requestium.requestium._ensure_click(element) From 66528ab171b7fc261e4560e83bb3dada005c8488 Mon Sep 17 00:00:00 2001 From: Ramona T Date: Tue, 2 Dec 2025 21:02:34 -0500 Subject: [PATCH 27/31] add test_ensure_add_cookie and test_ensure_add_cookie_domain_override --- tests/test_cookies.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/test_cookies.py b/tests/test_cookies.py index ae23bc9..2f38eec 100644 --- a/tests/test_cookies.py +++ b/tests/test_cookies.py @@ -25,6 +25,38 @@ def clean_session(session: requestium.Session) -> requestium.Session: return session +def test_ensure_add_cookie(clean_session: requestium.Session, cookie_data: dict[str, str]) -> None: + clean_session.driver.get("https://google.com") + clean_session.driver.delete_all_cookies() + clean_session.driver.ensure_add_cookie(cookie_data) + + driver_cookies = clean_session.driver.get_cookies() + assert len(driver_cookies) == 1 + + cookie = driver_cookies[0] + assert cookie["name"] == cookie_data["name"] + assert cookie["value"] == cookie_data["value"] + assert cookie["domain"] in {cookie_data["domain"], f".{cookie_data['domain']}"} + assert cookie["path"] == cookie_data["path"] + + +def test_ensure_add_cookie_domain_override(clean_session: requestium.Session, cookie_data: dict[str, str]) -> None: + override_domain = "example.net" + + clean_session.driver.get("https://google.com") + clean_session.driver.delete_all_cookies() + clean_session.driver.ensure_add_cookie(cookie_data, override_domain=override_domain) + + driver_cookies = clean_session.driver.get_cookies() + assert len(driver_cookies) == 1 + + cookie = driver_cookies[0] + assert cookie["name"] == cookie_data["name"] + assert cookie["value"] == cookie_data["value"] + assert cookie["domain"] in {override_domain, f".{override_domain}"} + assert cookie["path"] == cookie_data["path"] + + def test_transfer_driver_cookies_to_session(clean_session: requestium.Session, cookie_data: dict[str, str]) -> None: clean_session.driver.get(f"https://{cookie_data['domain']}") clean_session.driver.add_cookie(cookie_data) From 875e27b0a4e1c5d80bdefa005bde63c0450ba90a Mon Sep 17 00:00:00 2001 From: Ramona T Date: Tue, 2 Dec 2025 21:10:25 -0500 Subject: [PATCH 28/31] simplify cookie tests with assert_first_cookie_matches utility function --- tests/test_cookies.py | 52 ++++++++++++++----------------------------- 1 file changed, 17 insertions(+), 35 deletions(-) diff --git a/tests/test_cookies.py b/tests/test_cookies.py index 2f38eec..fc0c735 100644 --- a/tests/test_cookies.py +++ b/tests/test_cookies.py @@ -19,25 +19,29 @@ def cookie_data(request: FixtureRequest) -> dict[str, str]: @pytest.fixture def clean_session(session: requestium.Session) -> requestium.Session: - """Fixture that ensures cookies are cleared before each test.""" + """Ensure cookies are cleared before each test.""" session.cookies.clear() session.driver.delete_all_cookies() return session +def assert_first_cookie_matches(driver_cookies: list[dict], expected: dict[str, str]) -> None: + """Verify the first cookie in a list matches expected values.""" + assert len(driver_cookies) == 1 + + cookie = driver_cookies[0] + assert cookie["name"] == expected["name"] + assert cookie["value"] == expected["value"] + assert cookie["domain"] in {expected["domain"], f".{expected['domain']}"} + assert cookie["path"] == expected["path"] + + def test_ensure_add_cookie(clean_session: requestium.Session, cookie_data: dict[str, str]) -> None: clean_session.driver.get("https://google.com") clean_session.driver.delete_all_cookies() clean_session.driver.ensure_add_cookie(cookie_data) - driver_cookies = clean_session.driver.get_cookies() - assert len(driver_cookies) == 1 - - cookie = driver_cookies[0] - assert cookie["name"] == cookie_data["name"] - assert cookie["value"] == cookie_data["value"] - assert cookie["domain"] in {cookie_data["domain"], f".{cookie_data['domain']}"} - assert cookie["path"] == cookie_data["path"] + assert_first_cookie_matches(clean_session.driver.get_cookies(), cookie_data) def test_ensure_add_cookie_domain_override(clean_session: requestium.Session, cookie_data: dict[str, str]) -> None: @@ -47,14 +51,8 @@ def test_ensure_add_cookie_domain_override(clean_session: requestium.Session, co clean_session.driver.delete_all_cookies() clean_session.driver.ensure_add_cookie(cookie_data, override_domain=override_domain) - driver_cookies = clean_session.driver.get_cookies() - assert len(driver_cookies) == 1 - - cookie = driver_cookies[0] - assert cookie["name"] == cookie_data["name"] - assert cookie["value"] == cookie_data["value"] - assert cookie["domain"] in {override_domain, f".{override_domain}"} - assert cookie["path"] == cookie_data["path"] + expected = {**cookie_data, "domain": override_domain} + assert_first_cookie_matches(clean_session.driver.get_cookies(), expected) def test_transfer_driver_cookies_to_session(clean_session: requestium.Session, cookie_data: dict[str, str]) -> None: @@ -72,15 +70,7 @@ def test_transfer_session_cookies_to_driver(clean_session: requestium.Session, c assert not clean_session.driver.get_cookies() clean_session.transfer_session_cookies_to_driver() - - driver_cookies = clean_session.driver.get_cookies() - assert len(driver_cookies) == 1 - - cookie = driver_cookies[0] - assert cookie["name"] == cookie_data["name"] - assert cookie["value"] == cookie_data["value"] - assert cookie["domain"] in {cookie_data["domain"], f".{cookie_data['domain']}"} - assert cookie["path"] == cookie_data["path"] + assert_first_cookie_matches(clean_session.driver.get_cookies(), cookie_data) def test_transfer_session_cookies_to_driver_domain_filter(clean_session: requestium.Session, cookie_data: dict[str, str]) -> None: @@ -90,15 +80,7 @@ def test_transfer_session_cookies_to_driver_domain_filter(clean_session: request assert not clean_session.driver.get_cookies() clean_session.transfer_session_cookies_to_driver(domain=cookie_data["domain"]) - - driver_cookies = clean_session.driver.get_cookies() - assert len(driver_cookies) == 1 - - cookie = driver_cookies[0] - assert cookie["name"] == cookie_data["name"] - assert cookie["value"] == cookie_data["value"] - assert cookie["domain"] in {cookie_data["domain"], f".{cookie_data['domain']}"} - assert cookie["path"] == cookie_data["path"] + assert_first_cookie_matches(clean_session.driver.get_cookies(), cookie_data) def test_transfer_session_cookies_to_driver_no_domain_error(session: requestium.Session) -> None: From 629eebb0e251f91aedef05a82765fd4255c5714e Mon Sep 17 00:00:00 2001 From: Ramona T Date: Wed, 3 Dec 2025 09:11:35 -0500 Subject: [PATCH 29/31] add assert function to check webelement text --- tests/test_mixin.py | 36 ++++++++++++++++-------------------- 1 file changed, 16 insertions(+), 20 deletions(-) diff --git a/tests/test_mixin.py b/tests/test_mixin.py index 7d6c695..a6e2918 100644 --- a/tests/test_mixin.py +++ b/tests/test_mixin.py @@ -5,68 +5,66 @@ import requestium.requestium +def assert_webelement_text_exact_match(element: WebElement, expected: str) -> None: + """Verify the provided element is a WebElement with matching text.""" + assert isinstance(element, WebElement) + assert element.text == expected + + def test_ensure_element_by_id(session: requestium.Session, example_html: str) -> None: session.driver.get(f"data:text/html,{example_html}") element = session.driver.ensure_element_by_id("test-header") - assert isinstance(element, WebElement) - assert element.text == "Test Header 2" + assert_webelement_text_exact_match(element, "Test Header 2") def test_ensure_element_by_name(session: requestium.Session, example_html: str) -> None: session.driver.get(f"data:text/html,{example_html}") element = session.driver.ensure_element_by_name("link-paragraph") - assert isinstance(element, WebElement) - assert element.text == "Test Link 1" + assert_webelement_text_exact_match(element, "Test Link 1") def test_ensure_element_by_xpath(session: requestium.Session, example_html: str) -> None: session.driver.get(f"data:text/html,{example_html}") element = session.driver.ensure_element_by_xpath("//a[text()='Test Link 2']") - assert isinstance(element, WebElement) - assert element.text == "Test Link 2" + assert_webelement_text_exact_match(element, "Test Link 2") def test_ensure_element_by_link_text(session: requestium.Session, example_html: str) -> None: session.driver.get(f"data:text/html,{example_html}") element = session.driver.ensure_element_by_link_text("Test Link 1") - assert isinstance(element, WebElement) - assert element.text == "Test Link 1" + assert_webelement_text_exact_match(element, "Test Link 1") def test_ensure_element_by_partial_link_text(session: requestium.Session, example_html: str) -> None: session.driver.get(f"data:text/html,{example_html}") element = session.driver.ensure_element_by_partial_link_text("Link 2") - assert isinstance(element, WebElement) - assert element.text == "Test Link 2" + assert_webelement_text_exact_match(element, "Test Link 2") def test_ensure_element_by_tag_name(session: requestium.Session, example_html: str) -> None: session.driver.get(f"data:text/html,{example_html}") element = session.driver.ensure_element_by_tag_name("h1") - assert isinstance(element, WebElement) - assert element.text == "Test Header 1" + assert_webelement_text_exact_match(element, "Test Header 1") def test_ensure_element_by_class_name(session: requestium.Session, example_html: str) -> None: session.driver.get(f"data:text/html,{example_html}") element = session.driver.ensure_element_by_class_name("body-text") - assert isinstance(element, WebElement) - assert element.text == "Test Paragraph 1" + assert_webelement_text_exact_match(element, "Test Paragraph 1") def test_ensure_element_by_css_selector(session: requestium.Session, example_html: str) -> None: session.driver.get(f"data:text/html,{example_html}") element = session.driver.ensure_element_by_css_selector(".body-text") - assert isinstance(element, WebElement) - assert element.text == "Test Paragraph 1" + assert_webelement_text_exact_match(element, "Test Paragraph 1") @pytest.mark.parametrize( @@ -88,12 +86,10 @@ def test_ensure_element(session: requestium.Session, example_html: str, locator: session.driver.get(f"data:text/html,{example_html}") element = session.driver.ensure_element(locator=locator, selector=selector) - assert isinstance(element, WebElement) - assert element.text == result + assert_webelement_text_exact_match(element, result) element = session.driver.ensure_element(locator, selector) - assert isinstance(element, WebElement) - assert element.text == result + assert_webelement_text_exact_match(element, result) def test_deprecation_warning_for_ensure_element_locators_with_underscores(session: requestium.Session, example_html: str) -> None: From 7a7063a90c04f0495eec67d0d3e3176c216ee475 Mon Sep 17 00:00:00 2001 From: Ramona T Date: Fri, 5 Dec 2025 22:25:35 -0500 Subject: [PATCH 30/31] fix assert_webelement_text_exact_match type hint --- tests/test_mixin.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_mixin.py b/tests/test_mixin.py index a6e2918..b23f262 100644 --- a/tests/test_mixin.py +++ b/tests/test_mixin.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import pytest from selenium.webdriver.common.by import By, ByType from selenium.webdriver.remote.webelement import WebElement @@ -5,7 +7,7 @@ import requestium.requestium -def assert_webelement_text_exact_match(element: WebElement, expected: str) -> None: +def assert_webelement_text_exact_match(element: WebElement | None, expected: str) -> None: """Verify the provided element is a WebElement with matching text.""" assert isinstance(element, WebElement) assert element.text == expected From f8027beecbc3e019e43c088f755dd902e57b47fe Mon Sep 17 00:00:00 2001 From: Ramona T Date: Sat, 6 Dec 2025 12:43:52 -0500 Subject: [PATCH 31/31] wait for webdriver to open window before use in tests --- tests/conftest.py | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/tests/conftest.py b/tests/conftest.py index 4eeee9c..b8c455a 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -6,6 +6,8 @@ from _pytest.fixtures import FixtureRequest from selenium import webdriver from selenium.common import WebDriverException +from selenium.webdriver.support.wait import WebDriverWait +from selenium.webdriver.support import expected_conditions as EC import requestium @@ -39,7 +41,9 @@ def _create_chrome_driver(headless: bool) -> webdriver.Chrome: options.add_argument("--disable-dev-shm-usage") if headless: options.add_argument("--headless=new") - return webdriver.Chrome(options=options) + driver = webdriver.Chrome(options=options) + WebDriverWait(driver, 5).until(EC.number_of_windows_to_be(1)) + return driver def _create_firefox_driver(headless: bool) -> webdriver.Firefox: @@ -50,7 +54,9 @@ def _create_firefox_driver(headless: bool) -> webdriver.Firefox: options.set_preference("network.http.use-cache", False) if headless: options.add_argument("--headless") - return webdriver.Firefox(options=options) + driver = webdriver.Firefox(options=options) + WebDriverWait(driver, 5).until(EC.number_of_windows_to_be(1)) + return driver @pytest.fixture( @@ -76,3 +82,19 @@ def session(request: FixtureRequest) -> Generator[requestium.Session, None, None with contextlib.suppress(WebDriverException, OSError): driver.quit() + + +@pytest.fixture(autouse=True) +def ensure_valid_session(session: requestium.Session) -> None: + """Skip test if browser context is discarded.""" + 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")