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