blob: c935d7980d3849d1847062e56003b400029c95f7 [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 Kupietz5a73a4d2022-12-04 14:09:58 +010023const korap_rc = require('../lib/korap_rc.js').new(KORAP_URL)
Marc Kupietz2f17a762025-12-06 11:45:49 +010024const { sendToNextcloudTalk, ifConditionIt } = require('../lib/utils.js');
Marc Kupietz5a73a4d2022-12-04 14:09:58 +010025
Marc Kupietz7f1666a2024-07-12 18:35:31 +020026const slack_webhook = process.env.SLACK_WEBHOOK_URL;
Marc Kupietz4d335a32024-09-04 16:13:48 +020027
Marc Kupietz7f1666a2024-07-12 18:35:31 +020028if (slack_webhook) {
29 slack = require('slack-notify')(slack_webhook);
30}
31
Marc Kupietzd0ee97e2025-12-04 15:46:14 +010032
Marc Kupietzc4077822022-12-03 15:32:40 +010033
Marc Kupietz0f6c54d2022-12-03 15:32:40 +010034describe('Running KorAP UI end-to-end tests on ' + KORAP_URL, () => {
Marc Kupietzc4077822022-12-03 15:32:40 +010035
Marc Kupietz93d7f702025-06-27 15:41:48 +020036 let browser;
37 let page;
38
39
Marc Kupietzc4077822022-12-03 15:32:40 +010040 before(async () => {
Marc Kupietz69e02802023-11-08 14:37:22 +010041 browser = await puppeteer.launch({
Marc Kupietzbfb23012025-06-03 15:47:10 +020042 headless: "shell",
43 args: [
44 '--no-sandbox',
45 '--disable-setuid-sandbox',
46 '--disable-dev-shm-usage',
47 '--disable-accelerated-2d-canvas',
48 '--no-first-run',
49 '--no-zygote',
50 '--disable-gpu'
51 ]
Marc Kupietz69e02802023-11-08 14:37:22 +010052 })
Marc Kupietzc4077822022-12-03 15:32:40 +010053 page = await browser.newPage()
Marc Kupietz4c5a7a52022-12-04 16:56:30 +010054 await page.setViewport({
Marc Kupietz9e0f5192025-03-09 12:12:16 +010055 width: 1980,
Marc Kupietz4c5a7a52022-12-04 16:56:30 +010056 height: 768,
57 deviceScaleFactor: 1,
Marc Kupietz964e7772025-06-03 15:02:30 +020058 });
Marc Kupietz93d7f702025-06-27 15:41:48 +020059
Marc Kupietzc4077822022-12-03 15:32:40 +010060 })
61
Marc Kupietz93d7f702025-06-27 15:41:48 +020062 after(async function() {
63 if (browser && typeof browser.close === 'function') {
64 await browser.close();
65 }
Marc Kupietzc4077822022-12-03 15:32:40 +010066 })
67
Marc Kupietz4c5a7a52022-12-04 16:56:30 +010068 afterEach(async function () {
69 if (this.currentTest.state == "failed") {
Marc Kupietz65dea512025-12-04 17:55:57 +010070 // Only take screenshot if it's not one of the initial connectivity/SSL tests
71 const initialTestTitles = [
72 'should be reachable',
73 'should have a valid SSL certificate'
74 ];
75 let screenshotPath = null;
76
77 if (!initialTestTitles.includes(this.currentTest.title) && page) {
78 screenshotPath = "failed_" + this.currentTest.title.replaceAll(/[ &\/]/g, "_") + '.png';
79 await page.screenshot({ path: screenshotPath });
80 }
81
82 // Send notification to Slack
Marc Kupietz7f1666a2024-07-12 18:35:31 +020083 if (slack) {
Marc Kupietz8f7c2042025-06-24 09:55:03 +020084 try {
Marc Kupietz8f7c2042025-06-24 09:55:03 +020085 slack.alert({
86 text: `🚨 Test on ${KORAP_URL} failed: *${this.currentTest.title}*`,
87 attachments: [{
88 color: 'danger',
89 fields: [{
90 title: 'Failed Test',
91 value: this.currentTest.title,
92 short: false
93 }, {
94 title: 'URL',
95 value: KORAP_URL,
96 short: true
97 }]
98 }]
99 });
100 } catch (slackError) {
101 console.error('Failed to send notification to Slack:', slackError.message);
102 }
103 }
104
Marc Kupietz65dea512025-12-04 17:55:57 +0100105 // Upload screenshot to Slack if available
106 if (screenshotPath) {
Marc Kupietz93d7f702025-06-27 15:41:48 +0200107 const slackToken = process.env.SLACK_TOKEN;
108 if (slackToken) {
109 try {
110 const { WebClient } = require('@slack/web-api');
Marc Kupietz65dea512025-12-04 17:55:57 +0100111 const fs = require('fs');
112 const web = new WebClient(slackToken);
Marc Kupietz93d7f702025-06-27 15:41:48 +0200113 const channelId = process.env.SLACK_CHANNEL_ID || 'C07CM4JS48H';
Marc Kupietz8f7c2042025-06-24 09:55:03 +0200114
Marc Kupietz93d7f702025-06-27 15:41:48 +0200115 const result = await web.files.uploadV2({
116 channel_id: channelId,
117 file: fs.createReadStream(screenshotPath),
118 filename: screenshotPath,
119 title: `Screenshot: ${this.currentTest.title}`,
120 initial_comment: `📸 Screenshot of failed test: ${this.currentTest.title} on ${KORAP_URL}`
121 });
Marc Kupietz8f7c2042025-06-24 09:55:03 +0200122
Marc Kupietz93d7f702025-06-27 15:41:48 +0200123 } catch (uploadError) {
124 console.error('Failed to upload screenshot to Slack:', uploadError.message);
125 }
Marc Kupietz8f7c2042025-06-24 09:55:03 +0200126 }
Marc Kupietz7f1666a2024-07-12 18:35:31 +0200127 }
Marc Kupietz65dea512025-12-04 17:55:57 +0100128
129 // Send notification to Nextcloud Talk with screenshot
Marc Kupietz2f17a762025-12-06 11:45:49 +0100130 try {
131 const message = `🚨 Test on ${KORAP_URL} failed: **${this.currentTest.title}**`;
132 await sendToNextcloudTalk(message, false, screenshotPath);
133 } catch (ncError) {
134 console.error('Failed to send notification to Nextcloud Talk:', ncError.message);
Marc Kupietz65dea512025-12-04 17:55:57 +0100135 }
Marc Kupietz4c5a7a52022-12-04 16:56:30 +0100136 }
Marc Kupietz964e7772025-06-03 15:02:30 +0200137 })
Marc Kupietz4c5a7a52022-12-04 16:56:30 +0100138
Marc Kupietz93d7f702025-06-27 15:41:48 +0200139 it('should be reachable', function (done) {
140 let doneCalled = false;
141 const url = new URL(KORAP_URL);
142 const httpModule = url.protocol === 'https:' ? https : require('http');
Marc Kupietz5a73a4d2022-12-04 14:09:58 +0100143
Marc Kupietz93d7f702025-06-27 15:41:48 +0200144 const req = httpModule.request({
145 method: 'HEAD',
146 hostname: url.hostname,
147 port: url.port || (url.protocol === 'https:' ? 443 : 80),
148 path: url.pathname,
149 timeout: 5000
150 }, res => {
151 if (!doneCalled) {
152 doneCalled = true;
153 if (res.statusCode >= 200 && res.statusCode < 400) {
154 done();
155 } else {
156 done(new Error(`Server is not reachable. Status code: ${res.statusCode}`));
157 }
158 }
159 });
160 req.on('timeout', () => {
161 if (!doneCalled) {
162 doneCalled = true;
163 req.destroy();
164 done(new Error('Request to server timed out.'));
165 }
166 });
167 req.on('error', err => {
168 if (!doneCalled) {
169 doneCalled = true;
170 done(err);
171 }
172 });
173 req.end();
174 });
Marc Kupietz5a73a4d2022-12-04 14:09:58 +0100175
Marc Kupietz93d7f702025-06-27 15:41:48 +0200176 it('should have a valid SSL certificate', function (done) {
177 let doneCalled = false;
178 const url = new URL(KORAP_URL);
179 if (url.protocol !== 'https:') {
180 return this.skip();
181 }
182 const req = https.request({
183 method: 'HEAD',
184 hostname: url.hostname,
185 port: url.port || 443,
186 path: url.pathname,
187 timeout: 5000
188 }, res => {
189 if (!doneCalled) {
190 doneCalled = true;
191 const cert = res.socket.getPeerCertificate();
192 if (cert && cert.valid_to) {
193 const validTo = new Date(cert.valid_to);
194 if (validTo > new Date()) {
195 done();
196 } else {
197 done(new Error(`SSL certificate expired on ${validTo.toDateString()}`));
198 }
199 } else if (res.socket.isSessionReused()){
200 done();
201 }
202 else {
203 done(new Error('Could not retrieve SSL certificate information.'));
204 }
205 }
206 });
207 req.on('timeout', () => {
208 if (!doneCalled) {
209 doneCalled = true;
210 req.destroy();
211 done(new Error('Request to server timed out.'));
212 }
213 });
214 req.on('error', err => {
215 if (!doneCalled) {
216 doneCalled = true;
217 if (err.code === 'CERT_HAS_EXPIRED') {
218 done(new Error('SSL certificate has expired.'));
219 } else {
220 done(err);
221 }
222 }
223 });
224 req.end();
225 });
Marc Kupietzc4077822022-12-03 15:32:40 +0100226
Marc Kupietz93d7f702025-06-27 15:41:48 +0200227 describe('UI Tests', function() {
Marc Kupietzc4077822022-12-03 15:32:40 +0100228
Marc Kupietz93d7f702025-06-27 15:41:48 +0200229 before(function() {
230 // Check the state of the parent suite's tests
231 const initialTests = this.test.parent.parent.tests;
232 if (initialTests[0].state === 'failed' || initialTests[1].state === 'failed') {
233 this.skip();
234 }
235 });
Marc Kupietzc4077822022-12-03 15:32:40 +0100236
Marc Kupietz93d7f702025-06-27 15:41:48 +0200237
Marc Kupietzc8ffb2b2025-06-12 16:44:23 +0200238
Marc Kupietz93d7f702025-06-27 15:41:48 +0200239 it('KorAP UI is up and running', async function () {
240 try {
241 await page.goto(KORAP_URL, { waitUntil: 'domcontentloaded' });
242 await page.waitForSelector("#q-field", { visible: true });
243 const query_field = await page.$("#q-field")
244 assert.isNotNull(query_field, "#q-field not found. Kalamar not running?");
245 } catch (error) {
246 throw new Error(`Failed to load KorAP UI or find query field: ${error.message}`);
247 }
Marc Kupietz0f6c54d2022-12-03 15:32:40 +0100248 })
Marc Kupietz5a73a4d2022-12-04 14:09:58 +0100249
Marc Kupietzc4077822022-12-03 15:32:40 +0100250
Marc Kupietz93d7f702025-06-27 15:41:48 +0200251 ifConditionIt('Login into KorAP with incorrect credentials fails',
252 KORAP_LOGIN != "",
253 (async () => {
254 const login_result = await korap_rc.login(page, KORAP_LOGIN, KORAP_PWD + "*")
255 login_result.should.be.false
256 }))
257
258 ifConditionIt('Login into KorAP with correct credentials succeeds',
259 KORAP_LOGIN != "",
260 (async () => {
261 const login_result = await korap_rc.login(page, KORAP_LOGIN, KORAP_PWD)
262 login_result.should.be.true
263 }))
264
265 it('Can turn glimpse off',
266 (async () => {
267 await korap_rc.assure_glimpse_off(page)
268 }))
269
270 it('Corpus statistics show sufficient tokens',
271 (async () => {
272 const tokenCount = await korap_rc.check_corpus_statistics(page, KORAP_MIN_TOKENS_IN_CORPUS);
273 console.log(`Found ${tokenCount} tokens in corpus, minimum required: ${KORAP_MIN_TOKENS_IN_CORPUS}`);
274 tokenCount.should.be.above(KORAP_MIN_TOKENS_IN_CORPUS - 1,
275 `Corpus should have at least ${KORAP_MIN_TOKENS_IN_CORPUS} tokens, but found ${tokenCount}`);
276 })).timeout(90000)
277
278 describe('Running searches that should have hits', () => {
279
280 before(async () => { await korap_rc.login(page, KORAP_LOGIN, KORAP_PWD) })
281
282 KORAP_QUERIES.split(/[;,] */).forEach((query, i) => {
283 it('Search for "' + query + '" has hits',
284 (async () => {
285 await korap_rc.assure_glimpse_off(page)
286 const hits = await korap_rc.search(page, query)
287 hits.should.be.above(0)
288 })).timeout(20000)
289 })
290 })
291
292 ifConditionIt('Logout works',
293 KORAP_LOGIN != "",
294 (async () => {
295 const logout_result = await korap_rc.logout(page)
296 logout_result.should.be.true
297 })).timeout(15000)
298 });
299});