mirror of
https://github.com/daijro/camoufox.git
synced 2026-09-09 00:00:39 +00:00
fix(python): key the browser floor on Playwright instead of a flat minimum
The incompatibility is two-dimensional -- it needs both a Playwright >= 1.61
and a browser < beta.30 -- but MIN_VERSION only knows about the browser. To
stay safe a flat floor has to assume the worst Playwright, which means:
* every 0.5.6 user re-downloads the browser, including the majority on
<1.61 who are in no danger;
* installs pinned to an older build lose the pin, and prerelease/alpha users
are moved off their channel, since every alpha sorts below beta.30;
* the library cannot run at all until the matching browser release is
published, making the PyPI-after-release ordering load-bearing.
Key it on the resolved Playwright instead. Measured: 1.60 works on beta.29 and
beta.30; 1.61 and 1.62 fail on beta.29 and pass on beta.30.
playwright <1.61 -> floor alpha.1 -> every install kept
playwright >=1.61 -> floor beta.30 -> below-beta.30 installs upgraded
version unreadable -> floor alpha.1 -> kept; a spurious forced re-download is
worse than leaving a working install
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
df35ae79d2
commit
b1fe7227fa
@@ -8,9 +8,27 @@ class CONSTRAINTS:
|
||||
The minimum and maximum supported versions of the Camoufox browser.
|
||||
"""
|
||||
|
||||
MIN_VERSION = 'beta.30'
|
||||
MIN_VERSION = 'alpha.1'
|
||||
MAX_VERSION = '1'
|
||||
|
||||
# The browser floor is conditional on the resolved Playwright, not fixed.
|
||||
#
|
||||
# Each entry is (playwright_version, required_browser_build): from that
|
||||
# Playwright on, the browser must be at least that build. 1.61 began
|
||||
# sending viewport isMobile/screenSize in Browser.setDefaultViewport and
|
||||
# Page.setViewportSize; beta.30 is the first build whose Protocol.js schema
|
||||
# accepts them. Below that pairing every new_context() dies with
|
||||
# "Protocol error (Browser.setDefaultViewport)". Measured: 1.60 works on
|
||||
# beta.29 and beta.30; 1.61 and 1.62 fail on beta.29 and pass on beta.30.
|
||||
#
|
||||
# A flat MIN_VERSION cannot express this. It only knows about the browser,
|
||||
# so to stay safe it has to assume the worst Playwright and force *every*
|
||||
# user to re-download -- including the majority on <1.61, who are in no
|
||||
# danger -- and it leaves the library unusable until the matching browser
|
||||
# release is published. Keyed on Playwright, only the users who would
|
||||
# actually break get moved.
|
||||
PLAYWRIGHT_BROWSER_FLOORS = (((1, 61), 'beta.30'),)
|
||||
|
||||
@staticmethod
|
||||
def as_range() -> str:
|
||||
"""
|
||||
|
||||
@@ -367,7 +367,7 @@ class Version:
|
||||
return self.sorted_rel < other.sorted_rel
|
||||
|
||||
def is_supported(self) -> bool:
|
||||
return VERSION_MIN <= self < VERSION_MAX
|
||||
return effective_version_min() <= self < VERSION_MAX
|
||||
|
||||
@staticmethod
|
||||
def from_path(path: Optional[Path] = None) -> 'Version':
|
||||
@@ -406,6 +406,34 @@ class Version:
|
||||
VERSION_MIN, VERSION_MAX = Version.build_minmax()
|
||||
|
||||
|
||||
def _resolved_playwright_version() -> Optional[Tuple[int, ...]]:
|
||||
"""The installed Playwright version, or None if it cannot be determined."""
|
||||
from importlib.metadata import version
|
||||
|
||||
try:
|
||||
return _parse_semver(version('playwright'))
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def effective_version_min() -> 'Version':
|
||||
"""The lowest browser build this install can actually talk to.
|
||||
|
||||
VERSION_MIN, raised by whatever the resolved Playwright requires. When the
|
||||
Playwright version cannot be read we fall back to VERSION_MIN rather than
|
||||
assuming the worst: a spurious forced re-download is worse than leaving a
|
||||
working install alone, and pyproject caps Playwright anyway.
|
||||
"""
|
||||
floor = VERSION_MIN
|
||||
playwright_version = _resolved_playwright_version()
|
||||
if playwright_version is None:
|
||||
return floor
|
||||
for required_playwright, build in CONSTRAINTS.PLAYWRIGHT_BROWSER_FLOORS:
|
||||
if playwright_version >= required_playwright and floor < Version(build=build):
|
||||
floor = Version(build=build)
|
||||
return floor
|
||||
|
||||
|
||||
class GitHubDownloader:
|
||||
"""
|
||||
Manages fetching GitHub releases with fallback repos
|
||||
|
||||
@@ -119,3 +119,49 @@ def test_unsatisfiable_floor_reports_instead_of_recursing(tmp_path, monkeypatch)
|
||||
pkgman.camoufox_path()
|
||||
|
||||
assert attempts == [True], "should fetch once, not spin"
|
||||
|
||||
|
||||
class TestConditionalFloor:
|
||||
"""The browser floor is keyed on the resolved Playwright, not fixed.
|
||||
|
||||
A flat floor can only speak about the browser, so to be safe it has to
|
||||
assume the worst Playwright and re-download for everyone -- and it makes
|
||||
the library unusable until the matching browser release exists. Keyed on
|
||||
Playwright, only the pairing that actually breaks gets moved.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def _with_playwright(monkeypatch, version):
|
||||
parsed = pkgman._parse_semver(version) if version else None
|
||||
monkeypatch.setattr(pkgman, "_resolved_playwright_version", lambda: parsed)
|
||||
|
||||
@pytest.mark.parametrize("version", ["1.53.0", "1.60.0"])
|
||||
def test_old_playwright_leaves_older_builds_alone(self, monkeypatch, version):
|
||||
"""<1.61 never sends the fields beta.29 rejects, so nothing must move."""
|
||||
self._with_playwright(monkeypatch, version)
|
||||
assert pkgman.effective_version_min() == pkgman.Version(build="alpha.1")
|
||||
|
||||
@pytest.mark.parametrize("version", ["1.61.0", "1.62.0"])
|
||||
def test_new_playwright_raises_the_floor(self, monkeypatch, version):
|
||||
self._with_playwright(monkeypatch, version)
|
||||
assert pkgman.effective_version_min() == pkgman.Version(build="beta.30")
|
||||
|
||||
def test_unreadable_playwright_falls_back_permissive(self, monkeypatch):
|
||||
"""A spurious forced re-download is worse than leaving a working install."""
|
||||
self._with_playwright(monkeypatch, None)
|
||||
assert pkgman.effective_version_min() == pkgman.Version(build="alpha.1")
|
||||
|
||||
def test_old_playwright_keeps_a_below_floor_install(self, tmp_path, monkeypatch):
|
||||
_install(tmp_path, monkeypatch, "versioned", build="beta.29", floor="alpha.1")
|
||||
self._with_playwright(monkeypatch, "1.60.0")
|
||||
|
||||
resolved = pkgman.camoufox_path(download_if_missing=False)
|
||||
|
||||
assert resolved.name == "152.0.4-beta.29"
|
||||
|
||||
def test_new_playwright_moves_a_below_floor_install(self, tmp_path, monkeypatch):
|
||||
_install(tmp_path, monkeypatch, "versioned", build="beta.29", floor="alpha.1")
|
||||
self._with_playwright(monkeypatch, "1.62.0")
|
||||
|
||||
with pytest.raises(UnsupportedVersion):
|
||||
pkgman.camoufox_path(download_if_missing=False)
|
||||
|
||||
Reference in New Issue
Block a user