mirror of
https://github.com/daijro/camoufox.git
synced 2026-09-09 00:00:39 +00:00
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>
168 lines
6.9 KiB
Python
168 lines
6.9 KiB
Python
"""Regression coverage for raising the supported browser floor.
|
|
|
|
Raising ``CONSTRAINTS.MIN_VERSION`` is how the library forces an existing
|
|
install to upgrade when it can no longer talk to the old browser. That path
|
|
had been dead since the floor was dropped to ``alpha.1``, and it hid a bug:
|
|
``camoufox_path`` probed ``INSTALL_DIR/version.json``, which only the
|
|
pre-multiversion flat layout ever wrote, so a below-floor versioned install
|
|
raised ``FileNotFoundError`` instead of falling through to a fetch.
|
|
"""
|
|
|
|
import json
|
|
|
|
import pytest
|
|
|
|
from camoufox import multiversion, pkgman
|
|
from camoufox.exceptions import UnsupportedVersion
|
|
|
|
|
|
def _install(tmp_path, monkeypatch, layout, build, floor):
|
|
"""Build an install dir in the given layout and point the library at it."""
|
|
root = tmp_path / "cache"
|
|
root.mkdir()
|
|
(root / ".0.5_FLAG").write_text("")
|
|
(root / "repo_cache.json").write_text("{}")
|
|
|
|
if layout == "versioned":
|
|
relative = f"browsers/official/152.0.4-{build}"
|
|
(root / "config.json").write_text(json.dumps({"active_version": relative}))
|
|
version_dir = root / relative
|
|
version_dir.mkdir(parents=True)
|
|
else:
|
|
(root / "config.json").write_text("{}")
|
|
version_dir = root
|
|
(version_dir / "version.json").write_text(
|
|
json.dumps({"version": "152.0.4", "build": build})
|
|
)
|
|
|
|
for module in (pkgman, multiversion):
|
|
monkeypatch.setattr(module, "INSTALL_DIR", root)
|
|
monkeypatch.setattr(multiversion, "BROWSERS_DIR", root / "browsers")
|
|
monkeypatch.setattr(multiversion, "CONFIG_FILE", root / "config.json")
|
|
monkeypatch.setattr(multiversion, "COMPAT_FLAG", root / ".0.5_FLAG")
|
|
monkeypatch.setattr(pkgman, "VERSION_MIN", pkgman.Version(build=floor))
|
|
return root
|
|
|
|
|
|
@pytest.mark.parametrize("layout", ["versioned", "legacy"])
|
|
def test_below_floor_install_is_reported_as_outdated(tmp_path, monkeypatch, layout):
|
|
"""A build under the floor must report as outdated, never as missing."""
|
|
_install(tmp_path, monkeypatch, layout, build="beta.29", floor="beta.30")
|
|
|
|
with pytest.raises(UnsupportedVersion):
|
|
pkgman.camoufox_path(download_if_missing=False)
|
|
|
|
|
|
@pytest.mark.parametrize("layout", ["versioned", "legacy"])
|
|
def test_at_floor_install_is_kept(tmp_path, monkeypatch, layout):
|
|
"""A build at the floor is still served, in either layout."""
|
|
root = _install(tmp_path, monkeypatch, layout, build="beta.30", floor="beta.30")
|
|
|
|
resolved = pkgman.camoufox_path(download_if_missing=False)
|
|
|
|
expected = root if layout == "legacy" else root / "browsers/official/152.0.4-beta.30"
|
|
assert resolved == expected
|
|
|
|
|
|
def test_below_floor_install_triggers_a_fetch(tmp_path, monkeypatch):
|
|
"""The default path upgrades the install instead of raising."""
|
|
root = _install(tmp_path, monkeypatch, "versioned", build="beta.29", floor="beta.30")
|
|
installed = []
|
|
|
|
class StubFetcher:
|
|
def install(self):
|
|
installed.append(True)
|
|
relative = "browsers/official/152.0.4-beta.30"
|
|
version_dir = root / relative
|
|
version_dir.mkdir(parents=True)
|
|
(version_dir / "version.json").write_text(
|
|
json.dumps({"version": "152.0.4", "build": "beta.30"})
|
|
)
|
|
(root / "config.json").write_text(json.dumps({"active_version": relative}))
|
|
|
|
monkeypatch.setattr(pkgman, "CamoufoxFetcher", StubFetcher)
|
|
|
|
resolved = pkgman.camoufox_path()
|
|
|
|
assert installed == [True]
|
|
assert resolved == root / "browsers/official/152.0.4-beta.30"
|
|
|
|
|
|
def test_root_probe_tolerates_the_versioned_layout(tmp_path, monkeypatch):
|
|
"""The root probe reports False, rather than raising, with no root file."""
|
|
root = tmp_path / "cache"
|
|
root.mkdir()
|
|
monkeypatch.setattr(pkgman, "INSTALL_DIR", root)
|
|
|
|
assert pkgman._root_install_supported() is False
|
|
|
|
|
|
def test_unsatisfiable_floor_reports_instead_of_recursing(tmp_path, monkeypatch):
|
|
"""A floor no published build satisfies must report, not spin.
|
|
|
|
install() is a no-op when the newest release is already installed, so the
|
|
old `return camoufox_path()` tail recursed ~1000 times -- each iteration
|
|
firing another GitHub API call, which exhausts the unauthenticated rate
|
|
limit long before the RecursionError lands. That is the state a library
|
|
published ahead of its browser release puts every user in.
|
|
"""
|
|
_install(tmp_path, monkeypatch, "versioned", build="beta.29", floor="beta.30")
|
|
attempts = []
|
|
|
|
class StubFetcher:
|
|
def install(self):
|
|
attempts.append(True) # newest published build is still below the floor
|
|
|
|
monkeypatch.setattr(pkgman, "CamoufoxFetcher", StubFetcher)
|
|
|
|
with pytest.raises(UnsupportedVersion):
|
|
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)
|