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