Files
E2E-Monitoring/test/monitor.js
2025-04-22 00:51:00 +02:00

263 lines
9.9 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 = "",
SMTP_HOST,
SMTP_PORT,
MAIL_FROM,
MAIL_TO,
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: `/metrics/job/${encodeURIComponent(TESTNAME)}`,
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} ${PUSHGATEWAY_URL} ${lines}`));
}
});
});
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}/${timestamp}-eiamselect.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}/${timestamp}-userid.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}/${timestamp}-pw.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}/${timestamp}totp.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}/${timestamp}-result.png`, fullPage: true });
console.log(` Suche nach Benutzername: ${SUCCESS_TEXT}`);
const pageText = await page.textContent('body');
fs.writeFileSync(path.join(resultDir, `${timestamp}-final.html`), await page.content());
fs.writeFileSync(path.join(resultDir, `${timestamp}-final.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}/${timestamp}logout.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, `${timestamp}-error.png`);
const htmlPath = path.join(resultDir, `${timestamp}-page.html`);
const textPath = path.join(resultDir, `${timestamp}-page.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();
}
})();