Skip to content

Commit e66c69a

Browse files
authored
Merge pull request #748 from JWriter20/release/pythonlib-0.5.6b1
chore(release): mark pythonlib 0.5.6 as a pre-release (0.5.6b1)
2 parents 5d06ec1 + 8cb7914 commit e66c69a

3 files changed

Lines changed: 126 additions & 1 deletion

File tree

pythonlib/camoufox/utils.py

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,13 @@
2525
from .geolocation import geoip_allowed, get_geolocation
2626
from .ip import Proxy, public_ip, valid_ipv4, valid_ipv6
2727
from .locales import handle_locales
28+
import warnings
29+
2830
from .pkgman import (
2931
INSTALL_DIR,
3032
OS_NAME,
33+
Version,
34+
effective_version_min,
3135
ensure_browser_profile_dir,
3236
get_path,
3337
installed_verstr,
@@ -86,6 +90,57 @@ def _generate_fontconfig(fontconfig_path: str, path: Optional[Path] = None) -> s
8690
return runtime_conf
8791

8892

93+
def warn_if_executable_predates_playwright(path: Optional[Path]) -> None:
94+
"""Warn when a caller's own binary is older than their Playwright needs.
95+
96+
A managed install below the floor is simply upgraded (pkgman resolves it),
97+
but `executable_path` deliberately bypasses that -- the caller supplied the
98+
binary, so we neither replace it nor download another. That leaves the one
99+
pairing nothing checks: an old build driven by Playwright >= 1.61, which
100+
sends viewport fields the older Juggler schema rejects.
101+
102+
This warns rather than raises, because the pairing is not always fatal.
103+
Camoufox defaults to no_viewport when it spoofs window dimensions
104+
(sync_api), and Playwright then never sends Browser.setDefaultViewport --
105+
so the default path works on an old build. It breaks only when a viewport
106+
is set explicitly, and then the error is a bare "Protocol error
107+
(Browser.setDefaultViewport)" with nothing pointing at the real cause.
108+
Refusing to launch would break setups that currently work.
109+
110+
A build with no version.json beside it -- an unpackaged objdir build, say --
111+
tells us nothing, so it is left alone.
112+
"""
113+
if path is None:
114+
return
115+
try:
116+
installed = Version.from_path(Path(path).parent)
117+
except (FileNotFoundError, KeyError, ValueError):
118+
return
119+
120+
required = effective_version_min()
121+
if installed >= required:
122+
return
123+
124+
warnings.warn(
125+
f"The Camoufox build at {path} is {installed.build}, but Playwright "
126+
f"{_resolved_playwright_version_str()} needs at least {required.build}. "
127+
"Contexts created with an explicit viewport will fail with "
128+
'"Protocol error (Browser.setDefaultViewport)". Update the build, or pin '
129+
"playwright<1.61.",
130+
RuntimeWarning,
131+
stacklevel=3,
132+
)
133+
134+
135+
def _resolved_playwright_version_str() -> str:
136+
from importlib.metadata import version
137+
138+
try:
139+
return version('playwright')
140+
except Exception:
141+
return 'the installed version'
142+
143+
89144
def get_env_vars(
90145
config_map: Dict[str, str],
91146
user_agent_os: str,
@@ -951,6 +1006,7 @@ def launch_options(
9511006
pprint(config)
9521007

9531008
# Validate the config
1009+
warn_if_executable_predates_playwright(executable_path)
9541010
validate_config(config, path=executable_path)
9551011

9561012
# Prepare environment variables to pass to Camoufox

pythonlib/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ build-backend = "poetry.core.masonry.api"
44

55
[tool.poetry]
66
name = "camoufox"
7-
version = "0.5.6"
7+
version = "0.5.6b1"
88
description = "Wrapper around Playwright to help launch Camoufox"
99
authors = ["daijro <daijro.dev@gmail.com>"]
1010
license = "MIT"
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
"""A caller's own binary is never replaced -- but it should still be checked.
2+
3+
A managed install below the floor gets upgraded by pkgman. `executable_path`
4+
deliberately bypasses that, which leaves one pairing nothing checks: an old
5+
build driven by Playwright >= 1.61, whose viewport fields the older Juggler
6+
schema rejects. Without this the user sees a bare "Protocol error
7+
(Browser.setDefaultViewport)" and nothing naming the cause.
8+
9+
It warns rather than raises on purpose: camoufox defaults to no_viewport when
10+
it spoofs window dimensions, so the default path works on an old build. Only an
11+
explicit viewport breaks, so refusing to launch would break working setups.
12+
"""
13+
14+
import json
15+
16+
import pytest
17+
18+
from camoufox import pkgman, utils
19+
20+
21+
def _bundle(tmp_path, build):
22+
"""A browser directory with version.json beside the binary, as a release has."""
23+
d = tmp_path / f"152.0.4-{build}"
24+
d.mkdir()
25+
(d / "version.json").write_text(json.dumps({"version": "152.0.4", "build": build}))
26+
return d / "camoufox-bin"
27+
28+
29+
@pytest.fixture
30+
def floor_at_beta30(monkeypatch):
31+
monkeypatch.setattr(utils, "effective_version_min", lambda: pkgman.Version(build="beta.30"))
32+
33+
34+
def test_warns_when_the_supplied_build_is_too_old(tmp_path, floor_at_beta30):
35+
exe = _bundle(tmp_path, "beta.29")
36+
37+
with pytest.warns(RuntimeWarning, match=r"beta\.29.*beta\.30"):
38+
utils.warn_if_executable_predates_playwright(exe)
39+
40+
41+
def test_names_the_symptom_the_user_will_actually_see(tmp_path, floor_at_beta30):
42+
exe = _bundle(tmp_path, "beta.29")
43+
44+
with pytest.warns(RuntimeWarning) as caught:
45+
utils.warn_if_executable_predates_playwright(exe)
46+
47+
assert "Browser.setDefaultViewport" in str(caught[0].message)
48+
49+
50+
@pytest.mark.parametrize("build", ["beta.30", "beta.31"])
51+
def test_silent_when_the_build_is_new_enough(tmp_path, floor_at_beta30, build, recwarn):
52+
utils.warn_if_executable_predates_playwright(_bundle(tmp_path, build))
53+
54+
assert not [w for w in recwarn if issubclass(w.category, RuntimeWarning)]
55+
56+
57+
def test_silent_for_a_custom_build_with_no_version_json(tmp_path, floor_at_beta30, recwarn):
58+
"""An unpackaged objdir build tells us nothing; do not nag about it."""
59+
(tmp_path / "dist").mkdir()
60+
61+
utils.warn_if_executable_predates_playwright(tmp_path / "dist" / "camoufox-bin")
62+
63+
assert not [w for w in recwarn if issubclass(w.category, RuntimeWarning)]
64+
65+
66+
def test_silent_when_no_executable_path_was_given(floor_at_beta30, recwarn):
67+
utils.warn_if_executable_predates_playwright(None)
68+
69+
assert not [w for w in recwarn if issubclass(w.category, RuntimeWarning)]

0 commit comments

Comments
 (0)