The picker's paging state outlives a reopen — navigate, don't assume
Sleeping Gods and Gloomhaven "vanished on the second pass" because the second pass began wherever the first ended: closing and reopening the version sub-view does NOT reset it to page 1 (Angular keeps the scope), so the rescan started mid-list and never revisited the earlier pages holding the row. Verified against the live picker. The second pass now clicks the visible numbered "1" anchor first — and so does the initial scan, since paging state can outlive anything. The reopen is gone entirely. Docs record both this and the has_text whitespace trap. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016jXZFSTZQKzAC8fqpWSz9g
This commit is contained in:
co-authored by
Claude Fable 5
parent
6f2f2dac53
commit
6e72ef22d1
+24
-11
@@ -382,6 +382,7 @@ class PlaywrightUploader:
|
||||
# visible-xs-* — so every one of these selectors matches hidden nodes
|
||||
# too; clicking one waits forever. Always pick the visible match.
|
||||
_NEXT_PAGE = 'ul.pagination a[title="Next Page"]'
|
||||
_PAGE_LINKS = "ul.pagination a"
|
||||
# Rows read "<game name> (<version name>) (<year>)", so the version name
|
||||
# is matched inside its parentheses — bare substrings would let
|
||||
# "English edition" match "English edition, second printing".
|
||||
@@ -428,6 +429,8 @@ class PlaywrightUploader:
|
||||
# BGG's API name can carry a printing qualifier the picker omits
|
||||
# ("English edition 2018-2" vs "(English edition) (2018)")
|
||||
relaxed = f"({self._TRAILING_YEAR.sub('', version_name).strip()})".casefold()
|
||||
self._goto_first_page(dialog) # a fresh sub-view opens on page 1,
|
||||
# but never assume it: paging state outlives a close/reopen
|
||||
exact: list[tuple[int, str]] = []
|
||||
loose: list[tuple[int, str]] = []
|
||||
pages = 0
|
||||
@@ -472,18 +475,28 @@ class PlaywrightUploader:
|
||||
f"({', '.join(t for _, t in hits[:3])}…) — refusing to guess"
|
||||
)
|
||||
|
||||
def _click_row(self, dialog, text: str) -> bool:
|
||||
"""Reopen the sub-view (it always starts on page 1) and hunt for the
|
||||
row again by its NORMALIZED text, clicking by index.
|
||||
def _goto_first_page(self, dialog) -> None:
|
||||
"""Back to page 1 via the numbered "1" anchor. Closing and
|
||||
reopening the sub-view does NOT reset Angular's paging state (it
|
||||
reopens wherever it was left), and First/Prev render in a
|
||||
mobile-only variant that a desktop viewport can never click."""
|
||||
pager = dialog.locator(self._PAGE_LINKS)
|
||||
for i in range(pager.count()):
|
||||
anchor = pager.nth(i)
|
||||
if anchor.is_visible() and (
|
||||
" ".join((anchor.text_content() or "").split()) == "1"
|
||||
):
|
||||
anchor.click()
|
||||
self._page.wait_for_timeout(400)
|
||||
return
|
||||
# no pagination rendered: a single-page list is already page 1
|
||||
|
||||
Two traps this avoids: First/Prev exist only in the mobile variant
|
||||
(unclickable on a desktop viewport), and Playwright's has_text
|
||||
regex matches raw textContent — whose tabs and newlines a
|
||||
whitespace-normalized capture will never equal."""
|
||||
dialog.get_by_role("button", name="Cancel").first.click()
|
||||
self._page.wait_for_timeout(300)
|
||||
dialog.get_by_role("button", name="Set version/edition").click()
|
||||
dialog.locator(self._VERSION_ROWS).first.wait_for(timeout=15_000)
|
||||
def _click_row(self, dialog, text: str) -> bool:
|
||||
"""Find the row again by its NORMALIZED text and click it by index.
|
||||
Playwright's has_text regex matches raw textContent — whose tabs and
|
||||
newlines a normalized capture will never equal — so the comparison
|
||||
happens in Python, and the click addresses a position."""
|
||||
self._goto_first_page(dialog)
|
||||
for _ in range(MAX_VERSION_PAGES):
|
||||
for i, row_text in enumerate(self._rows_on_page(dialog)):
|
||||
if row_text == text:
|
||||
|
||||
Reference in New Issue
Block a user