test/monitor.js hinzugefügt

This commit is contained in:
2025-04-20 01:20:37 +02:00
parent 25fe7f2db7
commit 035b08eb1d

259
test/monitor.js Normal file
View File

@@ -0,0 +1,259 @@
const { chromium } = require('playwright');
const fs = require('fs');
const path = require('path');
const dotenv = require('dotenv');
const speakeasy = require('speakeasy');
const { URL } = require('url');
const http = require('http');
const https = require('https');
const nodemailer = require('nodemailer');
// Lade .env-Variablen
dotenv.config();
const {
USERNAME,
PASSWORD,
TESTNAME,
TOTP_SECRET,
LOGIN_URL = 'about:privatebrowsing',
LOGOUT_URL = 'about:privatebrowsing',
SUCCESS_TEXT = "",
PUSHGATEWAY_URL
} = process.env;
// E-Mail bei Fehlschlag senden
async function sendFailureMail(subject, text, attachments) {
const transporter = nodemailer.createTransport({
host: process.env.SMTP_HOST,
port: parseInt(process.env.SMTP_PORT, 10),
secure: false, // kein TLS
tls: { rejectUnauthorized: false }
});
await transporter.sendMail({
from: process.env.MAIL_FROM,
to: process.env.MAIL_TO,
subject,
text,
attachments
});
}
// Prüfe notwendige ENV-Variablen
if (!USERNAME || !PASSWORD || !TOTP_SECRET) {
console.error('❌ Bitte .env mit USERNAME, PASSWORD und TOTP_SECRET konfigurieren');
process.exit(2);
}
if (!PUSHGATEWAY_URL) {
console.error('❌ PUSHGATEWAY_URL nicht gesetzt Schritte werden nicht an PushGateway gesendet');
}
(async () => {
const resultDir = '/app/test-results';
const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext({});
const page = await context.newPage();
const stepsData = [];
const overallStartTime = Date.now();
// Hilfsfunktion: Sende Metriktext an PushGateway (falls URL konfiguriert)
async function sendMetrics(lines) {
if (!PUSHGATEWAY_URL) return;
const pushUrl = new URL(PUSHGATEWAY_URL);
const protocolLib = pushUrl.protocol === 'https:' ? https : http;
const options = {
hostname: pushUrl.hostname,
path: pushUrl.pathname + pushUrl.search,
method: 'POST',
headers: { 'Content-Type': 'text/plain' }
};
if (pushUrl.port) {
options.port = pushUrl.port;
}
await new Promise((resolve, reject) => {
const req = protocolLib.request(options, res => {
res.on('error', reject);
res.on('data', () => {}); // Antwortinhalt ignorieren
res.on('end', () => {
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
resolve();
} else {
reject(new Error(`PushGateway HTTP-Status: ${res.statusCode}`));
}
});
});
req.on('error', reject);
req.write(lines.join('\n') + '\n');
req.end();
});
}
// Hilfsfunktion: Führe einen Schritt aus und erfasse Metriken
async function doStep(stepNumber, stepName, stepFunction) {
const stepStart = Date.now();
let success = true;
let errorMessage = "";
try {
await stepFunction();
} catch (err) {
success = false;
errorMessage = err.message;
}
const stepEnd = Date.now();
const duration = stepEnd - stepStart;
const timeSinceStart = stepEnd - overallStartTime;
const stepMetrics = { stepNumber, stepName, success, errorMessage, duration, timeSinceStart };
// Ausgabe des Schritts als JSON-Log
stepsData.push(stepMetrics);
console.log(JSON.stringify(stepMetrics));
// Vorbereitung des Metrik-Eintrags
const safe = (val) => ("" + val).replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n');
const baseLabels = `step="${stepNumber}", step_name="${safe(stepName)}"${TESTNAME ? `, test="${safe(TESTNAME)}"` : ""}`;
const metricLines = [];
// Erfolg (Gauge 0/1, Fehlertext als Label falls vorhanden)
if (errorMessage) {
metricLines.push(`monitor_step_success{${baseLabels}, error="${safe(errorMessage)}"} ${success ? 1 : 0}`);
} else {
metricLines.push(`monitor_step_success{${baseLabels}} ${success ? 1 : 0}`);
}
// Dauer und Zeit seit Start (ms)
metricLines.push(`monitor_step_duration_ms{${baseLabels}} ${duration}`);
metricLines.push(`monitor_step_elapsed_ms{${baseLabels}} ${timeSinceStart}`);
try {
await sendMetrics(metricLines);
} catch (pushErr) {
console.error(`❌ Fehler beim Senden der Metriken für Schritt ${stepNumber}:`, pushErr.message);
}
if (!success) {
// Bei Fehler Abbruch auslösen
throw new Error(errorMessage || `Step ${stepNumber} failed`);
}
}
try {
// Schritt 1: Login-Seite aufrufen und zum eIAM-Login gelangen
console.log(`${TESTNAME}: 1 Login aufrufen`);
await doStep(1, 'Login aufrufen', async () => {
await page.goto(LOGIN_URL);
await page.setViewportSize({ width: 1920, height: 937 });
await Promise.all([
page.click('span:nth-child(2)'),
page.waitForNavigation()
]);
await page.screenshot({ path: `${resultDir}/eiamselect-${timestamp}.png`, fullPage: true });
await Promise.all([
page.click('#social-eIAM'),
page.waitForNavigation()
]);
});
// Schritt 2: Benutzername eingeben
console.log('✅ Login eIAM Benutzername eintragen');
await doStep(2, 'Benutzernamen eingeben', async () => {
await page.click('#isiwebuserid');
await page.fill('#isiwebuserid', USERNAME);
await page.screenshot({ path: `${resultDir}/userid-${timestamp}.png`, fullPage: true });
await Promise.all([
page.press('#isiwebuserid', 'Enter'),
page.waitForNavigation()
]);
});
// Schritt 3: Passwort eingeben
console.log('✅ Login eIAM Passwort eintragen');
await doStep(3, 'Passwort eingeben', async () => {
await page.click('#isiwebpasswd');
await page.fill('#isiwebpasswd', PASSWORD);
await page.screenshot({ path: `${resultDir}/pw-${timestamp}.png`, fullPage: true });
await Promise.all([
page.press('#isiwebpasswd', 'Enter'),
page.waitForNavigation()
]);
});
// Schritt 4: TOTP eingeben
console.log('✅ Login eIAM TOTP generieren');
await doStep(4, 'TOTP eingeben', async () => {
const totp = speakeasy.totp({ secret: TOTP_SECRET, encoding: 'base32' });
console.log(`✅ Login eIAM TOTP Code: ${totp}`);
console.log('✅ Login eIAM TOTP eintragen');
await page.fill('#code2FA', totp);
await page.screenshot({ path: `${resultDir}/totp-${timestamp}.png`, fullPage: true });
await Promise.all([
page.press('#code2FA', 'Enter'),
page.waitForNavigation()
]);
});
// Schritt 5: Login-Erfolg prüfen (Benutzername sichtbar?)
console.log('✅ WhoAmI Seite laden und prüfen');
await doStep(5, 'Login-Erfolg prüfen', async () => {
await page.waitForSelector(`text=${SUCCESS_TEXT}`, { timeout: 10000 });
await page.screenshot({ path: `${resultDir}/result-${timestamp}.png`, fullPage: true });
console.log(` Suche nach Benutzername: ${SUCCESS_TEXT}`);
const pageText = await page.textContent('body');
fs.writeFileSync(path.join(resultDir, `final-${timestamp}.html`), await page.content());
fs.writeFileSync(path.join(resultDir, `final-${timestamp}.txt`), pageText);
if (!pageText.includes(SUCCESS_TEXT)) {
throw new Error('Benutzername nicht gefunden Login möglicherweise fehlerhaft');
}
console.log('✅ Login erfolgreich Benutzername sichtbar');
});
// Schritt 6: Logout durchführen
console.log('✅ Logout');
await doStep(6, 'Logout', async () => {
await page.goto(LOGOUT_URL);
await Promise.all([
page.click('#kc-logout'),
page.waitForNavigation()
]);
await page.screenshot({ path: `${resultDir}/logout-${timestamp}.png`, fullPage: true });
console.log('✅ Logout erfolgreich');
});
console.log('✅ Alle Schritte erfolgreich abgeschlossen');
// Schreibe alle Schritt-Metriken in die Datei
fs.writeFileSync(path.join(resultDir, 'step-metrics.json'), JSON.stringify(stepsData, null, 2));
} catch (err) {
console.error('❌ Fehler beim Login:', err.message);
// Fehlerbehandlung und Ergebnisse sichern
const screenshotPath = path.join(resultDir, `error_${timestamp}.png`);
const htmlPath = path.join(resultDir, `page_${timestamp}.html`);
const textPath = path.join(resultDir, `page-${timestamp}.txt`);
await page.screenshot({ path: screenshotPath });
const pageText = await page.textContent('body');
fs.writeFileSync(htmlPath, await page.content());
fs.writeFileSync(textPath, pageText);
const captchaDetected = await page.evaluate(() => {
return document.body.innerText.toLowerCase().includes('captcha') ||
document.querySelector('input[name="captcha"]') !== null ||
document.querySelector('.g-recaptcha') !== null;
});
await sendFailureMail(
'❌ OIDC Login Monitoring fehlgeschlagen',
`Fehler beim Login:\n\n${err.message}\n\nZeitpunkt: ${timestamp}`,
[
{ filename: 'error.png', path: screenshotPath },
{ filename: 'page.html', path: htmlPath },
{ filename: 'page.txt', path: textPath }
]
);
// Schritt-Metriken (bis zum Fehler) in Datei schreiben
fs.writeFileSync(path.join(resultDir, 'step-metrics.json'), JSON.stringify(stepsData, null, 2));
if (captchaDetected) {
console.error('❌ Captcha erkannt Test wird abgebrochen');
const captchaPath = path.join(resultDir, `captcha_${timestamp}.png`);
const captchaHtmlPath = path.join(resultDir, `captcha_${timestamp}.html`);
const captchaTextPath = path.join(resultDir, `captcha_${timestamp}.txt`);
await page.screenshot({ path: captchaPath });
fs.writeFileSync(captchaHtmlPath, await page.content());
fs.writeFileSync(captchaTextPath, await page.evaluate(() => document.body.innerText));
process.exit(3); // spezieller Exit-Code für Captcha
}
process.exit(1);
} finally {
await browser.close();
}
})();