Version picker: real pagination, and matching that declines to guess

Five games uploaded; two landed version-less. Neither was the picker's
fault: paging is an AngularJS <ul class="pagination"> of anchors, not
buttons named "next", so the old guess found no control and quit after
page one — and BGG's API version names carry printing qualifiers the
picker omits ("English edition 2018-2" vs "(English edition) (2018)").

_select_version now scans the WHOLE list (verified selectors: rows are
<li>s with a thumbnail; a[title="Next Page"] advances; the parent <li>
disables at the end), collects every candidate, then decides: one exact
match wins; failing that, one match after stripping a trailing year
qualifier wins and says so; several matches are refused outright rather
than guessed, and the reason reaches upload_log.csv. Both call sites
carry the reason through.

docs/bgg-upload-flow.md records what the live site actually does —
including that every login-gate selector the doc called "verified" was
wrong, while the "unverified" dialog structure was right.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016jXZFSTZQKzAC8fqpWSz9g
This commit is contained in:
Eric Wagoner
2026-08-05 22:36:48 -04:00
co-authored by Claude Fable 5
parent 6ae2667c58
commit ba55863cef
4 changed files with 253 additions and 24 deletions
+36
View File
@@ -100,3 +100,39 @@ the existing one).
page but did not overlay the form in the probe.
- Logged-in detection heuristic (unverified): the header shows a "Sign In"
link only when logged out.
## Verified against the live site (2026-08-06, first real uploads)
The add flow works end to end; every failure on the way was in code the
earlier walkthrough had marked *verified*, and the parts marked
*unverified* were mostly right. Corrections:
- **Sign In is an `<a class="btn">` with no `href`.** It therefore has no
implicit `link` role: `get_by_role("link", name="Sign In")` matches
nothing in any state. The header also hydrates after
`domcontentloaded`, so for a moment neither Sign In nor Sign Out
exists — a check resting on one absence silently concludes "signed in"
and browses anonymously. Poll until one control or the other proves the
state; treat "neither, after 30s" as an error.
- **`get_by_label("Own")` also matches "Prev. Owned."** Use
`get_by_role("checkbox", name="Own", exact=True)`.
- **Version rows** are the `<li>`s carrying a thumbnail:
`li:has(.summary-item-thumbnail)`. Plain `listitem` also catches the
paging `<li>`s ("First", "Prev", "1", "…").
- **Paging is an AngularJS `<ul class="pagination">` of anchors**, not
buttons: `a[title="Next Page"]`, `a[title="First Page"]`, with the
parent `<li>` gaining `disabled` at the end. The numbered anchors
render twice (mobile + desktop), so step with Next rather than
clicking a number. Paging is client-side over an already-loaded list —
no request per page.
- **Row text is `<game name> (<version name>) (<year>)`**, and the game
name is localized (a Czech edition's row starts "Spící bohové"). Match
the version name inside its parentheses.
- **The API's version name is not always the picker's string.** BGG's
XML gives e.g. `English edition 2018-2` where the picker shows
`(English edition) (2018)`. Match the full name first, then retry with
a trailing year/printing qualifier stripped — and if that relaxed match
hits more than one row, refuse and add version-less (never guess).
- **An owned game's page has no "Add To" button**; it reads
"In Collections (Own…)". That is the update flow's entry point.