Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/python.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,9 @@ jobs:
- run: pytest -r A
env:
SE_AUTO_UPDATE: true # opt-in to selenium 5 behavior
# Runner images put a chromedriver on PATH that can lag the installed Chrome ("This version of
# ChromeDriver only supports Chrome version N"); let Selenium Manager resolve a matching driver instead.
SE_SKIP_DRIVER_IN_PATH: true
COVERAGE_FILE: .coverage.${{ matrix.os }}.${{ matrix.python-version }}.${{ matrix.resolution }}

- uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f
Expand Down
18 changes: 15 additions & 3 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from collections.abc import Generator, Sequence
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
from typing import TYPE_CHECKING, cast
from typing import TYPE_CHECKING, Any, cast
from urllib.parse import parse_qs, urlsplit

import pytest
Expand Down Expand Up @@ -46,6 +46,11 @@
DISABLE_HTTPS_UPGRADES = "--disable-features=HttpsUpgrades,HttpsFirstBalancedModeAutoEnable"


# Chrome for Testing with its sandbox enabled silently dropped navigations on GitHub's Windows runners (driver.get returned but
# the page stayed on data:, in 9 of 30 launches). The library must not disable the sandbox itself, so tests pass these.
CHROME_CI_ARGUMENTS = ("--no-sandbox", "--disable-dev-shm-usage")


class _Handler(BaseHTTPRequestHandler):
"""
Serves pages and small JSON endpoints, and doubles as an HTTP forward proxy.
Expand Down Expand Up @@ -206,13 +211,20 @@ def chrome_options(server: LocalServer, *, headless: bool, arguments: Sequence[s
options.add_argument(DISABLE_HTTPS_UPGRADES)
for argument in arguments:
options.add_argument(argument)
options.add_argument("--no-sandbox")
options.add_argument("--disable-dev-shm-usage")
for argument in CHROME_CI_ARGUMENTS:
options.add_argument(argument)
if headless:
options.add_argument("--headless=new")
return options


def own_chrome_session(*, arguments: Sequence[str] = (), **kwargs: Any) -> requestium.Session: # noqa: ANN401
"""Create a Session that starts its own Chrome with the CI-safe arguments, followed by any in webdriver_options and then arguments."""
webdriver_options = dict(kwargs.pop("webdriver_options", None) or {})
webdriver_options["arguments"] = [*CHROME_CI_ARGUMENTS, *webdriver_options.get("arguments", ()), *arguments]
return requestium.Session(webdriver_options=webdriver_options, **kwargs)


def _create_chrome_driver(server: LocalServer, *, headless: bool) -> webdriver.Chrome:
try:
driver = webdriver.Chrome(options=chrome_options(server, headless=headless))
Expand Down
6 changes: 2 additions & 4 deletions tests/test_add_cookie.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
import requestium.requestium
from requestium.requestium_mixin import DriverMixin

from .conftest import LocalServer, assert_first_cookie_matches, chrome_options, cookies_sent_by_driver
from .conftest import LocalServer, assert_first_cookie_matches, chrome_options, cookies_sent_by_driver, own_chrome_session


def test_ensure_add_cookie(clean_session: requestium.Session, cookie_data: dict[str, str]) -> None:
Expand Down Expand Up @@ -63,12 +63,10 @@ def chrome(server: LocalServer) -> Generator[DriverMixin, None, None]:
def secure_chrome(https_server: LocalServer) -> Generator[DriverMixin, None, None]:
"""Start Chrome with *.secure.com mapped to the TLS listener (a forward proxy can't tunnel to it) and its certificate trusted."""
arguments = [
"--no-sandbox",
"--disable-dev-shm-usage",
f"--host-resolver-rules=MAP *.secure.com 127.0.0.1:{https_server.port}",
"--ignore-certificate-errors",
]
session = requestium.Session(headless=True, webdriver_options={"arguments": arguments})
session = own_chrome_session(headless=True, arguments=arguments)
yield session.driver
session.close()

Expand Down
4 changes: 2 additions & 2 deletions tests/test_cookies.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@

import requestium.requestium

from .conftest import LocalServer, assert_first_cookie_matches, cookies_sent_by_driver
from .conftest import LocalServer, assert_first_cookie_matches, cookies_sent_by_driver, own_chrome_session

# Cookie domains in these tests are fake public hostnames such as example.com. The browsers and requests are pointed at
# the local server as an HTTP proxy (see LocalServer), so nothing leaves the machine, yet cookie and domain rules are
Expand Down Expand Up @@ -107,7 +107,7 @@ def test_transfer_session_cookies_to_driver_no_domain_error(clean_session: reque
def localhost_session(server: LocalServer) -> Generator[requestium.Session, None, None]:
"""Start a Chrome session where http://localhost:<port> reaches the test server and counts as a secure context for Secure cookies."""
arguments = ["--host-resolver-rules=MAP localhost 127.0.0.1", f"--unsafely-treat-insecure-origin-as-secure=http://localhost:{server.port}"]
session = requestium.Session(headless=True, webdriver_options={"arguments": ["--no-sandbox", "--disable-dev-shm-usage", *arguments]})
session = own_chrome_session(headless=True, arguments=arguments)
yield session
session.close()

Expand Down
5 changes: 3 additions & 2 deletions tests/test_ensure_element.py
Original file line number Diff line number Diff line change
Expand Up @@ -144,11 +144,12 @@ def test_session_default_timeout_is_used(session: requestium.Session) -> None:
short_session = requestium.Session(driver=session.driver, default_timeout=0.6)
assert short_session.driver.default_timeout == 0.6

run_later(short_session, "document.body.appendChild(Object.assign(document.createElement('div'), {id: 'slow'}));", delay_ms=1500)
run_later(short_session, "document.body.appendChild(Object.assign(document.createElement('div'), {id: 'slow'}));", delay_ms=3000)
start = time.monotonic()
with pytest.raises(TimeoutException):
short_session.driver.ensure_element(By.ID, "slow")
assert 0.6 <= time.monotonic() - start < 1.4
# Well under the 3s it takes the element to appear, with headroom for slow CI runners
assert 0.6 <= time.monotonic() - start < 2.5

# An explicit timeout overrides the default
assert short_session.driver.ensure_element(By.ID, "slow", timeout=5) is not None
Expand Down
8 changes: 4 additions & 4 deletions tests/test_proxy.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,13 @@

import requestium

from .conftest import DISABLE_HTTPS_UPGRADES, LocalServer
from .conftest import DISABLE_HTTPS_UPGRADES, LocalServer, own_chrome_session

PROXIED_HOST = "requestium-proxy-test.invalid"


def test_proxy_flag_reaches_chrome(server: LocalServer) -> None:
session = requestium.Session(headless=True, webdriver_options={"arguments": [DISABLE_HTTPS_UPGRADES]})
session = own_chrome_session(headless=True, arguments=[DISABLE_HTTPS_UPGRADES])
try:
# Set after construction but before the first driver access
session.proxies = {"http": server.url}
Expand All @@ -35,7 +35,7 @@ def test_proxy_flag_format() -> None:


def test_credentialed_proxy_warns_and_is_skipped() -> None:
session = requestium.Session(headless=True)
session = own_chrome_session(headless=True)
session.proxies = {"http": "http://user:secret@proxy.example:8080"}
try:
with pytest.warns(UserWarning, match="credentials") as record:
Expand All @@ -46,7 +46,7 @@ def test_credentialed_proxy_warns_and_is_skipped() -> None:


def test_no_warnings_without_credentials() -> None:
session = requestium.Session(headless=True)
session = own_chrome_session(headless=True)
session.proxies = {"http": "http://proxy.example:8080"}
with warnings.catch_warnings():
warnings.simplefilter("error")
Expand Down
14 changes: 7 additions & 7 deletions tests/test_session.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from requestium.requestium import DriverMixin, RequestiumResponse
from requestium.requestium_session import _mixin_class

from .conftest import LocalServer, chrome_options, validate_session
from .conftest import LocalServer, chrome_options, own_chrome_session, validate_session

SessionFactory = Callable[..., requestium.Session]

Expand All @@ -24,7 +24,7 @@ def make_session() -> Generator[SessionFactory, None, None]:
sessions: list[requestium.Session] = []

def _make_session(**kwargs: Any) -> requestium.Session: # noqa: ANN401
session = requestium.Session(**kwargs)
session = own_chrome_session(**kwargs)
sessions.append(session)
return session

Expand Down Expand Up @@ -90,7 +90,7 @@ def driver_is_dead(driver: WebDriver) -> bool:


def test_close_quits_lazily_started_chrome() -> None:
session = requestium.Session(headless=True)
session = own_chrome_session(headless=True)
driver = session.driver
assert not driver_is_dead(driver)
session.close()
Expand All @@ -99,7 +99,7 @@ def test_close_quits_lazily_started_chrome() -> None:


def test_close_then_driver_access_starts_fresh_chrome() -> None:
session = requestium.Session(headless=True)
session = own_chrome_session(headless=True)
first = session.driver
session.close()
second = session.driver
Expand All @@ -111,20 +111,20 @@ def test_close_then_driver_access_starts_fresh_chrome() -> None:


def test_context_manager_quits_driver() -> None:
with requestium.Session(headless=True) as session:
with own_chrome_session(headless=True) as session:
driver = session.driver
assert not driver_is_dead(driver)
assert driver_is_dead(driver)


def test_close_without_driver_does_not_start_one() -> None:
session = requestium.Session(headless=True)
session = own_chrome_session(headless=True)
session.close()
assert session._driver is None


def test_close_twice_is_harmless() -> None:
session = requestium.Session(headless=True)
session = own_chrome_session(headless=True)
driver = session.driver
driver.quit()
session.close()
Expand Down
8 changes: 4 additions & 4 deletions tests/test_user_agent.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

import requestium.requestium

from .conftest import LocalServer
from .conftest import LocalServer, own_chrome_session


@pytest.fixture
Expand Down Expand Up @@ -55,7 +55,7 @@ def test_transfer_driver_cookies_to_session_can_skip_user_agent(reset_session_he


def test_custom_user_agent_reaches_chrome() -> None:
session = requestium.Session(headless=True)
session = own_chrome_session(headless=True)
session.headers["User-Agent"] = "RequestiumTest/1.0"
try:
assert session.driver.execute_script("return navigator.userAgent;") == "RequestiumTest/1.0"
Expand All @@ -64,7 +64,7 @@ def test_custom_user_agent_reaches_chrome() -> None:


def test_default_user_agent_is_not_forced() -> None:
session = requestium.Session(headless=True)
session = own_chrome_session(headless=True)
try:
user_agent = session.driver.execute_script("return navigator.userAgent;")
assert "python-requests" not in user_agent
Expand All @@ -73,7 +73,7 @@ def test_default_user_agent_is_not_forced() -> None:


def test_user_supplied_user_agent_argument_wins() -> None:
session = requestium.Session(headless=True, webdriver_options={"arguments": ["--user-agent=FromOptions/2.0"]})
session = own_chrome_session(headless=True, arguments=["--user-agent=FromOptions/2.0"])
session.headers["User-Agent"] = "FromHeaders/1.0"
try:
assert session.driver.execute_script("return navigator.userAgent;") == "FromOptions/2.0"
Expand Down
Loading