test/monitor.js hinzugefügt
This commit is contained in:
259
test/monitor.js
Normal file
259
test/monitor.js
Normal 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();
|
||||||
|
}
|
||||||
|
})();
|
||||||
Reference in New Issue
Block a user