blob: 158ecbb712db188d1d034f5bf8ff46f99a9b27ee [file] [log] [blame]
Marc Kupietz55fc3162022-12-04 16:25:49 +01001const chai = require('chai');
2const assert = chai.assert;
Marc Kupietz5e45a2f2022-12-03 15:32:40 +01003
4class KorAPRC {
5 korap_url = ""
6
7 constructor(korap_url) {
8 this.korap_url = korap_url
9 }
10
11 static new(korap_url) {
12 return new KorAPRC(korap_url)
13 }
14
15 async login(page, username, password) {
Marc Kupietz93d7f702025-06-27 15:41:48 +020016 try {
17 await page.goto(this.korap_url, { waitUntil: 'domcontentloaded' });
18 if (username == "") return false;
19 if (password == "") return false;
Marc Kupietz5e45a2f2022-12-03 15:32:40 +010020
Marc Kupietz96aa4152026-06-12 17:20:32 +020021 // Check if already logged in as the correct user
22 const currentLoggedInUser = await page.evaluate(() => {
23 const profileBtn = document.querySelector('.dropdown-btn.profile');
24 if (profileBtn) {
25 const userNameEl = profileBtn.querySelector('.user-name');
26 return userNameEl ? userNameEl.textContent.trim() : null;
27 }
28 return null;
29 });
30
31 if (currentLoggedInUser === username) {
32 console.log(`Already logged in as ${username}`);
33 return true;
34 } else if (currentLoggedInUser) {
35 console.log(`Logged in as different user: ${currentLoggedInUser}. Logging out...`);
36 await this.logout(page);
37 await page.goto(this.korap_url, { waitUntil: 'domcontentloaded' });
38 }
39
Marc Kupietz93d7f702025-06-27 15:41:48 +020040 await page.waitForSelector('.dropdown-btn', { visible: true });
Marc Kupietz96aa4152026-06-12 17:20:32 +020041 const loginBtn = await page.$('.dropdown-btn.login') || await page.$('.dropdown-btn');
42 if (!loginBtn) {
43 return false;
44 }
45 await loginBtn.click();
Marc Kupietz93d7f702025-06-27 15:41:48 +020046 await page.waitForSelector('input[name=handle_or_email]', { visible: true });
47 const username_field = await page.$("input[name=handle_or_email]")
48 if (username_field != null) {
49 await username_field.focus();
50 await username_field.type(username);
51 const password_field = await page.$("input[name=pwd]")
52 await password_field.focus()
53 await page.keyboard.type(password)
54 await page.keyboard.press("Enter")
55 } else {
56 return false
57 }
58
59 await page.waitForNavigation({ waitUntil: 'domcontentloaded' }); // Wait for navigation after login
60 await page.waitForSelector("#q-field", { visible: true }); // Wait for query field to confirm login
Marc Kupietz96aa4152026-06-12 17:20:32 +020061 const logout = await page.$(".logout, a[data-testid=\"logout\"], .dropdown-btn.profile")
Marc Kupietz93d7f702025-06-27 15:41:48 +020062 if (logout == null) {
63 return false
64 }
65
66 return true
67 } catch (error) {
68 console.error(`Login failed: ${error.message}`);
69 return false;
Marc Kupietz5e45a2f2022-12-03 15:32:40 +010070 }
Marc Kupietz5e45a2f2022-12-03 15:32:40 +010071 }
72
73 async search(page, query) {
Marc Kupietz964e7772025-06-03 15:02:30 +020074 try {
Marc Kupietz93d7f702025-06-27 15:41:48 +020075 await page.waitForSelector("#q-field", { visible: true });
76 const query_field = await page.$("#q-field");
77 assert.notEqual(query_field, null, "Query field not found");
Marc Kupietz964e7772025-06-03 15:02:30 +020078
Marc Kupietz93d7f702025-06-27 15:41:48 +020079 await query_field.click({ clickCount: 3 });
80 await page.keyboard.type(query);
81 await page.keyboard.press("Enter");
Marc Kupietz964e7772025-06-03 15:02:30 +020082
Marc Kupietz93d7f702025-06-27 15:41:48 +020083 await page.waitForNavigation({ waitUntil: 'domcontentloaded' });
84
85 // Wait for search results to be fully loaded
86 try {
87 await page.waitForSelector('ol li, #resultinfo, .result-item', {
88 visible: true,
89 timeout: 15000
90 });
91 // Give additional time for the results count to be populated
92 await new Promise(resolve => setTimeout(resolve, 2000));
93 } catch (error) {
94 // Continue if timeout, fallback methods will handle it
95 }
96
97 const resultsInfo = await page.evaluate(() => {
98 // Check common selectors for result counts
99 const selectors = [
100 '#total-results',
101 '#resultinfo',
102 '.result-count',
103 '.total-results',
104 '[data-results]',
105 '.found'
106 ];
107
108 for (const selector of selectors) {
109 const element = document.querySelector(selector);
110 if (element) {
111 const text = element.textContent || element.innerText || '';
112 const numbers = text.match(/\d+/g);
113 if (numbers && numbers.length > 0) {
114 return {
115 selector: selector,
116 numbers: numbers
117 };
118 }
119 }
120 }
121
122 // Look in the page title for results count
123 const title = document.title;
124 if (title) {
125 const numbers = title.match(/\d+/g);
Marc Kupietz964e7772025-06-03 15:02:30 +0200126 if (numbers && numbers.length > 0) {
127 return {
Marc Kupietz93d7f702025-06-27 15:41:48 +0200128 selector: 'title',
Marc Kupietz964e7772025-06-03 15:02:30 +0200129 numbers: numbers
130 };
131 }
132 }
Marc Kupietz964e7772025-06-03 15:02:30 +0200133
Marc Kupietz93d7f702025-06-27 15:41:48 +0200134 // Count the actual result items as fallback
135 const resultItems = document.querySelectorAll('ol li');
136 if (resultItems.length > 0) {
Marc Kupietz964e7772025-06-03 15:02:30 +0200137 return {
Marc Kupietz93d7f702025-06-27 15:41:48 +0200138 selector: 'counted-items',
139 numbers: [resultItems.length.toString()]
Marc Kupietz964e7772025-06-03 15:02:30 +0200140 };
141 }
Marc Kupietz964e7772025-06-03 15:02:30 +0200142
Marc Kupietz93d7f702025-06-27 15:41:48 +0200143 return null;
Marc Kupietz964e7772025-06-03 15:02:30 +0200144 });
145
Marc Kupietz93d7f702025-06-27 15:41:48 +0200146 if (!resultsInfo || !resultsInfo.numbers || resultsInfo.numbers.length === 0) {
147 // Final fallback: just count visible list items
148 const itemCount = await page.evaluate(() => {
149 return document.querySelectorAll('ol li').length;
150 });
151
152 if (itemCount > 0) {
153 return itemCount;
154 }
155
156 throw new Error("Cannot find any results count on the page");
Marc Kupietz964e7772025-06-03 15:02:30 +0200157 }
158
Marc Kupietz93d7f702025-06-27 15:41:48 +0200159 // Extract the largest number found (likely the total results)
160 const hits = Math.max(...resultsInfo.numbers.map(n => parseInt(n, 10)));
161 return hits;
162 } catch (error) {
163 throw new Error(`Failed to perform search: ${error.message}`);
Marc Kupietz964e7772025-06-03 15:02:30 +0200164 }
Marc Kupietz5e45a2f2022-12-03 15:32:40 +0100165 }
166
167 async logout(page) {
Marc Kupietz964e7772025-06-03 15:02:30 +0200168 try {
Marc Kupietz96aa4152026-06-12 17:20:32 +0200169 // First, try to find the logout link in the page
170 const logoutHref = await page.evaluate(() => {
171 const logoutEl = document.querySelector('a.logout, a[data-testid="logout"]');
172 return logoutEl ? logoutEl.getAttribute('href') : null;
173 });
Marc Kupietz964e7772025-06-03 15:02:30 +0200174
Marc Kupietz96aa4152026-06-12 17:20:32 +0200175 if (logoutHref) {
176 const absoluteLogoutUrl = new URL(logoutHref, page.url()).href;
177 await page.goto(absoluteLogoutUrl, { waitUntil: 'domcontentloaded', timeout: 10000 });
178 } else {
179 const currentUrl = await page.url();
180 const baseUrl = currentUrl.replace(/\/$/, '');
181 try {
182 await page.goto(baseUrl + '/user/logout', { waitUntil: 'domcontentloaded', timeout: 5000 });
183 } catch (e) {
184 await page.goto(baseUrl + '/logout', { waitUntil: 'domcontentloaded', timeout: 5000 });
185 }
186 }
Marc Kupietz964e7772025-06-03 15:02:30 +0200187
188 // Navigate back to main page to ensure clean state for subsequent tests
189 await page.goto(this.korap_url, { waitUntil: 'domcontentloaded', timeout: 10000 });
190
Marc Kupietz96aa4152026-06-12 17:20:32 +0200191 // Verify we are actually logged out
192 const loggedOut = await page.evaluate(() => {
193 return document.querySelector('a.logout, a[data-testid="logout"], .dropdown-btn.profile') === null;
194 });
195
196 return loggedOut;
Marc Kupietz964e7772025-06-03 15:02:30 +0200197 } catch (error) {
Marc Kupietz96aa4152026-06-12 17:20:32 +0200198 console.error(`Logout failed: ${error.message}`);
Marc Kupietz964e7772025-06-03 15:02:30 +0200199 return false;
Marc Kupietz5e45a2f2022-12-03 15:32:40 +0100200 }
Marc Kupietz5e45a2f2022-12-03 15:32:40 +0100201 }
202
203 async assure_glimpse_off(page) {
Marc Kupietz0462c7d2026-03-21 10:10:00 +0100204 // Get the cutoff checkbox - works in both old and new Kalamar versions
Marc Kupietz5e45a2f2022-12-03 15:32:40 +0100205 const glimpse = await page.$("input[name=cutoff]")
Marc Kupietz0462c7d2026-03-21 10:10:00 +0100206 if (!glimpse) {
207 console.log("Glimpse checkbox not found, skipping")
208 return
209 }
Marc Kupietz5e45a2f2022-12-03 15:32:40 +0100210 const glimpse_value = await (await glimpse.getProperty('checked')).jsonValue()
211 if (glimpse_value) {
Marc Kupietz0462c7d2026-03-21 10:10:00 +0100212 // Try new Kalamar version first (toggle button with class 'glimpse')
213 const newGlimpseButton = await page.$(".glimpse")
214 if (newGlimpseButton) {
215 const isVisible = await page.evaluate(el => {
216 const style = window.getComputedStyle(el)
217 return style.display !== 'none' && style.visibility !== 'hidden'
218 }, newGlimpseButton)
219 if (isVisible) {
220 await newGlimpseButton.click()
221 return
222 }
223 }
224 // Fall back to old Kalamar version (label with id 'glimpse')
225 const oldGlimpseLabel = await page.$("#glimpse")
226 if (oldGlimpseLabel) {
227 const isVisible = await page.evaluate(el => {
228 const style = window.getComputedStyle(el.parentNode || el)
229 return style.display !== 'none' && style.visibility !== 'hidden'
230 }, oldGlimpseLabel)
231 if (isVisible) {
232 await page.click("#glimpse")
233 return
234 }
235 }
236 // Last resort: directly toggle the checkbox via JavaScript
237 await page.evaluate(() => {
238 const checkbox = document.querySelector("input[name=cutoff]")
239 if (checkbox) checkbox.checked = false
240 })
Marc Kupietz5e45a2f2022-12-03 15:32:40 +0100241 }
Marc Kupietzc8ffb2b2025-06-12 16:44:23 +0200242 }
Marc Kupietz5e45a2f2022-12-03 15:32:40 +0100243
Marc Kupietzc8ffb2b2025-06-12 16:44:23 +0200244 async check_corpus_statistics(page, minTokenThreshold = 1000) {
245 try {
Marc Kupietz669c0432025-07-12 12:33:42 +0200246 console.log(`Starting corpus statistics check with minTokenThreshold: ${minTokenThreshold}`);
247
Marc Kupietzc8ffb2b2025-06-12 16:44:23 +0200248 // Navigate to the corpus view if not already there
Marc Kupietz669c0432025-07-12 12:33:42 +0200249 console.log(`Navigating to: ${this.korap_url}`);
Marc Kupietz93d7f702025-06-27 15:41:48 +0200250 await page.goto(this.korap_url, { waitUntil: 'domcontentloaded' });
Marc Kupietz669c0432025-07-12 12:33:42 +0200251 console.log("Navigation completed");
252
Marc Kupietzc8ffb2b2025-06-12 16:44:23 +0200253 // Click the vc-choose element to open corpus selection
Marc Kupietz669c0432025-07-12 12:33:42 +0200254 console.log("Waiting for #vc-choose selector...");
Marc Kupietz93d7f702025-06-27 15:41:48 +0200255 await page.waitForSelector('#vc-choose', { visible: true, timeout: 90000 });
Marc Kupietz669c0432025-07-12 12:33:42 +0200256 console.log("Found #vc-choose, clicking...");
Marc Kupietzc8ffb2b2025-06-12 16:44:23 +0200257 await page.click('#vc-choose');
Marc Kupietz669c0432025-07-12 12:33:42 +0200258 console.log("Clicked #vc-choose");
259
Marc Kupietzc8ffb2b2025-06-12 16:44:23 +0200260 // Wait a moment for the UI to respond
Marc Kupietz669c0432025-07-12 12:33:42 +0200261 console.log("Waiting 1 second for UI to respond...");
Marc Kupietzc8ffb2b2025-06-12 16:44:23 +0200262 await new Promise(resolve => setTimeout(resolve, 1000));
Marc Kupietz669c0432025-07-12 12:33:42 +0200263
Marc Kupietzc8ffb2b2025-06-12 16:44:23 +0200264 // Click the statistic element
Marc Kupietz669c0432025-07-12 12:33:42 +0200265 console.log("Waiting for .statistic selector...");
Marc Kupietz93d7f702025-06-27 15:41:48 +0200266 await page.waitForSelector('.statistic', { visible: true, timeout: 90000 });
Marc Kupietz669c0432025-07-12 12:33:42 +0200267 console.log("Found .statistic element, attempting to click...");
Marc Kupietz93d7f702025-06-27 15:41:48 +0200268 try {
269 await page.click('.statistic');
Marc Kupietz669c0432025-07-12 12:33:42 +0200270 console.log("Successfully clicked .statistic element");
Marc Kupietz93d7f702025-06-27 15:41:48 +0200271 } catch (error) {
Marc Kupietz669c0432025-07-12 12:33:42 +0200272 console.error(`Failed to click statistic element: ${error.message}`);
Marc Kupietz93d7f702025-06-27 15:41:48 +0200273 throw new Error(`Failed to click statistic element: ${error.message}`);
274 }
Marc Kupietz669c0432025-07-12 12:33:42 +0200275
276 // Wait for statistics to load with a more efficient approach
277 console.log("Waiting for token statistics to load...");
Marc Kupietzc8ffb2b2025-06-12 16:44:23 +0200278
Marc Kupietz669c0432025-07-12 12:33:42 +0200279 // First, wait for any dd elements to appear (basic structure)
280 await page.waitForSelector('dd', { visible: true, timeout: 30000 });
281
282 // Then wait for the specific token statistics with a simplified check
Marc Kupietz93d7f702025-06-27 15:41:48 +0200283 await page.waitForFunction(() => {
Marc Kupietz669c0432025-07-12 12:33:42 +0200284 // Simplified check - look for any dd element with a large number
Marc Kupietz93d7f702025-06-27 15:41:48 +0200285 const ddElements = document.querySelectorAll('dd');
Marc Kupietz669c0432025-07-12 12:33:42 +0200286 for (let i = 0; i < ddElements.length; i++) {
287 const text = ddElements[i].textContent || ddElements[i].innerText || '';
Marc Kupietz93d7f702025-06-27 15:41:48 +0200288 const cleanedText = text.replace(/[,\.]/g, '');
289 const numbers = cleanedText.match(/\d+/g);
290 if (numbers && numbers.length > 0) {
Marc Kupietz669c0432025-07-12 12:33:42 +0200291 const num = parseInt(numbers[0], 10);
292 if (num > 1000) { // Found a substantial number, likely loaded
293 return true;
294 }
Marc Kupietz93d7f702025-06-27 15:41:48 +0200295 }
296 }
297 return false;
Marc Kupietz669c0432025-07-12 12:33:42 +0200298 }, { timeout: 90000, polling: 1000 }); // Poll every second instead of continuously
Marc Kupietz93d7f702025-06-27 15:41:48 +0200299
Marc Kupietzc8ffb2b2025-06-12 16:44:23 +0200300 // Look for the tokens count in a dd element that follows an element with title "tokens"
Marc Kupietz669c0432025-07-12 12:33:42 +0200301 console.log(`Starting token count extraction with minThreshold: ${minTokenThreshold}`);
Marc Kupietzc8ffb2b2025-06-12 16:44:23 +0200302 const tokenCount = await page.evaluate((minThreshold) => {
Marc Kupietz669c0432025-07-12 12:33:42 +0200303 // Find the element with title "tokens" first
Marc Kupietzc8ffb2b2025-06-12 16:44:23 +0200304 const tokenTitleElements = document.querySelectorAll('[title="tokens"], [title*="token"]');
305
Marc Kupietz669c0432025-07-12 12:33:42 +0200306 for (let i = 0; i < tokenTitleElements.length; i++) {
307 const element = tokenTitleElements[i];
308
Marc Kupietzc8ffb2b2025-06-12 16:44:23 +0200309 // Look for the next dd element
310 let nextElement = element.nextElementSibling;
Marc Kupietz669c0432025-07-12 12:33:42 +0200311 let siblingCount = 0;
312 while (nextElement && siblingCount < 10) {
313 siblingCount++;
314
Marc Kupietzc8ffb2b2025-06-12 16:44:23 +0200315 if (nextElement.tagName.toLowerCase() === 'dd') {
316 const text = nextElement.textContent || nextElement.innerText || '';
317 // Remove number separators (commas and periods) and extract number
318 const cleanedText = text.replace(/[,\.]/g, '');
319 const numbers = cleanedText.match(/\d+/g);
320 if (numbers && numbers.length > 0) {
Marc Kupietz669c0432025-07-12 12:33:42 +0200321 const tokenValue = parseInt(numbers[0], 10);
322 return tokenValue;
Marc Kupietzc8ffb2b2025-06-12 16:44:23 +0200323 }
324 }
325 nextElement = nextElement.nextElementSibling;
326 }
327 }
Marc Kupietz669c0432025-07-12 12:33:42 +0200328
Marc Kupietzc8ffb2b2025-06-12 16:44:23 +0200329 // Alternative approach: look for dd elements that contain large numbers
330 const ddElements = document.querySelectorAll('dd');
Marc Kupietz669c0432025-07-12 12:33:42 +0200331 const candidateTokenCounts = [];
332
333 for (let i = 0; i < ddElements.length; i++) {
334 const dd = ddElements[i];
Marc Kupietzc8ffb2b2025-06-12 16:44:23 +0200335 const text = dd.textContent || dd.innerText || '';
336 // Remove separators and check if it's a large number (likely token count)
337 const cleanedText = text.replace(/[,\.]/g, '');
338 const numbers = cleanedText.match(/\d+/g);
339 if (numbers && numbers.length > 0) {
340 const num = parseInt(numbers[0], 10);
Marc Kupietz669c0432025-07-12 12:33:42 +0200341
342 // Use the provided threshold to filter candidates
Marc Kupietzc8ffb2b2025-06-12 16:44:23 +0200343 if (num > minThreshold) {
Marc Kupietz669c0432025-07-12 12:33:42 +0200344 candidateTokenCounts.push({ value: num, text: text, index: i });
Marc Kupietzc8ffb2b2025-06-12 16:44:23 +0200345 }
346 }
347 }
Marc Kupietz669c0432025-07-12 12:33:42 +0200348
349 if (candidateTokenCounts.length > 0) {
350 // Return the largest candidate (most likely to be the total token count)
351 const bestCandidate = candidateTokenCounts.reduce((max, current) =>
352 current.value > max.value ? current : max
353 );
354 return bestCandidate.value;
355 }
356
Marc Kupietzc8ffb2b2025-06-12 16:44:23 +0200357 return null;
358 }, minTokenThreshold);
Marc Kupietz669c0432025-07-12 12:33:42 +0200359
360 console.log(`Token count extraction completed. Result: ${tokenCount}`);
361
Marc Kupietzc8ffb2b2025-06-12 16:44:23 +0200362 if (tokenCount === null) {
Marc Kupietz669c0432025-07-12 12:33:42 +0200363 console.error("ERROR: Token count extraction returned null");
Marc Kupietzc8ffb2b2025-06-12 16:44:23 +0200364 throw new Error("Could not find token count in corpus statistics");
365 }
Marc Kupietz669c0432025-07-12 12:33:42 +0200366
367 console.log(`SUCCESS: Found token count: ${tokenCount}, threshold was: ${minTokenThreshold}`);
Marc Kupietzc8ffb2b2025-06-12 16:44:23 +0200368 return tokenCount;
Marc Kupietz669c0432025-07-12 12:33:42 +0200369
Marc Kupietzc8ffb2b2025-06-12 16:44:23 +0200370 } catch (error) {
Marc Kupietz669c0432025-07-12 12:33:42 +0200371 console.error(`ERROR in check_corpus_statistics: ${error.message}`);
372 console.error("Full error stack:", error.stack);
Marc Kupietzc8ffb2b2025-06-12 16:44:23 +0200373 throw new Error(`Failed to check corpus statistics: ${error.message}`);
374 }
Marc Kupietz5e45a2f2022-12-03 15:32:40 +0100375 }
376}
377
Marc Kupietz93d7f702025-06-27 15:41:48 +0200378module.exports = KorAPRC