blob: ae1d8a42caa700b6e4a242cce9487dd1ee0e4172 [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 Kupietzbbe6de02026-02-04 09:39:48 +010023const NOTIFY_ON_SUCCESS = process.env.NOTIFY_ON_SUCCESS === 'true' || process.env.NOTIFY_ON_SUCCESS === '1';
Marc Kupietz819a52d2026-03-21 09:41:28 +010024const KORAP_HEADLESS = !(process.env.KORAP_HEADLESS === 'false' || process.env.KORAP_HEADLESS === '0');
Marc Kupietz5a73a4d2022-12-04 14:09:58 +010025const korap_rc = require('../lib/korap_rc.js').new(KORAP_URL)
Marc Kupietz2f17a762025-12-06 11:45:49 +010026const { sendToNextcloudTalk, ifConditionIt } = require('../lib/utils.js');
Marc Kupietz5a73a4d2022-12-04 14:09:58 +010027
Marc Kupietz7f1666a2024-07-12 18:35:31 +020028const slack_webhook = process.env.SLACK_WEBHOOK_URL;
Marc Kupietz4d335a32024-09-04 16:13:48 +020029
Marc Kupietz7f1666a2024-07-12 18:35:31 +020030if (slack_webhook) {
31 slack = require('slack-notify')(slack_webhook);
32}
33
Marc Kupietzd0ee97e2025-12-04 15:46:14 +010034
Marc Kupietzc4077822022-12-03 15:32:40 +010035
Marc Kupietz0f6c54d2022-12-03 15:32:40 +010036describe('Running KorAP UI end-to-end tests on ' + KORAP_URL, () => {
Marc Kupietzc4077822022-12-03 15:32:40 +010037
Marc Kupietz93d7f702025-06-27 15:41:48 +020038 let browser;
39 let page;
40
41
Marc Kupietzc4077822022-12-03 15:32:40 +010042 before(async () => {
Marc Kupietz69e02802023-11-08 14:37:22 +010043 browser = await puppeteer.launch({
Marc Kupietz819a52d2026-03-21 09:41:28 +010044 headless: KORAP_HEADLESS ? "shell" : false,
Marc Kupietzbfb23012025-06-03 15:47:10 +020045 args: [
46 '--no-sandbox',
47 '--disable-setuid-sandbox',
48 '--disable-dev-shm-usage',
49 '--disable-accelerated-2d-canvas',
50 '--no-first-run',
51 '--no-zygote',
52 '--disable-gpu'
53 ]
Marc Kupietz69e02802023-11-08 14:37:22 +010054 })
Marc Kupietzc4077822022-12-03 15:32:40 +010055 page = await browser.newPage()
Marc Kupietz4c5a7a52022-12-04 16:56:30 +010056 await page.setViewport({
Marc Kupietz9e0f5192025-03-09 12:12:16 +010057 width: 1980,
Marc Kupietz4c5a7a52022-12-04 16:56:30 +010058 height: 768,
59 deviceScaleFactor: 1,
Marc Kupietz964e7772025-06-03 15:02:30 +020060 });
Marc Kupietz93d7f702025-06-27 15:41:48 +020061
Marc Kupietzc4077822022-12-03 15:32:40 +010062 })
63
Marc Kupietz93d7f702025-06-27 15:41:48 +020064 after(async function() {
65 if (browser && typeof browser.close === 'function') {
66 await browser.close();
67 }
Marc Kupietzc4077822022-12-03 15:32:40 +010068 })
69
Marc Kupietz4c5a7a52022-12-04 16:56:30 +010070 afterEach(async function () {
Marc Kupietzbbe6de02026-02-04 09:39:48 +010071 const testPassed = this.currentTest.state === "passed";
72 const testFailed = this.currentTest.state === "failed";
73
74 // Determine if we should send notification based on NOTIFY_ON_SUCCESS setting
75 const shouldNotify = NOTIFY_ON_SUCCESS ? testPassed : testFailed;
76
77 if (shouldNotify) {
Marc Kupietz65dea512025-12-04 17:55:57 +010078 // Only take screenshot if it's not one of the initial connectivity/SSL tests
79 const initialTestTitles = [
80 'should be reachable',
81 'should have a valid SSL certificate'
82 ];
83 let screenshotPath = null;
84
Marc Kupietzbbe6de02026-02-04 09:39:48 +010085 // Only take screenshots for failures (not for success notifications)
86 if (testFailed && !initialTestTitles.includes(this.currentTest.title) && page) {
Marc Kupietz65dea512025-12-04 17:55:57 +010087 screenshotPath = "failed_" + this.currentTest.title.replaceAll(/[ &\/]/g, "_") + '.png';
88 await page.screenshot({ path: screenshotPath });
89 }
90
Marc Kupietzbbe6de02026-02-04 09:39:48 +010091 // Prepare notification content based on success/failure
92 const emoji = testPassed ? '✅' : '🚨';
93 const status = testPassed ? 'passed' : 'failed';
94 const color = testPassed ? 'good' : 'danger';
95
Marc Kupietz65dea512025-12-04 17:55:57 +010096 // Send notification to Slack
Marc Kupietz7f1666a2024-07-12 18:35:31 +020097 if (slack) {
Marc Kupietz8f7c2042025-06-24 09:55:03 +020098 try {
Marc Kupietz8f7c2042025-06-24 09:55:03 +020099 slack.alert({
Marc Kupietzbbe6de02026-02-04 09:39:48 +0100100 text: `${emoji} Test on ${KORAP_URL} ${status}: *${this.currentTest.title}*`,
Marc Kupietz8f7c2042025-06-24 09:55:03 +0200101 attachments: [{
Marc Kupietzbbe6de02026-02-04 09:39:48 +0100102 color: color,
Marc Kupietz8f7c2042025-06-24 09:55:03 +0200103 fields: [{
Marc Kupietzbbe6de02026-02-04 09:39:48 +0100104 title: testPassed ? 'Passed Test' : 'Failed Test',
Marc Kupietz8f7c2042025-06-24 09:55:03 +0200105 value: this.currentTest.title,
106 short: false
107 }, {
108 title: 'URL',
109 value: KORAP_URL,
110 short: true
111 }]
112 }]
113 });
114 } catch (slackError) {
115 console.error('Failed to send notification to Slack:', slackError.message);
116 }
117 }
118
Marc Kupietzbbe6de02026-02-04 09:39:48 +0100119 // Upload screenshot to Slack if available (only for failures)
Marc Kupietz65dea512025-12-04 17:55:57 +0100120 if (screenshotPath) {
Marc Kupietz93d7f702025-06-27 15:41:48 +0200121 const slackToken = process.env.SLACK_TOKEN;
122 if (slackToken) {
123 try {
124 const { WebClient } = require('@slack/web-api');
Marc Kupietz65dea512025-12-04 17:55:57 +0100125 const fs = require('fs');
126 const web = new WebClient(slackToken);
Marc Kupietz93d7f702025-06-27 15:41:48 +0200127 const channelId = process.env.SLACK_CHANNEL_ID || 'C07CM4JS48H';
Marc Kupietz8f7c2042025-06-24 09:55:03 +0200128
Marc Kupietz93d7f702025-06-27 15:41:48 +0200129 const result = await web.files.uploadV2({
130 channel_id: channelId,
131 file: fs.createReadStream(screenshotPath),
132 filename: screenshotPath,
133 title: `Screenshot: ${this.currentTest.title}`,
134 initial_comment: `📸 Screenshot of failed test: ${this.currentTest.title} on ${KORAP_URL}`
135 });
Marc Kupietz8f7c2042025-06-24 09:55:03 +0200136
Marc Kupietz93d7f702025-06-27 15:41:48 +0200137 } catch (uploadError) {
138 console.error('Failed to upload screenshot to Slack:', uploadError.message);
139 }
Marc Kupietz8f7c2042025-06-24 09:55:03 +0200140 }
Marc Kupietz7f1666a2024-07-12 18:35:31 +0200141 }
Marc Kupietz65dea512025-12-04 17:55:57 +0100142
Marc Kupietzbbe6de02026-02-04 09:39:48 +0100143 // Send notification to Nextcloud Talk with screenshot (if available)
Marc Kupietz2f17a762025-12-06 11:45:49 +0100144 try {
Marc Kupietzbbe6de02026-02-04 09:39:48 +0100145 const message = `${emoji} Test on ${KORAP_URL} ${status}: **${this.currentTest.title}**`;
Marc Kupietz2f17a762025-12-06 11:45:49 +0100146 await sendToNextcloudTalk(message, false, screenshotPath);
147 } catch (ncError) {
148 console.error('Failed to send notification to Nextcloud Talk:', ncError.message);
Marc Kupietz65dea512025-12-04 17:55:57 +0100149 }
Marc Kupietz4c5a7a52022-12-04 16:56:30 +0100150 }
Marc Kupietz964e7772025-06-03 15:02:30 +0200151 })
Marc Kupietz4c5a7a52022-12-04 16:56:30 +0100152
Marc Kupietz93d7f702025-06-27 15:41:48 +0200153 it('should be reachable', function (done) {
154 let doneCalled = false;
155 const url = new URL(KORAP_URL);
156 const httpModule = url.protocol === 'https:' ? https : require('http');
Marc Kupietz5a73a4d2022-12-04 14:09:58 +0100157
Marc Kupietz93d7f702025-06-27 15:41:48 +0200158 const req = httpModule.request({
159 method: 'HEAD',
160 hostname: url.hostname,
161 port: url.port || (url.protocol === 'https:' ? 443 : 80),
162 path: url.pathname,
163 timeout: 5000
164 }, res => {
165 if (!doneCalled) {
166 doneCalled = true;
167 if (res.statusCode >= 200 && res.statusCode < 400) {
168 done();
169 } else {
170 done(new Error(`Server is not reachable. Status code: ${res.statusCode}`));
171 }
172 }
173 });
174 req.on('timeout', () => {
175 if (!doneCalled) {
176 doneCalled = true;
177 req.destroy();
178 done(new Error('Request to server timed out.'));
179 }
180 });
181 req.on('error', err => {
182 if (!doneCalled) {
183 doneCalled = true;
184 done(err);
185 }
186 });
187 req.end();
188 });
Marc Kupietz5a73a4d2022-12-04 14:09:58 +0100189
Marc Kupietz93d7f702025-06-27 15:41:48 +0200190 it('should have a valid SSL certificate', function (done) {
191 let doneCalled = false;
192 const url = new URL(KORAP_URL);
193 if (url.protocol !== 'https:') {
194 return this.skip();
195 }
196 const req = https.request({
197 method: 'HEAD',
198 hostname: url.hostname,
199 port: url.port || 443,
200 path: url.pathname,
201 timeout: 5000
202 }, res => {
203 if (!doneCalled) {
204 doneCalled = true;
205 const cert = res.socket.getPeerCertificate();
206 if (cert && cert.valid_to) {
207 const validTo = new Date(cert.valid_to);
208 if (validTo > new Date()) {
209 done();
210 } else {
211 done(new Error(`SSL certificate expired on ${validTo.toDateString()}`));
212 }
213 } else if (res.socket.isSessionReused()){
214 done();
215 }
216 else {
217 done(new Error('Could not retrieve SSL certificate information.'));
218 }
219 }
220 });
221 req.on('timeout', () => {
222 if (!doneCalled) {
223 doneCalled = true;
224 req.destroy();
225 done(new Error('Request to server timed out.'));
226 }
227 });
228 req.on('error', err => {
229 if (!doneCalled) {
230 doneCalled = true;
231 if (err.code === 'CERT_HAS_EXPIRED') {
232 done(new Error('SSL certificate has expired.'));
233 } else {
234 done(err);
235 }
236 }
237 });
238 req.end();
239 });
Marc Kupietzc4077822022-12-03 15:32:40 +0100240
Marc Kupietz93d7f702025-06-27 15:41:48 +0200241 describe('UI Tests', function() {
Marc Kupietzc4077822022-12-03 15:32:40 +0100242
Marc Kupietz93d7f702025-06-27 15:41:48 +0200243 before(function() {
244 // Check the state of the parent suite's tests
245 const initialTests = this.test.parent.parent.tests;
246 if (initialTests[0].state === 'failed' || initialTests[1].state === 'failed') {
247 this.skip();
248 }
249 });
Marc Kupietzc4077822022-12-03 15:32:40 +0100250
Marc Kupietz93d7f702025-06-27 15:41:48 +0200251
Marc Kupietzc8ffb2b2025-06-12 16:44:23 +0200252
Marc Kupietz93d7f702025-06-27 15:41:48 +0200253 it('KorAP UI is up and running', async function () {
254 try {
255 await page.goto(KORAP_URL, { waitUntil: 'domcontentloaded' });
256 await page.waitForSelector("#q-field", { visible: true });
257 const query_field = await page.$("#q-field")
258 assert.isNotNull(query_field, "#q-field not found. Kalamar not running?");
259 } catch (error) {
260 throw new Error(`Failed to load KorAP UI or find query field: ${error.message}`);
261 }
Marc Kupietz0f6c54d2022-12-03 15:32:40 +0100262 })
Marc Kupietz5a73a4d2022-12-04 14:09:58 +0100263
Marc Kupietzc4077822022-12-03 15:32:40 +0100264
Marc Kupietz93d7f702025-06-27 15:41:48 +0200265 ifConditionIt('Login into KorAP with incorrect credentials fails',
266 KORAP_LOGIN != "",
267 (async () => {
268 const login_result = await korap_rc.login(page, KORAP_LOGIN, KORAP_PWD + "*")
269 login_result.should.be.false
270 }))
271
272 ifConditionIt('Login into KorAP with correct credentials succeeds',
273 KORAP_LOGIN != "",
274 (async () => {
275 const login_result = await korap_rc.login(page, KORAP_LOGIN, KORAP_PWD)
276 login_result.should.be.true
277 }))
278
279 it('Can turn glimpse off',
280 (async () => {
281 await korap_rc.assure_glimpse_off(page)
282 }))
283
284 it('Corpus statistics show sufficient tokens',
285 (async () => {
286 const tokenCount = await korap_rc.check_corpus_statistics(page, KORAP_MIN_TOKENS_IN_CORPUS);
287 console.log(`Found ${tokenCount} tokens in corpus, minimum required: ${KORAP_MIN_TOKENS_IN_CORPUS}`);
288 tokenCount.should.be.above(KORAP_MIN_TOKENS_IN_CORPUS - 1,
289 `Corpus should have at least ${KORAP_MIN_TOKENS_IN_CORPUS} tokens, but found ${tokenCount}`);
290 })).timeout(90000)
291
292 describe('Running searches that should have hits', () => {
293
294 before(async () => { await korap_rc.login(page, KORAP_LOGIN, KORAP_PWD) })
295
296 KORAP_QUERIES.split(/[;,] */).forEach((query, i) => {
297 it('Search for "' + query + '" has hits',
298 (async () => {
299 await korap_rc.assure_glimpse_off(page)
300 const hits = await korap_rc.search(page, query)
301 hits.should.be.above(0)
302 })).timeout(20000)
303 })
304 })
305
306 ifConditionIt('Logout works',
307 KORAP_LOGIN != "",
308 (async () => {
309 const logout_result = await korap_rc.logout(page)
310 logout_result.should.be.true
311 })).timeout(15000)
312 });
313});