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:
Jake Writer
2026-08-31 15:08:26 -06:00
co-authored by Claude Opus 5
parent df35ae79d2
commit b1fe7227fa
3 changed files with 94 additions and 2 deletions
@@ -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)