SeleniumBase off the leash (within 4.x), with a live Amazon test holding the other end

The pin froze SeleniumBase while Chrome updated itself every build, which
is the combination most likely to break undetected mode. It now floats
within 4.x (4.55.0 today), and test_amazon_album_names_itself names a real
Amazon album through /api/albums/resolve-album-url, so a breakage fails
the slow suite instead of somebody's album import.

Built with a human and a grumpy-AI combo, trusting tests over pins.
This commit is contained in:
g33kphr33k committed 2026-10-04 10:20:01 +01:00
1 parent a2b408e8c5
commit aa220b82bb
5 files changed
+62 -4

No files matched your search

+1 -1
View File
@@ -9,7 +9,7 @@ Housekeeping. The backlog's to-do list, cleared in one sitting, which has never
### Changed
- **One spacing scale, actually used**: `style.css` had a perfectly good spacing scale that 30 of its 513 margins, paddings and gaps used; the rest were picked by eye, one spot at a time, which is how you get fourteen slightly different gaps. Every one is now on the scale, which gained half-steps for 2, 6, 10 and 20px because those were doing too much work to snap away. The rare odd values (3, 5, 7, 14, 18px and friends) moved to the nearest step, so a few badges, chips and cards shift by a pixel or two; nothing should look different unless you go looking with a ruler.
- **SeleniumBase 4.49 to 4.54**: it only drives the Amazon Music album namer now, which was checked against real albums on the UK and US storefronts before trusting it.
- **SeleniumBase unpinned within 4.x**: it only drives the Amazon Music album namer now, and the pin was guarding the wrong half. Chrome is always whatever is current when the image is built, and SeleniumBase is where the fixes for Chrome's changes turn up, so an old SeleniumBase against a new Chrome was the riskier pairing. Each build now takes the newest 4.x (4.55.0 today), and a new slow test names a real Amazon album through the API, so the test suite notices a breakage before your next album link does.
- **Boot work waits for the server**: database setup, the schedulers and the source health sweep now run when uvicorn starts the app rather than whenever something imports `app.py`. Unit tests import it for its route functions and used to get the whole lot thrown in, including a health sweep probing every source over the real network mid-suite.
- **The cache-bust token watches the stylesheet too**: it only looked at `app.js`, `index.html` and `release-notes.js`, so a change to `style.css` or `quips.js` alone could be served stale from the browser cache until something else changed.
+5 -1
View File
@@ -45,7 +45,11 @@ RUN pip install --no-cache-dir \
# SeleniumBase is kept beside Playwright rather than replacing the working
# Spotify/Amazon playlist scrapers. Its undetected mode is what gets an Amazon
# Music album page to name itself (amazonalbum.py); Playwright cannot.
RUN pip install --no-cache-dir seleniumbase~=4.54.0
# It floats within 4.x on purpose: Chrome below is whatever is current at build
# time, and SeleniumBase is where the fixes for Chrome's changes arrive. A pin
# only froze the half that does not break. test_amazon_album_names_itself (slow)
# is the safety net; <5 keeps out a major rewrite.
RUN pip install --no-cache-dir "seleniumbase>=4.54,<5"
# Install Playwright browsers into a fixed path so non-root users (PUID/PGID) can find them.
# Without this, Playwright falls back to ~/.cache/ms-playwright which resolves differently per user.
+26
View File
@@ -5,6 +5,7 @@ All are @pytest.mark.slow - they hit real external services.
Current state (2026-09-22):
- Spotify embed: BROKEN (returns 1 track) - tests correctly catch this
- Amazon: working (showHome JSON with rendered-page fallback)
- Amazon albums: working (SeleniumBase undetected mode, page title)
- Apple Music: working
- YouTube: working (see test_playlists.py)
- ListenBrainz: working
@@ -158,6 +159,31 @@ def test_fetch_amazon_playlist_over_one_hundred_tracks(api, base_url):
assert len(d.get("tracks", [])) > 100
# Album pages are named by amazonalbum.py driving SeleniumBase's undetected
# mode, and SeleniumBase floats within 4.x while Chrome is whatever is current
# at build time. This is the test that notices when one of them, or Amazon,
# moves the goalposts, rather than the next person to paste an album link.
_AMAZON_ALBUM_URL = "https://music.amazon.co.uk/albums/B0FBGRYDM9" # Olivia Dean, The Art of Loving
@pytest.mark.slow
def test_amazon_album_names_itself(api, base_url):
r = api.post(
f"{base_url}/api/albums/resolve-album-url",
json={"url": _AMAZON_ALBUM_URL},
timeout=150,
)
if r.status_code == 503:
pytest.skip(f"MusicBrainz unreachable, which says nothing about Amazon: {r.text}")
assert r.status_code == 200, f"resolve failed ({r.status_code}): {r.text}"
d = r.json()
assert d.get("service") == "amazon"
assert (d.get("artist"), d.get("album")) == ("Olivia Dean", "The Art of Loving"), (
"Amazon did not name the album; check SeleniumBase, Chrome and "
f"amazonalbum.py against the container logs. Got: {d}"
)
# ---------------------------------------------------------------------------
# ListenBrainz
# ---------------------------------------------------------------------------
+22
View File
@@ -118,6 +118,28 @@ def test_check_duplicate_falls_back_to_primary_credited_artist(tmp_path, monkeyp
)
def test_check_duplicate_does_not_treat_instrumental_as_original(tmp_path, monkeypatch):
"""A version qualifier must not make a different recording a duplicate."""
import utils
singles = tmp_path / "Singles"
instrumental = singles / "Michael Jackson - Thriller (Instrumental).flac"
instrumental.parent.mkdir(parents=True)
instrumental.write_bytes(b"not the original")
monkeypatch.setattr(utils, "get_download_dir", lambda *a, **kw: singles)
monkeypatch.setattr(utils, "get_singles_dir", lambda **kw: singles)
monkeypatch.setattr(utils, "get_albums_dir", lambda **kw: tmp_path / "Albums")
monkeypatch.setattr(utils, "get_playlists_dir", lambda **kw: None)
monkeypatch.setattr(utils, "_tagged_duplicate", lambda *a, **kw: None)
assert utils.check_duplicate("Michael Jackson", "Thriller") is None
assert (
utils.check_duplicate("Michael Jackson", "Thriller (Instrumental)")
== instrumental
)
def test_check_duplicate_finds_externally_renamed_file_by_tags(tmp_path, monkeypatch):
import metadata
import time
+8 -2
View File
@@ -159,10 +159,16 @@ def clean_title(title: str) -> str:
def _normalise_duplicate_stem(stem: str) -> str:
"""Normalise a filename stem for duplicate comparisons."""
"""Normalise a filename stem for duplicate comparisons.
Keep bracketed version details. They are part of a recording's identity:
treating ``Thriller (Instrumental)`` as ``Thriller`` means the instrumental
can prevent the actual song being downloaded. ``clean_title`` has already
removed disposable publishing noise such as "Official Video" before this
point, so retaining the remaining words is the safer choice.
"""
s = clean_title(stem or "")
s = s.replace("’", "'").replace("‘", "'").replace("`", "'")
s = re.sub(r"\s*[\(\[].*?[\)\]]", "", s)
s = re.sub(r"[^a-z0-9]+", " ", s.lower())
return re.sub(r"\s+", " ", s).strip()