Fix false-positive failures in "has hits" search tests
The search tests sometimes reported failure (and fired notifications) even
though the result page showed matches. Two changes:
- The per-search mocha timeout was a hard 20s, shorter than search()'s own
internal waits, so a slow total-count computation could trip it before the
count was read. The timeout is now configurable via KORAP_SEARCH_TIMEOUT
(default 60s).
- search() no longer relies on a fixed 2s sleep. It waits until the result
page has settled -- the total is shown, matches are listed, or a "no
matches" message appears -- and reads the outcome immediately.
Note this slightly relaxes what "has hits" verifies: when glimpse/cutoff is
on, Kalamar lists matches without computing a total, so the test now accepts
the presence of listed matches as proof of hits rather than requiring a
counted total. This keeps the check stable across glimpse states; when
glimpse is off the exact total is still read and asserted.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Change-Id: I92f2004ba668722939e3a70e3ab8aa7eae698b4d
diff --git a/lib/korap_rc.js b/lib/korap_rc.js
index 158ecbb..0bdf1af 100644
--- a/lib/korap_rc.js
+++ b/lib/korap_rc.js
@@ -82,82 +82,37 @@
await page.waitForNavigation({ waitUntil: 'domcontentloaded' });
- // Wait for search results to be fully loaded
- try {
- await page.waitForSelector('ol li, #resultinfo, .result-item', {
- visible: true,
- timeout: 15000
- });
- // Give additional time for the results count to be populated
- await new Promise(resolve => setTimeout(resolve, 2000));
- } catch (error) {
- // Continue if timeout, fallback methods will handle it
- }
+ // Wait until the results page has actually settled, then read the
+ // count immediately (no fixed sleep). Kalamar renders the precise
+ // total into #total-results (e.g. "1,039,193") on hits, but when
+ // glimpse/cutoff is on it lists matches without computing a total,
+ // and on a miss it shows a .no-results message. A search "has hits"
+ // as soon as any match is listed, so settle on whichever appears.
+ await page.waitForFunction(() => {
+ const total = document.querySelector('#total-results');
+ if (total && /\d/.test(total.textContent || '')) return true;
+ if (document.querySelectorAll('#search ol li').length > 0) return true;
+ return document.querySelector('#search .no-results, .no-results') !== null;
+ }, { timeout: 15000, polling: 200 });
- const resultsInfo = await page.evaluate(() => {
- // Check common selectors for result counts
- const selectors = [
- '#total-results',
- '#resultinfo',
- '.result-count',
- '.total-results',
- '[data-results]',
- '.found'
- ];
-
- for (const selector of selectors) {
- const element = document.querySelector(selector);
- if (element) {
- const text = element.textContent || element.innerText || '';
- const numbers = text.match(/\d+/g);
- if (numbers && numbers.length > 0) {
- return {
- selector: selector,
- numbers: numbers
- };
- }
- }
+ const hits = await page.evaluate(() => {
+ const total = document.querySelector('#total-results');
+ if (total) {
+ // The span holds only the number; strip thousands separators
+ // (commas, periods, spaces) and parse what remains.
+ const digits = (total.textContent || '').replace(/[^\d]/g, '');
+ if (digits.length > 0) return parseInt(digits, 10);
}
- // Look in the page title for results count
- const title = document.title;
- if (title) {
- const numbers = title.match(/\d+/g);
- if (numbers && numbers.length > 0) {
- return {
- selector: 'title',
- numbers: numbers
- };
- }
- }
+ // No exact total (e.g. glimpse/cutoff on): listed matches still
+ // prove the query has hits, so fall back to counting them.
+ const items = document.querySelectorAll('#search ol li').length;
+ if (items > 0) return items;
- // Count the actual result items as fallback
- const resultItems = document.querySelectorAll('ol li');
- if (resultItems.length > 0) {
- return {
- selector: 'counted-items',
- numbers: [resultItems.length.toString()]
- };
- }
-
- return null;
+ // Explicit "no matches" state reported by Kalamar.
+ return 0;
});
- if (!resultsInfo || !resultsInfo.numbers || resultsInfo.numbers.length === 0) {
- // Final fallback: just count visible list items
- const itemCount = await page.evaluate(() => {
- return document.querySelectorAll('ol li').length;
- });
-
- if (itemCount > 0) {
- return itemCount;
- }
-
- throw new Error("Cannot find any results count on the page");
- }
-
- // Extract the largest number found (likely the total results)
- const hits = Math.max(...resultsInfo.numbers.map(n => parseInt(n, 10)));
return hits;
} catch (error) {
throw new Error(`Failed to perform search: ${error.message}`);