blob: bec14bd43848960fb696404a46357bb9a147cbb2 [file] [log] [blame]
Marc Kupietz93d7f702025-06-27 15:41:48 +02001const https = require('https');
Marc Kupietz2f17a762025-12-06 11:45:49 +01002
Marc Kupietz490b0532024-09-05 09:36:21 +02003const puppeteer = require('puppeteer-extra');
4puppeteer.use(require('puppeteer-extra-plugin-user-preferences')({
5 userPrefs: {
6 safebrowsing: {
7 enabled: false,
8 enhanced: false
9 }
10 }
11}));
Marc Kupietz55fc3162022-12-04 16:25:49 +010012const chai = require('chai');
Marc Kupietz4c5a7a52022-12-04 16:56:30 +010013const { afterEach } = require('mocha');
Marc Kupietz55fc3162022-12-04 16:25:49 +010014const assert = chai.assert;
15const should = chai.should();
Marc Kupietz7f1666a2024-07-12 18:35:31 +020016var slack = null;
Marc Kupietz55fc3162022-12-04 16:25:49 +010017
Marc Kupietz0f6c54d2022-12-03 15:32:40 +010018const KORAP_URL = process.env.KORAP_URL || "http://localhost:64543";
Marc Kupietzbfb23012025-06-03 15:47:10 +020019const KORAP_LOGIN = 'KORAP_USERNAME' in process.env ? process.env.KORAP_USERNAME : 'KORAP_LOGIN' in process.env ? process.env.KORAP_LOGIN : "user2"
20const KORAP_PWD = process.env.KORAP_PWD || process.env.KORAP_PASSWORD || "password2";
Marc Kupietz26982382022-12-04 19:02:57 +010021const KORAP_QUERIES = process.env.KORAP_QUERIES || 'geht, [orth=geht & cmc/pos=VVFIN]'
Marc Kupietzc8ffb2b2025-06-12 16:44:23 +020022const KORAP_MIN_TOKENS_IN_CORPUS = parseInt(process.env.KORAP_MIN_TOKENS_IN_CORPUS || "100000", 10);
Marc Kupietzf0079692026-06-14 20:25:54 +020023const KORAP_SEARCH_TIMEOUT = parseInt(process.env.KORAP_SEARCH_TIMEOUT || "60000", 10);
Marc Kupietzbbe6de02026-02-04 09:39:48 +010024const NOTIFY_ON_SUCCESS = process.env.NOTIFY_ON_SUCCESS === 'true' || process.env.NOTIFY_ON_SUCCESS === '1';
Marc Kupietz819a52d2026-03-21 09:41:28 +010025const KORAP_HEADLESS = !(process.env.KORAP_HEADLESS === 'false' || process.env.KORAP_HEADLESS === '0');
Marc Kupietz5a73a4d2022-12-04 14:09:58 +010026const korap_rc = require('../lib/korap_rc.js').new(KORAP_URL)
Marc Kupietz2f17a762025-12-06 11:45:49 +010027const { sendToNextcloudTalk, ifConditionIt } = require('../lib/utils.js');
Marc Kupietz5a73a4d2022-12-04 14:09:58 +010028
Marc Kupietz7f1666a2024-07-12 18:35:31 +020029const slack_webhook = process.env.SLACK_WEBHOOK_URL;
Marc Kupietz4d335a32024-09-04 16:13:48 +020030
Marc Kupietz7f1666a2024-07-12 18:35:31 +020031if (slack_webhook) {
32 slack = require('slack-notify')(slack_webhook);
33}
34
Marc Kupietzd0ee97e2025-12-04 15:46:14 +010035
Marc Kupietzc4077822022-12-03 15:32:40 +010036
Marc Kupietz0f6c54d2022-12-03 15:32:40 +010037describe('Running KorAP UI end-to-end tests on ' + KORAP_URL, () => {
Marc Kupietzc4077822022-12-03 15:32:40 +010038
Marc Kupietz93d7f702025-06-27 15:41:48 +020039 let browser;
40 let page;
41
42
Marc Kupietzc4077822022-12-03 15:32:40 +010043 before(async () => {
Marc Kupietzb4d62a82026-06-04 14:52:28 +020044 try {
45 browser = await puppeteer.launch({
46 headless: KORAP_HEADLESS ? "shell" : false,
47 args: [
48 '--no-sandbox',
49 '--disable-setuid-sandbox',
50 '--disable-dev-shm-usage',
51 '--disable-accelerated-2d-canvas',
52 '--no-first-run',
53 '--no-zygote',
54 '--disable-gpu'
55 ]
56 })
57 page = await browser.newPage()
58 await page.setViewport({
59 width: 1980,
60 height: 768,
61 deviceScaleFactor: 1,
62 });
63 } catch (error) {
64 console.error('Failed to initialize Puppeteer browser:', error.message);
65
66 // Send failure notification
67 const emoji = '🚨';
68 const message = `${emoji} Test setup on ${KORAP_URL} failed: **${error.message}**`;
69
70 // Send notification to Slack
71 if (slack) {
72 try {
73 slack.alert({
74 text: `${emoji} Test setup on ${KORAP_URL} failed`,
75 attachments: [{
76 color: 'danger',
77 fields: [{
78 title: 'Error Details',
79 value: error.message,
80 short: false
81 }, {
82 title: 'URL',
83 value: KORAP_URL,
84 short: true
85 }]
86 }]
87 });
88 } catch (slackError) {
89 console.error('Failed to send setup error to Slack:', slackError.message);
90 }
91 }
92
93 // Send notification to Nextcloud Talk
94 try {
95 await sendToNextcloudTalk(message, false);
96 } catch (ncError) {
97 console.error('Failed to send setup error to Nextcloud Talk:', ncError.message);
98 }
99
100 // Rethrow the error so that the test run is registered as failed
101 throw error;
102 }
Marc Kupietzc4077822022-12-03 15:32:40 +0100103 })
104
Marc Kupietz93d7f702025-06-27 15:41:48 +0200105 after(async function() {
106 if (browser && typeof browser.close === 'function') {
107 await browser.close();
108 }
Marc Kupietzc4077822022-12-03 15:32:40 +0100109 })
110
Marc Kupietz4c5a7a52022-12-04 16:56:30 +0100111 afterEach(async function () {
Marc Kupietzbbe6de02026-02-04 09:39:48 +0100112 const testPassed = this.currentTest.state === "passed";
113 const testFailed = this.currentTest.state === "failed";
114
115 // Determine if we should send notification based on NOTIFY_ON_SUCCESS setting
116 const shouldNotify = NOTIFY_ON_SUCCESS ? testPassed : testFailed;
117
118 if (shouldNotify) {
Marc Kupietz65dea512025-12-04 17:55:57 +0100119 // Only take screenshot if it's not one of the initial connectivity/SSL tests
120 const initialTestTitles = [
121 'should be reachable',
122 'should have a valid SSL certificate'
123 ];
124 let screenshotPath = null;
125
Marc Kupietzbbe6de02026-02-04 09:39:48 +0100126 // Only take screenshots for failures (not for success notifications)
127 if (testFailed && !initialTestTitles.includes(this.currentTest.title) && page) {
Marc Kupietz65dea512025-12-04 17:55:57 +0100128 screenshotPath = "failed_" + this.currentTest.title.replaceAll(/[ &\/]/g, "_") + '.png';
129 await page.screenshot({ path: screenshotPath });
130 }
131
Marc Kupietzbbe6de02026-02-04 09:39:48 +0100132 // Prepare notification content based on success/failure
133 const emoji = testPassed ? '✅' : '🚨';
134 const status = testPassed ? 'passed' : 'failed';
135 const color = testPassed ? 'good' : 'danger';
136
Marc Kupietz65dea512025-12-04 17:55:57 +0100137 // Send notification to Slack
Marc Kupietz7f1666a2024-07-12 18:35:31 +0200138 if (slack) {
Marc Kupietz8f7c2042025-06-24 09:55:03 +0200139 try {
Marc Kupietz8f7c2042025-06-24 09:55:03 +0200140 slack.alert({
Marc Kupietzbbe6de02026-02-04 09:39:48 +0100141 text: `${emoji} Test on ${KORAP_URL} ${status}: *${this.currentTest.title}*`,
Marc Kupietz8f7c2042025-06-24 09:55:03 +0200142 attachments: [{
Marc Kupietzbbe6de02026-02-04 09:39:48 +0100143 color: color,
Marc Kupietz8f7c2042025-06-24 09:55:03 +0200144 fields: [{
Marc Kupietzbbe6de02026-02-04 09:39:48 +0100145 title: testPassed ? 'Passed Test' : 'Failed Test',
Marc Kupietz8f7c2042025-06-24 09:55:03 +0200146 value: this.currentTest.title,
147 short: false
148 }, {
149 title: 'URL',
150 value: KORAP_URL,
151 short: true
152 }]
153 }]
154 });
155 } catch (slackError) {
156 console.error('Failed to send notification to Slack:', slackError.message);
157 }
158 }
159
Marc Kupietzbbe6de02026-02-04 09:39:48 +0100160 // Upload screenshot to Slack if available (only for failures)
Marc Kupietz65dea512025-12-04 17:55:57 +0100161 if (screenshotPath) {
Marc Kupietz93d7f702025-06-27 15:41:48 +0200162 const slackToken = process.env.SLACK_TOKEN;
163 if (slackToken) {
164 try {
165 const { WebClient } = require('@slack/web-api');
Marc Kupietz65dea512025-12-04 17:55:57 +0100166 const fs = require('fs');
167 const web = new WebClient(slackToken);
Marc Kupietz93d7f702025-06-27 15:41:48 +0200168 const channelId = process.env.SLACK_CHANNEL_ID || 'C07CM4JS48H';
Marc Kupietz8f7c2042025-06-24 09:55:03 +0200169
Marc Kupietz93d7f702025-06-27 15:41:48 +0200170 const result = await web.files.uploadV2({
171 channel_id: channelId,
172 file: fs.createReadStream(screenshotPath),
173 filename: screenshotPath,
174 title: `Screenshot: ${this.currentTest.title}`,
175 initial_comment: `📸 Screenshot of failed test: ${this.currentTest.title} on ${KORAP_URL}`
176 });
Marc Kupietz8f7c2042025-06-24 09:55:03 +0200177
Marc Kupietz93d7f702025-06-27 15:41:48 +0200178 } catch (uploadError) {
179 console.error('Failed to upload screenshot to Slack:', uploadError.message);
180 }
Marc Kupietz8f7c2042025-06-24 09:55:03 +0200181 }
Marc Kupietz7f1666a2024-07-12 18:35:31 +0200182 }
Marc Kupietz65dea512025-12-04 17:55:57 +0100183
Marc Kupietzbbe6de02026-02-04 09:39:48 +0100184 // Send notification to Nextcloud Talk with screenshot (if available)
Marc Kupietz2f17a762025-12-06 11:45:49 +0100185 try {
Marc Kupietzbbe6de02026-02-04 09:39:48 +0100186 const message = `${emoji} Test on ${KORAP_URL} ${status}: **${this.currentTest.title}**`;
Marc Kupietz2f17a762025-12-06 11:45:49 +0100187 await sendToNextcloudTalk(message, false, screenshotPath);
188 } catch (ncError) {
189 console.error('Failed to send notification to Nextcloud Talk:', ncError.message);
Marc Kupietz65dea512025-12-04 17:55:57 +0100190 }
Marc Kupietz4c5a7a52022-12-04 16:56:30 +0100191 }
Marc Kupietz964e7772025-06-03 15:02:30 +0200192 })
Marc Kupietz4c5a7a52022-12-04 16:56:30 +0100193
Marc Kupietz93d7f702025-06-27 15:41:48 +0200194 it('should be reachable', function (done) {
195 let doneCalled = false;
196 const url = new URL(KORAP_URL);
197 const httpModule = url.protocol === 'https:' ? https : require('http');
Marc Kupietz5a73a4d2022-12-04 14:09:58 +0100198
Marc Kupietz93d7f702025-06-27 15:41:48 +0200199 const req = httpModule.request({
200 method: 'HEAD',
201 hostname: url.hostname,
202 port: url.port || (url.protocol === 'https:' ? 443 : 80),
203 path: url.pathname,
204 timeout: 5000
205 }, res => {
206 if (!doneCalled) {
207 doneCalled = true;
208 if (res.statusCode >= 200 && res.statusCode < 400) {
209 done();
210 } else {
211 done(new Error(`Server is not reachable. Status code: ${res.statusCode}`));
212 }
213 }
214 });
215 req.on('timeout', () => {
216 if (!doneCalled) {
217 doneCalled = true;
218 req.destroy();
219 done(new Error('Request to server timed out.'));
220 }
221 });
222 req.on('error', err => {
223 if (!doneCalled) {
224 doneCalled = true;
225 done(err);
226 }
227 });
228 req.end();
229 });
Marc Kupietz5a73a4d2022-12-04 14:09:58 +0100230
Marc Kupietz93d7f702025-06-27 15:41:48 +0200231 it('should have a valid SSL certificate', function (done) {
232 let doneCalled = false;
233 const url = new URL(KORAP_URL);
234 if (url.protocol !== 'https:') {
235 return this.skip();
236 }
237 const req = https.request({
238 method: 'HEAD',
239 hostname: url.hostname,
240 port: url.port || 443,
241 path: url.pathname,
242 timeout: 5000
243 }, res => {
244 if (!doneCalled) {
245 doneCalled = true;
246 const cert = res.socket.getPeerCertificate();
247 if (cert && cert.valid_to) {
248 const validTo = new Date(cert.valid_to);
249 if (validTo > new Date()) {
250 done();
251 } else {
252 done(new Error(`SSL certificate expired on ${validTo.toDateString()}`));
253 }
254 } else if (res.socket.isSessionReused()){
255 done();
256 }
257 else {
258 done(new Error('Could not retrieve SSL certificate information.'));
259 }
260 }
261 });
262 req.on('timeout', () => {
263 if (!doneCalled) {
264 doneCalled = true;
265 req.destroy();
266 done(new Error('Request to server timed out.'));
267 }
268 });
269 req.on('error', err => {
270 if (!doneCalled) {
271 doneCalled = true;
272 if (err.code === 'CERT_HAS_EXPIRED') {
273 done(new Error('SSL certificate has expired.'));
274 } else {
275 done(err);
276 }
277 }
278 });
279 req.end();
280 });
Marc Kupietzc4077822022-12-03 15:32:40 +0100281
Marc Kupietz93d7f702025-06-27 15:41:48 +0200282 describe('UI Tests', function() {
Marc Kupietzc4077822022-12-03 15:32:40 +0100283
Marc Kupietz93d7f702025-06-27 15:41:48 +0200284 before(function() {
285 // Check the state of the parent suite's tests
286 const initialTests = this.test.parent.parent.tests;
287 if (initialTests[0].state === 'failed' || initialTests[1].state === 'failed') {
288 this.skip();
289 }
290 });
Marc Kupietzc4077822022-12-03 15:32:40 +0100291
Marc Kupietz93d7f702025-06-27 15:41:48 +0200292
Marc Kupietzc8ffb2b2025-06-12 16:44:23 +0200293
Marc Kupietz93d7f702025-06-27 15:41:48 +0200294 it('KorAP UI is up and running', async function () {
295 try {
296 await page.goto(KORAP_URL, { waitUntil: 'domcontentloaded' });
297 await page.waitForSelector("#q-field", { visible: true });
298 const query_field = await page.$("#q-field")
299 assert.isNotNull(query_field, "#q-field not found. Kalamar not running?");
300 } catch (error) {
301 throw new Error(`Failed to load KorAP UI or find query field: ${error.message}`);
302 }
Marc Kupietz0f6c54d2022-12-03 15:32:40 +0100303 })
Marc Kupietz5a73a4d2022-12-04 14:09:58 +0100304
Marc Kupietzc4077822022-12-03 15:32:40 +0100305
Marc Kupietz93d7f702025-06-27 15:41:48 +0200306 ifConditionIt('Login into KorAP with incorrect credentials fails',
307 KORAP_LOGIN != "",
308 (async () => {
309 const login_result = await korap_rc.login(page, KORAP_LOGIN, KORAP_PWD + "*")
310 login_result.should.be.false
311 }))
312
313 ifConditionIt('Login into KorAP with correct credentials succeeds',
314 KORAP_LOGIN != "",
315 (async () => {
316 const login_result = await korap_rc.login(page, KORAP_LOGIN, KORAP_PWD)
317 login_result.should.be.true
318 }))
319
320 it('Can turn glimpse off',
321 (async () => {
322 await korap_rc.assure_glimpse_off(page)
323 }))
324
325 it('Corpus statistics show sufficient tokens',
326 (async () => {
327 const tokenCount = await korap_rc.check_corpus_statistics(page, KORAP_MIN_TOKENS_IN_CORPUS);
328 console.log(`Found ${tokenCount} tokens in corpus, minimum required: ${KORAP_MIN_TOKENS_IN_CORPUS}`);
329 tokenCount.should.be.above(KORAP_MIN_TOKENS_IN_CORPUS - 1,
330 `Corpus should have at least ${KORAP_MIN_TOKENS_IN_CORPUS} tokens, but found ${tokenCount}`);
331 })).timeout(90000)
332
333 describe('Running searches that should have hits', () => {
334
335 before(async () => { await korap_rc.login(page, KORAP_LOGIN, KORAP_PWD) })
336
337 KORAP_QUERIES.split(/[;,] */).forEach((query, i) => {
338 it('Search for "' + query + '" has hits',
339 (async () => {
340 await korap_rc.assure_glimpse_off(page)
341 const hits = await korap_rc.search(page, query)
342 hits.should.be.above(0)
Marc Kupietzf0079692026-06-14 20:25:54 +0200343 })).timeout(KORAP_SEARCH_TIMEOUT)
Marc Kupietz93d7f702025-06-27 15:41:48 +0200344 })
345 })
346
347 ifConditionIt('Logout works',
348 KORAP_LOGIN != "",
349 (async () => {
350 const logout_result = await korap_rc.logout(page)
351 logout_result.should.be.true
352 })).timeout(15000)
353 });
354});