From d42e19f42f2b291112fdf8abbec2a5afaf080064 Mon Sep 17 00:00:00 2001 From: syrup <1032771964@qq.com> Date: Wed, 2 Sep 2026 10:57:01 +0800 Subject: [PATCH] fix: bind Amazon results to visible page --- scripts/test-amazon-search-pagination.ts | 14 +++++-- .../web-injectors/amazon-search-pagination.ts | 38 ++++++++++++++++--- 2 files changed, 43 insertions(+), 9 deletions(-) diff --git a/scripts/test-amazon-search-pagination.ts b/scripts/test-amazon-search-pagination.ts index 6bc4d56..925718b 100644 --- a/scripts/test-amazon-search-pagination.ts +++ b/scripts/test-amazon-search-pagination.ts @@ -44,23 +44,29 @@ const main = async () => { ); await testNextPage('missing next page', '
No pagination
', null); await testCurrentPage( - 'URL page wins over stale pagination selection', + 'visible page wins over hidden stale pagination selection', 'https://www.amazon.com/s?k=home+security&page=11&ref=sr_pg_11', - '12', + '
12
11', 11, ); for (const page of [1, 2, 5, 10, 20]) { await testCurrentPage( `URL page ${page}`, `https://www.amazon.com/s?k=home+security&page=${page}&ref=sr_pg_${page}`, - `${page + 1}`, + `${page}`, page, ); } + await testCurrentPage( + 'visible page wins while URL has advanced', + 'https://www.amazon.com/s?k=home+security&page=3&ref=sr_pg_3', + '2', + 2, + ); await testCurrentPage( 'invalid URL page uses ref fallback', 'https://www.amazon.com/s?k=home+security&page=invalid&ref=sr_pg_9', - '10', + '
No selected pagination item
', 9, ); await testCurrentPage( diff --git a/src/page-worker/web-injectors/amazon-search-pagination.ts b/src/page-worker/web-injectors/amazon-search-pagination.ts index 1c5870b..eab3c92 100644 --- a/src/page-worker/web-injectors/amazon-search-pagination.ts +++ b/src/page-worker/web-injectors/amazon-search-pagination.ts @@ -1,7 +1,7 @@ /** - * Resolves the page being collected. Amazon's selected pagination element can - * point at the next page while the current results are still rendered, so the - * document URL is the authoritative source whenever it carries a page number. + * Resolves the page represented by the currently rendered search results. + * Amazon can update the URL before replacing the visible result DOM, so the + * rendered selected-page control is authoritative and the URL is only a fallback. */ export async function collectAmazonCurrentPage(): Promise { const currentUrl = new URL(window.location.href); @@ -10,6 +10,32 @@ export async function collectAmazonCurrentPage(): Promise { return Number.isInteger(page) && page > 0 ? page : null; }; + const isRendered = (node: HTMLElement) => { + for (let current: HTMLElement | null = node; current; current = current.parentElement) { + if (current.hidden || current.getAttribute('aria-hidden') === 'true') return false; + const style = current.ownerDocument.defaultView?.getComputedStyle(current); + if ( + style && + (style.display === 'none' || + style.visibility === 'hidden' || + style.visibility === 'collapse' || + style.opacity === '0') + ) { + return false; + } + } + if (document.documentElement.getClientRects().length === 0) return true; + return Array.from(node.getClientRects()).some((rect) => rect.width > 0 && rect.height > 0); + }; + + const renderedSelectedPages = Array.from( + document.querySelectorAll('.s-pagination-item.s-pagination-selected'), + ) + .filter(isRendered) + .map((node) => parsePage(node.textContent?.trim())) + .filter((page): page is number => page !== null); + if (renderedSelectedPages.length > 0) return renderedSelectedPages[0]; + const urlPage = parsePage(currentUrl.searchParams.get('page')); if (urlPage) return urlPage; @@ -18,8 +44,10 @@ export async function collectAmazonCurrentPage(): Promise { ); if (refPage) return refPage; - const selected = document.querySelector('.s-pagination-item.s-pagination-selected'); - return parsePage(selected?.textContent?.trim()) || 1; + const anySelected = document.querySelector( + '.s-pagination-item.s-pagination-selected', + ); + return parsePage(anySelected?.textContent?.trim()) || 1; } /**