First test if server is reachable and via https
Change-Id: I02bdb64e0300d2d7913e3929505eb2e5e29f79ea
diff --git a/lib/korap_rc.js b/lib/korap_rc.js
index 21c1eb8..8e5a22f 100644
--- a/lib/korap_rc.js
+++ b/lib/korap_rc.js
@@ -13,123 +13,132 @@
}
async login(page, username, password) {
- page.goto(this.korap_url);
- await page.waitForNavigation({ waitUntil: 'networkidle2' });
- if (this.login == "") return false;
- if (username == "") return false;
- if (password == "") return false;
+ try {
+ await page.goto(this.korap_url, { waitUntil: 'domcontentloaded' });
+ if (username == "") return false;
+ if (password == "") return false;
- await page.click('.dropdown-btn');
- await page.waitForSelector('input[name=handle_or_email]', { visible: true });
- const username_field = await page.$("input[name=handle_or_email]")
- if (username_field != null) {
- await username_field.focus();
- await username_field.type(username);
- const password_field = await page.$("input[name=pwd]")
- await password_field.focus()
- await page.keyboard.type(password)
- await page.keyboard.press("Enter")
- } else {
- return false
+ await page.waitForSelector('.dropdown-btn', { visible: true });
+ await page.click('.dropdown-btn');
+ await page.waitForSelector('input[name=handle_or_email]', { visible: true });
+ const username_field = await page.$("input[name=handle_or_email]")
+ if (username_field != null) {
+ await username_field.focus();
+ await username_field.type(username);
+ const password_field = await page.$("input[name=pwd]")
+ await password_field.focus()
+ await page.keyboard.type(password)
+ await page.keyboard.press("Enter")
+ } else {
+ return false
+ }
+
+ await page.waitForNavigation({ waitUntil: 'domcontentloaded' }); // Wait for navigation after login
+ await page.waitForSelector("#q-field", { visible: true }); // Wait for query field to confirm login
+ const logout = await page.$(".logout")
+ if (logout == null) {
+ return false
+ }
+
+ return true
+ } catch (error) {
+ console.error(`Login failed: ${error.message}`);
+ return false;
}
-
- await page.waitForNavigation({ waitUntil: 'networkidle2' });
- const logout = await page.$(".logout")
- if (logout == null) {
- return false
- }
-
- return true
}
async search(page, query) {
- const query_field = await page.$("#q-field");
- assert.notEqual(query_field, null, "Query field not found");
-
- await page.waitForSelector("#q-field", { visible: true });
- await query_field.click({ clickCount: 3 });
- await page.keyboard.type(query);
- await page.keyboard.press("Enter");
-
- await page.waitForNavigation({ waitUntil: 'networkidle2' });
-
- // 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
- }
+ await page.waitForSelector("#q-field", { visible: true });
+ const query_field = await page.$("#q-field");
+ assert.notEqual(query_field, null, "Query field not found");
- const resultsInfo = await page.evaluate(() => {
- // Check common selectors for result counts
- const selectors = [
- '#total-results',
- '#resultinfo',
- '.result-count',
- '.total-results',
- '[data-results]',
- '.found'
- ];
+ await query_field.click({ clickCount: 3 });
+ await page.keyboard.type(query);
+ await page.keyboard.press("Enter");
- for (const selector of selectors) {
- const element = document.querySelector(selector);
- if (element) {
- const text = element.textContent || element.innerText || '';
- const numbers = text.match(/\d+/g);
+ 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
+ }
+
+ 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
+ };
+ }
+ }
+ }
+
+ // 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: selector,
+ selector: 'title',
numbers: numbers
};
}
}
- }
- // 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) {
+ // Count the actual result items as fallback
+ const resultItems = document.querySelectorAll('ol li');
+ if (resultItems.length > 0) {
return {
- selector: 'title',
- numbers: numbers
+ selector: 'counted-items',
+ numbers: [resultItems.length.toString()]
};
}
- }
- // 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;
- });
-
- 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;
+ return null;
});
- if (itemCount > 0) {
- return itemCount;
+ 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");
}
- 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}`);
}
-
- // Extract the largest number found (likely the total results)
- const hits = Math.max(...resultsInfo.numbers.map(n => parseInt(n, 10)));
- return hits;
}
async logout(page) {
@@ -160,24 +169,55 @@
async check_corpus_statistics(page, minTokenThreshold = 1000) {
try {
// Navigate to the corpus view if not already there
- await page.goto(this.korap_url, { waitUntil: 'networkidle2' });
+ await page.goto(this.korap_url, { waitUntil: 'domcontentloaded' });
// Click the vc-choose element to open corpus selection
- await page.waitForSelector('#vc-choose', { visible: true, timeout: 10000 });
+ await page.waitForSelector('#vc-choose', { visible: true, timeout: 90000 });
await page.click('#vc-choose');
// Wait a moment for the UI to respond
await new Promise(resolve => setTimeout(resolve, 1000));
// Click the statistic element
- await page.waitForSelector('.statistic', { visible: true, timeout: 10000 });
- await page.click('.statistic');
+ await page.waitForSelector('.statistic', { visible: true, timeout: 90000 });
+ try {
+ await page.click('.statistic');
+ } catch (error) {
+ throw new Error(`Failed to click statistic element: ${error.message}`);
+ }
- // Wait for statistics to load
- await new Promise(resolve => setTimeout(resolve, 3000));
-
+ // Wait for statistics to load and token count to appear
+ await page.waitForFunction(() => {
+ const tokenTitleElements = document.querySelectorAll('[title="tokens"], [title*="token"]');
+ for (const element of tokenTitleElements) {
+ let nextElement = element.nextElementSibling;
+ while (nextElement) {
+ if (nextElement.tagName.toLowerCase() === 'dd') {
+ const text = nextElement.textContent || nextElement.innerText || '';
+ const cleanedText = text.replace(/[,\.]/g, '');
+ const numbers = cleanedText.match(/\d+/g);
+ if (numbers && numbers.length > 0) {
+ return true;
+ }
+ }
+ nextElement = nextElement.nextElementSibling;
+ }
+ }
+ const ddElements = document.querySelectorAll('dd');
+ for (const dd of ddElements) {
+ const text = dd.textContent || dd.innerText || '';
+ const cleanedText = text.replace(/[,\.]/g, '');
+ const numbers = cleanedText.match(/\d+/g);
+ if (numbers && numbers.length > 0) {
+ return true;
+ }
+ }
+ return false;
+ }, { timeout: 60000 });
+
// Look for the tokens count in a dd element that follows an element with title "tokens"
const tokenCount = await page.evaluate((minThreshold) => {
+ console.log("Attempting to find token count within page.evaluate...");
// Find the element with title "tokens"
const tokenTitleElements = document.querySelectorAll('[title="tokens"], [title*="token"]');
@@ -191,6 +231,7 @@
const cleanedText = text.replace(/[,\.]/g, '');
const numbers = cleanedText.match(/\d+/g);
if (numbers && numbers.length > 0) {
+ console.log(`Found token count from title element: ${numbers[0]}`);
return parseInt(numbers[0], 10);
}
}
@@ -209,11 +250,13 @@
const num = parseInt(numbers[0], 10);
// Use the provided threshold instead of hardcoded value
if (num > minThreshold) {
+ console.log(`Found token count from dd element: ${num}`);
return num;
}
}
}
+ console.log("Could not find token count using any method.");
return null;
}, minTokenThreshold);
@@ -229,4 +272,4 @@
}
}
-module.exports = KorAPRC
+module.exports = KorAPRC
\ No newline at end of file
diff --git a/test/korap-ui.js b/test/korap-ui.js
index f81193a..fcf1fcd 100644
--- a/test/korap-ui.js
+++ b/test/korap-ui.js
@@ -1,3 +1,4 @@
+const https = require('https');
const puppeteer = require('puppeteer-extra');
puppeteer.use(require('puppeteer-extra-plugin-user-preferences')({
userPrefs: {
@@ -32,6 +33,10 @@
describe('Running KorAP UI end-to-end tests on ' + KORAP_URL, () => {
+ let browser;
+ let page;
+
+
before(async () => {
browser = await puppeteer.launch({
headless: "shell",
@@ -51,21 +56,19 @@
height: 768,
deviceScaleFactor: 1,
});
- console.log("Browser version: " + await browser.version() + " started")
+
})
- after(async () => {
- await browser.close()
+ after(async function() {
+ if (browser && typeof browser.close === 'function') {
+ await browser.close();
+ }
})
afterEach(async function () {
if (this.currentTest.state == "failed") {
- const screenshotPath = "failed_" + this.currentTest.title.replaceAll(/[ &\/]/g, "_") + '.png';
- await page.screenshot({ path: screenshotPath });
-
if (slack) {
try {
- // First send text notification via webhook
slack.alert({
text: `🚨 Test on ${KORAP_URL} failed: *${this.currentTest.title}*`,
attachments: [{
@@ -86,87 +89,196 @@
}
}
- // Try to upload screenshot via Slack Web API if token is available
- const slackToken = process.env.SLACK_TOKEN;
- if (slackToken) {
- try {
- const { WebClient } = require('@slack/web-api');
- const fs = require('fs'); const web = new WebClient(slackToken);
- const channelId = process.env.SLACK_CHANNEL_ID || 'C07CM4JS48H';
+ // Only take screenshot if it's not one of the initial connectivity/SSL tests
+ const initialTestTitles = [
+ 'should be reachable',
+ 'should have a valid SSL certificate'
+ ];
+ if (!initialTestTitles.includes(this.currentTest.title) && page) {
+ const screenshotPath = "failed_" + this.currentTest.title.replaceAll(/[ &\/]/g, "_") + '.png';
+ await page.screenshot({ path: screenshotPath });
- // Upload the file to Slack
- const result = await web.files.uploadV2({
- channel_id: channelId,
- file: fs.createReadStream(screenshotPath),
- filename: screenshotPath,
- title: `Screenshot: ${this.currentTest.title}`,
- initial_comment: `📸 Screenshot of failed test: ${this.currentTest.title} on ${KORAP_URL}`
- });
+ const slackToken = process.env.SLACK_TOKEN;
+ if (slackToken) {
+ try {
+ const { WebClient } = require('@slack/web-api');
+ const fs = require('fs'); const web = new WebClient(slackToken);
+ const channelId = process.env.SLACK_CHANNEL_ID || 'C07CM4JS48H';
- console.log('Screenshot uploaded to Slack successfully');
+ const result = await web.files.uploadV2({
+ channel_id: channelId,
+ file: fs.createReadStream(screenshotPath),
+ filename: screenshotPath,
+ title: `Screenshot: ${this.currentTest.title}`,
+ initial_comment: `📸 Screenshot of failed test: ${this.currentTest.title} on ${KORAP_URL}`
+ });
- } catch (uploadError) {
- console.error('Failed to upload screenshot to Slack:', uploadError.message);
+ } catch (uploadError) {
+ console.error('Failed to upload screenshot to Slack:', uploadError.message);
+ }
}
}
}
})
- it('KorAP UI is up and running',
- (async () => {
- page.goto(KORAP_URL);
- await page.waitForNavigation({ waitUntil: 'networkidle2' });
- const query_field = await page.$("#q-field")
- assert.isNotNull(query_field, "#q-field not found. Kalamar not running?");
- }))
+ it('should be reachable', function (done) {
+ let doneCalled = false;
+ const url = new URL(KORAP_URL);
+ const httpModule = url.protocol === 'https:' ? https : require('http');
+ const req = httpModule.request({
+ method: 'HEAD',
+ hostname: url.hostname,
+ port: url.port || (url.protocol === 'https:' ? 443 : 80),
+ path: url.pathname,
+ timeout: 5000
+ }, res => {
+ if (!doneCalled) {
+ doneCalled = true;
+ if (res.statusCode >= 200 && res.statusCode < 400) {
+ done();
+ } else {
+ done(new Error(`Server is not reachable. Status code: ${res.statusCode}`));
+ }
+ }
+ });
+ req.on('timeout', () => {
+ if (!doneCalled) {
+ doneCalled = true;
+ req.destroy();
+ done(new Error('Request to server timed out.'));
+ }
+ });
+ req.on('error', err => {
+ if (!doneCalled) {
+ doneCalled = true;
+ done(err);
+ }
+ });
+ req.end();
+ });
- ifConditionIt('Login into KorAP with incorrect credentials fails',
- KORAP_LOGIN != "",
- (async () => {
- const login_result = await korap_rc.login(page, KORAP_LOGIN, KORAP_PWD + "*")
- login_result.should.be.false
- }))
+ it('should have a valid SSL certificate', function (done) {
+ let doneCalled = false;
+ const url = new URL(KORAP_URL);
+ if (url.protocol !== 'https:') {
+ return this.skip();
+ }
+ const req = https.request({
+ method: 'HEAD',
+ hostname: url.hostname,
+ port: url.port || 443,
+ path: url.pathname,
+ timeout: 5000
+ }, res => {
+ if (!doneCalled) {
+ doneCalled = true;
+ const cert = res.socket.getPeerCertificate();
+ if (cert && cert.valid_to) {
+ const validTo = new Date(cert.valid_to);
+ if (validTo > new Date()) {
+ done();
+ } else {
+ done(new Error(`SSL certificate expired on ${validTo.toDateString()}`));
+ }
+ } else if (res.socket.isSessionReused()){
+ done();
+ }
+ else {
+ done(new Error('Could not retrieve SSL certificate information.'));
+ }
+ }
+ });
+ req.on('timeout', () => {
+ if (!doneCalled) {
+ doneCalled = true;
+ req.destroy();
+ done(new Error('Request to server timed out.'));
+ }
+ });
+ req.on('error', err => {
+ if (!doneCalled) {
+ doneCalled = true;
+ if (err.code === 'CERT_HAS_EXPIRED') {
+ done(new Error('SSL certificate has expired.'));
+ } else {
+ done(err);
+ }
+ }
+ });
+ req.end();
+ });
- ifConditionIt('Login into KorAP with correct credentials succeeds',
- KORAP_LOGIN != "",
- (async () => {
- const login_result = await korap_rc.login(page, KORAP_LOGIN, KORAP_PWD)
- login_result.should.be.true
- }))
+ describe('UI Tests', function() {
- it('Can turn glimpse off',
- (async () => {
- await korap_rc.assure_glimpse_off(page)
- }))
+ before(function() {
+ // Check the state of the parent suite's tests
+ const initialTests = this.test.parent.parent.tests;
+ if (initialTests[0].state === 'failed' || initialTests[1].state === 'failed') {
+ this.skip();
+ }
+ });
- it('Corpus statistics show sufficient tokens',
- (async () => {
- const tokenCount = await korap_rc.check_corpus_statistics(page, KORAP_MIN_TOKENS_IN_CORPUS);
- console.log(`Found ${tokenCount} tokens in corpus, minimum required: ${KORAP_MIN_TOKENS_IN_CORPUS}`);
- tokenCount.should.be.above(KORAP_MIN_TOKENS_IN_CORPUS - 1,
- `Corpus should have at least ${KORAP_MIN_TOKENS_IN_CORPUS} tokens, but found ${tokenCount}`);
- })).timeout(50000)
+
- describe('Running searches that should have hits', () => {
-
- before(async () => { await korap_rc.login(page, KORAP_LOGIN, KORAP_PWD) })
-
- KORAP_QUERIES.split(/[;,] */).forEach((query, i) => {
- it('Search for "' + query + '" has hits',
- (async () => {
- await korap_rc.assure_glimpse_off(page)
- const hits = await korap_rc.search(page, query)
- hits.should.be.above(0)
- })).timeout(20000)
+ it('KorAP UI is up and running', async function () {
+ try {
+ await page.goto(KORAP_URL, { waitUntil: 'domcontentloaded' });
+ await page.waitForSelector("#q-field", { visible: true });
+ const query_field = await page.$("#q-field")
+ assert.isNotNull(query_field, "#q-field not found. Kalamar not running?");
+ } catch (error) {
+ throw new Error(`Failed to load KorAP UI or find query field: ${error.message}`);
+ }
})
- })
- ifConditionIt('Logout works',
- KORAP_LOGIN != "",
- (async () => {
- const logout_result = await korap_rc.logout(page)
- logout_result.should.be.true
- })).timeout(15000)
-})
+ ifConditionIt('Login into KorAP with incorrect credentials fails',
+ KORAP_LOGIN != "",
+ (async () => {
+ const login_result = await korap_rc.login(page, KORAP_LOGIN, KORAP_PWD + "*")
+ login_result.should.be.false
+ }))
+
+ ifConditionIt('Login into KorAP with correct credentials succeeds',
+ KORAP_LOGIN != "",
+ (async () => {
+ const login_result = await korap_rc.login(page, KORAP_LOGIN, KORAP_PWD)
+ login_result.should.be.true
+ }))
+
+ it('Can turn glimpse off',
+ (async () => {
+ await korap_rc.assure_glimpse_off(page)
+ }))
+
+ it('Corpus statistics show sufficient tokens',
+ (async () => {
+ const tokenCount = await korap_rc.check_corpus_statistics(page, KORAP_MIN_TOKENS_IN_CORPUS);
+ console.log(`Found ${tokenCount} tokens in corpus, minimum required: ${KORAP_MIN_TOKENS_IN_CORPUS}`);
+ tokenCount.should.be.above(KORAP_MIN_TOKENS_IN_CORPUS - 1,
+ `Corpus should have at least ${KORAP_MIN_TOKENS_IN_CORPUS} tokens, but found ${tokenCount}`);
+ })).timeout(90000)
+
+ describe('Running searches that should have hits', () => {
+
+ before(async () => { await korap_rc.login(page, KORAP_LOGIN, KORAP_PWD) })
+
+ KORAP_QUERIES.split(/[;,] */).forEach((query, i) => {
+ it('Search for "' + query + '" has hits',
+ (async () => {
+ await korap_rc.assure_glimpse_off(page)
+ const hits = await korap_rc.search(page, query)
+ hits.should.be.above(0)
+ })).timeout(20000)
+ })
+ })
+
+ ifConditionIt('Logout works',
+ KORAP_LOGIN != "",
+ (async () => {
+ const logout_result = await korap_rc.logout(page)
+ logout_result.should.be.true
+ })).timeout(15000)
+ });
+});
\ No newline at end of file