Files
camoufox/pythonlib/tests/test_executable_path_version_warning.py
T
Jake WriterandClaude Opus 5 8cb7914328 feat(python): warn when a supplied binary predates the Playwright in use
A managed install below the version floor is upgraded by pkgman, but
executable_path deliberately bypasses that -- the caller supplied the binary,
so we neither replace it nor download another. That left one pairing nothing
checked: an old build driven by Playwright >= 1.61, which sends viewport fields
the older Juggler schema rejects. The user saw a bare

    Protocol error (Browser.setDefaultViewport)

with nothing naming the cause.

Warn rather than raise, because the pairing is not always fatal. Camoufox
defaults to no_viewport when it spoofs window dimensions (sync_api), and
Playwright then never sends Browser.setDefaultViewport -- so the default path
works fine on an old build. Measured against a real beta.29 binary on
Playwright 1.62:

    default path                     WORKS
    new_context(viewport=...)        BREAKS
    new_context(no_viewport=False)   BREAKS
    new_context(viewport=..., is_mobile=False)  BREAKS

Refusing to launch would break the setups in the first row. A build with no
version.json beside it -- an unpackaged objdir build -- tells us nothing, so it
is left alone rather than nagged about.

Verified end to end: warns on the real beta.29 build under Playwright 1.62,
silent on beta.30.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-31 19:34:52 -06:00

70 lines
2.5 KiB
Python

"""A caller's own binary is never replaced -- but it should still be checked.
A managed install below the floor gets upgraded by pkgman. `executable_path`
deliberately bypasses that, which leaves one pairing nothing checks: an old
build driven by Playwright >= 1.61, whose viewport fields the older Juggler
schema rejects. Without this the user sees a bare "Protocol error
(Browser.setDefaultViewport)" and nothing naming the cause.
It warns rather than raises on purpose: camoufox defaults to no_viewport when
it spoofs window dimensions, so the default path works on an old build. Only an
explicit viewport breaks, so refusing to launch would break working setups.
"""
import json
import pytest
from camoufox import pkgman, utils
def _bundle(tmp_path, build):
"""A browser directory with version.json beside the binary, as a release has."""
d = tmp_path / f"152.0.4-{build}"
d.mkdir()
(d / "version.json").write_text(json.dumps({"version": "152.0.4", "build": build}))
return d / "camoufox-bin"
@pytest.fixture
def floor_at_beta30(monkeypatch):
monkeypatch.setattr(utils, "effective_version_min", lambda: pkgman.Version(build="beta.30"))
def test_warns_when_the_supplied_build_is_too_old(tmp_path, floor_at_beta30):
exe = _bundle(tmp_path, "beta.29")
with pytest.warns(RuntimeWarning, match=r"beta\.29.*beta\.30"):
utils.warn_if_executable_predates_playwright(exe)
def test_names_the_symptom_the_user_will_actually_see(tmp_path, floor_at_beta30):
exe = _bundle(tmp_path, "beta.29")
with pytest.warns(RuntimeWarning) as caught:
utils.warn_if_executable_predates_playwright(exe)
assert "Browser.setDefaultViewport" in str(caught[0].message)
@pytest.mark.parametrize("build", ["beta.30", "beta.31"])
def test_silent_when_the_build_is_new_enough(tmp_path, floor_at_beta30, build, recwarn):
utils.warn_if_executable_predates_playwright(_bundle(tmp_path, build))
assert not [w for w in recwarn if issubclass(w.category, RuntimeWarning)]
def test_silent_for_a_custom_build_with_no_version_json(tmp_path, floor_at_beta30, recwarn):
"""An unpackaged objdir build tells us nothing; do not nag about it."""
(tmp_path / "dist").mkdir()
utils.warn_if_executable_predates_playwright(tmp_path / "dist" / "camoufox-bin")
assert not [w for w in recwarn if issubclass(w.category, RuntimeWarning)]
def test_silent_when_no_executable_path_was_given(floor_at_beta30, recwarn):
utils.warn_if_executable_predates_playwright(None)
assert not [w for w in recwarn if issubclass(w.category, RuntimeWarning)]