Send E2E test failures to NC Talk conversion if credentials provided

Change-Id: Ibe20bdb7e57eac536a2e7e7fe12fb2a5df4ed736
diff --git a/Readme.md b/Readme.md
index 8f65727..26c26f8 100644
--- a/Readme.md
+++ b/Readme.md
@@ -34,6 +34,9 @@
 | `SLACK_WEBHOOK_URL` | _(none)_ | Slack webhook URL for test failure notifications (text only) |
 | `SLACK_TOKEN` | _(none)_ | Slack bot token for uploading failure screenshots |
 | `SLACK_CHANNEL_ID` | `C07CM4JS48H` | Slack channel ID for screenshot uploads (e.g., `C1234567890`) |
+| `NC_TALK_URL` | `https://cloud.ids-mannheim.de` | Nextcloud instance URL for Talk notifications |
+| `NC_TALK_CONVERSATION` | _(none)_ | Nextcloud Talk conversation token for notifications |
+| `NC_TALK_SECRET` | _(none)_ | Nextcloud Talk bot secret for authentication |
 | `LC_ALL` | _(system default)_ | Locale setting (recommended: `C` for consistent results) |
 
 ### Usage Notes
@@ -54,8 +57,30 @@
 
 ### Notifications
 
+#### Slack Notifications
+
 If you run KorAP-E2E-tests as a cronjob or in scheduled pipelines and
-want to get notified about failed tests via slack, set the environment variable `SLACK_WEBHOOK_URL` to the URL of your [slack webhook](https://api.slack.com/messaging/webhooks).
+want to get notified about failed tests via Slack, set the environment variable `SLACK_WEBHOOK_URL` to the URL of your [Slack webhook](https://api.slack.com/messaging/webhooks).
+
+For screenshot uploads, also set:
+
+- `SLACK_TOKEN`: Your Slack bot token (starts with `xoxb-`)
+- `SLACK_CHANNEL_ID`: The channel ID where screenshots should be posted (e.g., `C07CM4JS48H`)
+
+**Note**: The bot must be invited to the channel, and needs the following permissions:
+
+- `chat:write`
+- `files:write`
+
+#### Nextcloud Talk Notifications
+
+To receive test failure notifications in Nextcloud Talk, set these environment variables:
+
+- `NC_TALK_URL`: Your Nextcloud instance URL (default: `https://cloud.ids-mannheim.de`)
+- `NC_TALK_CONVERSATION`: The conversation/room token (e.g., `o6toyqx7`)
+- `NC_TALK_SECRET`: The bot secret for authentication
+
+The bot must be configured in your Nextcloud Talk settings with the appropriate secret.
 
 
 ## Development and License
diff --git a/package-lock.json b/package-lock.json
index 6382330..06decd6 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -10,6 +10,7 @@
       "license": "APACHE-2.0",
       "dependencies": {
         "@slack/web-api": "^7.12.0",
+        "axios": "^1.7.9",
         "chai": "^6.2.0",
         "form-data": "^4.0.5",
         "mocha": "^11.7.4",
@@ -742,8 +743,7 @@
       "version": "0.0.1534754",
       "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1534754.tgz",
       "integrity": "sha512-26T91cV5dbOYnXdJi5qQHoTtUoNEqwkHcAyu/IKtjIAxiEqPMrDiRkDOPWVsGfNZGmlQVHQbZRSjD8sxagWVsQ==",
-      "license": "BSD-3-Clause",
-      "peer": true
+      "license": "BSD-3-Clause"
     },
     "node_modules/diff": {
       "version": "7.0.0",
@@ -1691,7 +1691,6 @@
       "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.7.4.tgz",
       "integrity": "sha512-1jYAaY8x0kAZ0XszLWu14pzsf4KV740Gld4HXkhNTXwcHx4AUEDkPzgEHg9CM5dVcW+zv036tjpsEbLraPJj4w==",
       "license": "MIT",
-      "peer": true,
       "dependencies": {
         "browser-stdout": "^1.3.1",
         "chokidar": "^4.0.1",
@@ -2028,7 +2027,6 @@
       "integrity": "sha512-exyxHPV5DSsigIhM/pzLcyzl5XU4Dp5lNP+APwIeStDxAdYqpMnJ1qN0QHXghjJx+cQJczby+ySH5rgv/5GQLw==",
       "hasInstallScript": true,
       "license": "Apache-2.0",
-      "peer": true,
       "dependencies": {
         "@puppeteer/browsers": "2.11.0",
         "chromium-bidi": "11.0.0",
@@ -2067,7 +2065,6 @@
       "resolved": "https://registry.npmjs.org/puppeteer-extra/-/puppeteer-extra-3.3.6.tgz",
       "integrity": "sha512-rsLBE/6mMxAjlLd06LuGacrukP2bqbzKCLzV1vrhHFavqQE/taQ2UXv3H5P0Ls7nsrASa+6x3bDbXHpqMwq+7A==",
       "license": "MIT",
-      "peer": true,
       "dependencies": {
         "@types/debug": "^4.1.0",
         "debug": "^4.1.1",
diff --git a/package.json b/package.json
index e50a175..cc04403 100644
--- a/package.json
+++ b/package.json
@@ -19,6 +19,7 @@
   "license": "APACHE-2.0",
   "dependencies": {
     "@slack/web-api": "^7.12.0",
+    "axios": "^1.7.9",
     "chai": "^6.2.0",
     "form-data": "^4.0.5",
     "mocha": "^11.7.4",
diff --git a/test/korap-ui.js b/test/korap-ui.js
index fcf1fcd..9fc736c 100644
--- a/test/korap-ui.js
+++ b/test/korap-ui.js
@@ -1,4 +1,5 @@
 const https = require('https');
+const crypto = require('crypto');
 const puppeteer = require('puppeteer-extra');
 puppeteer.use(require('puppeteer-extra-plugin-user-preferences')({
     userPrefs: {
@@ -22,11 +23,53 @@
 const korap_rc = require('../lib/korap_rc.js').new(KORAP_URL)
 
 const slack_webhook = process.env.SLACK_WEBHOOK_URL;
+const nc_talk_url = process.env.NC_TALK_URL || 'https://cloud.ids-mannheim.de';
+const nc_talk_conversation = process.env.NC_TALK_CONVERSATION;
+const nc_talk_secret = process.env.NC_TALK_SECRET;
 
 if (slack_webhook) {
     slack = require('slack-notify')(slack_webhook);
 }
 
+// Function to send message to Nextcloud Talk
+async function sendToNextcloudTalk(message, silent = false) {
+    if (!nc_talk_conversation || !nc_talk_secret) {
+        return;
+    }
+
+    try {
+        const axios = require('axios');
+        
+        // Generate random header and signature
+        const randomHeader = crypto.randomBytes(32).toString('hex');
+        const messageToSign = randomHeader + message;
+        const signature = crypto.createHmac('sha256', nc_talk_secret)
+            .update(messageToSign)
+            .digest('hex');
+
+        // Send the message
+        await axios.post(
+            `${nc_talk_url}/ocs/v2.php/apps/spreed/api/v1/bot/${nc_talk_conversation}/message`,
+            {
+                message: message,
+                silent: silent
+            },
+            {
+                headers: {
+                    'Content-Type': 'application/json',
+                    'Accept': 'application/json',
+                    'OCS-APIRequest': 'true',
+                    'X-Nextcloud-Talk-Bot-Random': randomHeader,
+                    'X-Nextcloud-Talk-Bot-Signature': signature
+                }
+            }
+        );
+        console.log('Message sent to Nextcloud Talk successfully');
+    } catch (error) {
+        console.error('Failed to send message to Nextcloud Talk:', error.message);
+    }
+}
+
 function ifConditionIt(title, condition, test) {
     return condition ? it(title, test) : it.skip(title + " (skipped)", test)
 }
@@ -89,6 +132,16 @@
                 }
             }
 
+            // Send notification to Nextcloud Talk
+            if (nc_talk_conversation && nc_talk_secret) {
+                try {
+                    const message = `🚨 Test on ${KORAP_URL} failed: **${this.currentTest.title}**`;
+                    await sendToNextcloudTalk(message);
+                } catch (ncError) {
+                    console.error('Failed to send notification to Nextcloud Talk:', ncError.message);
+                }
+            }
+
             // Only take screenshot if it's not one of the initial connectivity/SSL tests
             const initialTestTitles = [
                 'should be reachable',