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',
- '',
+ '
',
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,
);
}
+ await testCurrentPage(
+ 'visible page wins while URL has advanced',
+ 'https://www.amazon.com/s?k=home+security&page=3&ref=sr_pg_3',
+ '',
+ 2,
+ );
await testCurrentPage(
'invalid URL page uses ref fallback',
'https://www.amazon.com/s?k=home+security&page=invalid&ref=sr_pg_9',
- '',
+ '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;
}
/**