🎉 Added new documentation files and scripts for Screenshot Tool 📚 in backend/
This commit is contained in:
parent
cad2f4f7cc
commit
915a5d7ffe
1
backend/docs/SCHULUNG_SCREENSHOT_TOOL.md
Normal file
1
backend/docs/SCHULUNG_SCREENSHOT_TOOL.md
Normal file
@ -0,0 +1 @@
|
||||
|
169
backend/scripts/run_screenshot_tool.ps1
Normal file
169
backend/scripts/run_screenshot_tool.ps1
Normal file
@ -0,0 +1,169 @@
|
||||
# PowerShell-Skript für automatische Screenshots
|
||||
# =============================================
|
||||
|
||||
param(
|
||||
[string]$ConfigFile = "screenshot_config.json",
|
||||
[switch]$Interactive = $false,
|
||||
[switch]$QuickRun = $false,
|
||||
[string]$ServerUrl = "",
|
||||
[string]$OutputDir = ""
|
||||
)
|
||||
|
||||
Write-Host "===============================================" -ForegroundColor Cyan
|
||||
Write-Host "🎯 AUTOMATISCHES SCREENSHOT-TOOL FÜR SCHULUNGEN" -ForegroundColor Yellow
|
||||
Write-Host "===============================================" -ForegroundColor Cyan
|
||||
Write-Host ""
|
||||
|
||||
# Arbeitsverzeichnis zum Skript-Ordner ändern
|
||||
$ScriptDir = Split-Path -Parent $MyInvocation.MyCommand.Path
|
||||
Set-Location $ScriptDir
|
||||
|
||||
# Prüfe ob Python verfügbar ist
|
||||
try {
|
||||
$pythonVersion = python --version 2>&1
|
||||
Write-Host "✅ Python gefunden: $pythonVersion" -ForegroundColor Green
|
||||
} catch {
|
||||
Write-Host "❌ Python nicht gefunden!" -ForegroundColor Red
|
||||
Write-Host "💡 Installieren Sie Python von https://python.org" -ForegroundColor Yellow
|
||||
exit 1
|
||||
}
|
||||
|
||||
# Prüfe ob Selenium installiert ist
|
||||
$seleniumCheck = python -c "import selenium; print('✅ Selenium verfügbar')" 2>&1
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "⚠️ Selenium nicht installiert. Installiere jetzt..." -ForegroundColor Yellow
|
||||
pip install selenium webdriver-manager
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "❌ Selenium-Installation fehlgeschlagen!" -ForegroundColor Red
|
||||
exit 1
|
||||
}
|
||||
Write-Host "✅ Selenium erfolgreich installiert" -ForegroundColor Green
|
||||
} else {
|
||||
Write-Host $seleniumCheck -ForegroundColor Green
|
||||
}
|
||||
|
||||
# Prüfe ChromeDriver
|
||||
$chromeCheck = python -c "from selenium import webdriver; from selenium.webdriver.chrome.service import Service; print('✅ Chrome WebDriver verfügbar')" 2>&1
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "⚠️ ChromeDriver nicht gefunden. Installiere webdriver-manager..." -ForegroundColor Yellow
|
||||
pip install webdriver-manager
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "❌ WebDriver-Manager-Installation fehlgeschlagen!" -ForegroundColor Red
|
||||
Write-Host "💡 Manuell ChromeDriver von https://chromedriver.chromium.org/ herunterladen" -ForegroundColor Yellow
|
||||
} else {
|
||||
Write-Host "✅ WebDriver-Manager installiert" -ForegroundColor Green
|
||||
}
|
||||
}
|
||||
|
||||
# Flask-App verfügbarkeit prüfen
|
||||
Write-Host ""
|
||||
Write-Host "🔍 Prüfe Flask-App..." -ForegroundColor Blue
|
||||
$flaskCheck = python -c "import sys; sys.path.append('..'); from app import app; print('✅ Flask-App verfügbar')" 2>&1
|
||||
if ($LASTEXITCODE -ne 0) {
|
||||
Write-Host "⚠️ Flask-App nicht direkt verfügbar" -ForegroundColor Yellow
|
||||
Write-Host "📋 Das Tool wird mit Standard-Routen arbeiten" -ForegroundColor Blue
|
||||
} else {
|
||||
Write-Host $flaskCheck -ForegroundColor Green
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "🚀 STARTE SCREENSHOT-TOOL" -ForegroundColor Green
|
||||
Write-Host "=" * 30 -ForegroundColor Green
|
||||
|
||||
# Parameter für das Python-Skript vorbereiten
|
||||
$pythonArgs = @("screenshot_tool.py")
|
||||
|
||||
if ($QuickRun) {
|
||||
Write-Host "⚡ Quick-Run-Modus aktiviert" -ForegroundColor Yellow
|
||||
# Setze Umgebungsvariablen für automatische Konfiguration
|
||||
$env:SCREENSHOT_AUTO_MODE = "true"
|
||||
$env:SCREENSHOT_HEADLESS = "true"
|
||||
|
||||
if ($ServerUrl) {
|
||||
$env:SCREENSHOT_SERVER_URL = $ServerUrl
|
||||
}
|
||||
|
||||
if ($OutputDir) {
|
||||
$env:SCREENSHOT_OUTPUT_DIR = $OutputDir
|
||||
}
|
||||
}
|
||||
|
||||
# Tool ausführen
|
||||
try {
|
||||
if ($Interactive) {
|
||||
Write-Host "🎛️ Interaktiver Modus - Folgen Sie den Anweisungen" -ForegroundColor Blue
|
||||
python @pythonArgs
|
||||
} else {
|
||||
Write-Host "🤖 Automatischer Modus" -ForegroundColor Blue
|
||||
python @pythonArgs
|
||||
}
|
||||
|
||||
$exitCode = $LASTEXITCODE
|
||||
|
||||
if ($exitCode -eq 0) {
|
||||
Write-Host ""
|
||||
Write-Host "🎉 SCREENSHOT-ERSTELLUNG ERFOLGREICH ABGESCHLOSSEN!" -ForegroundColor Green
|
||||
Write-Host ""
|
||||
|
||||
# Zeige Ausgabe-Ordner an
|
||||
$outputPath = "docs\schulung\screenshots"
|
||||
if (Test-Path $outputPath) {
|
||||
Write-Host "📁 Screenshots verfügbar in:" -ForegroundColor Blue
|
||||
Write-Host " $(Resolve-Path $outputPath)" -ForegroundColor White
|
||||
|
||||
# Frage ob Ordner geöffnet werden soll
|
||||
$openFolder = Read-Host "📂 Möchten Sie den Screenshot-Ordner öffnen? (j/n)"
|
||||
if ($openFolder -eq "j" -or $openFolder -eq "ja" -or $openFolder -eq "y" -or $openFolder -eq "yes") {
|
||||
Start-Process "explorer.exe" -ArgumentList (Resolve-Path $outputPath)
|
||||
}
|
||||
}
|
||||
|
||||
# Zeige Bericht an
|
||||
$reportPath = "$outputPath\screenshot_bericht.md"
|
||||
if (Test-Path $reportPath) {
|
||||
Write-Host ""
|
||||
Write-Host "📊 Detaillierter Bericht verfügbar:" -ForegroundColor Blue
|
||||
Write-Host " $(Resolve-Path $reportPath)" -ForegroundColor White
|
||||
|
||||
$openReport = Read-Host "📖 Möchten Sie den Bericht öffnen? (j/n)"
|
||||
if ($openReport -eq "j" -or $openReport -eq "ja" -or $openReport -eq "y" -or $openReport -eq "yes") {
|
||||
Start-Process "notepad.exe" -ArgumentList (Resolve-Path $reportPath)
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
Write-Host ""
|
||||
Write-Host "❌ FEHLER BEI DER SCREENSHOT-ERSTELLUNG" -ForegroundColor Red
|
||||
Write-Host "📋 Überprüfen Sie die Logs für Details" -ForegroundColor Yellow
|
||||
}
|
||||
|
||||
} catch {
|
||||
Write-Host ""
|
||||
Write-Host "❌ UNERWARTETER FEHLER: $($_.Exception.Message)" -ForegroundColor Red
|
||||
$exitCode = 1
|
||||
}
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "💡 VERWENDUNGSHINWEISE FÜR SCHULUNGEN:" -ForegroundColor Cyan
|
||||
Write-Host "=" * 40 -ForegroundColor Cyan
|
||||
Write-Host "• Admin-Screenshots: docs\schulung\screenshots\admin\" -ForegroundColor White
|
||||
Write-Host "• Benutzer-Screenshots: docs\schulung\screenshots\benutzer\" -ForegroundColor White
|
||||
Write-Host "• Öffentliche Screenshots: docs\schulung\screenshots\oeffentlich\" -ForegroundColor White
|
||||
Write-Host "• Verschiedene Auflösungen in Unterordnern verfügbar" -ForegroundColor White
|
||||
Write-Host "• Perfekt für PowerPoint-Präsentationen geeignet" -ForegroundColor White
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "🎓 TIPPS FÜR PRÄSENTATIONEN:" -ForegroundColor Yellow
|
||||
Write-Host "• Desktop-Screenshots für Hauptpräsentationen verwenden" -ForegroundColor White
|
||||
Write-Host "• Mobile-Screenshots für Responsive-Design zeigen" -ForegroundColor White
|
||||
Write-Host "• Admin-Ordner für Administrator-Schulungen" -ForegroundColor White
|
||||
Write-Host "• Benutzer-Ordner für allgemeine Mitarbeiterschulungen" -ForegroundColor White
|
||||
|
||||
Write-Host ""
|
||||
Write-Host "📞 Bei Problemen:" -ForegroundColor Magenta
|
||||
Write-Host "• Log-Datei prüfen: screenshot_tool.log" -ForegroundColor White
|
||||
Write-Host "• Server läuft auf korrekter URL?" -ForegroundColor White
|
||||
Write-Host "• Admin-Zugangsdaten korrekt?" -ForegroundColor White
|
||||
Write-Host "• ChromeDriver installiert?" -ForegroundColor White
|
||||
|
||||
exit $exitCode
|
113
backend/scripts/screenshot_config.json
Normal file
113
backend/scripts/screenshot_config.json
Normal file
@ -0,0 +1,113 @@
|
||||
{
|
||||
"server": {
|
||||
"base_url": "http://localhost:5000",
|
||||
"admin_email": "admin@example.com",
|
||||
"admin_password": "admin123"
|
||||
},
|
||||
"browser": {
|
||||
"type": "chrome",
|
||||
"headless": true,
|
||||
"page_load_timeout": 15,
|
||||
"element_wait_timeout": 10,
|
||||
"screenshot_delay": 2
|
||||
},
|
||||
"output": {
|
||||
"base_directory": "docs/schulung/screenshots",
|
||||
"include_timestamp": true,
|
||||
"create_subdirectories": true
|
||||
},
|
||||
"resolutions": {
|
||||
"desktop": {
|
||||
"width": 1920,
|
||||
"height": 1080,
|
||||
"description": "Standard Desktop-Auflösung"
|
||||
},
|
||||
"tablet": {
|
||||
"width": 1024,
|
||||
"height": 768,
|
||||
"description": "Tablet-Auflösung"
|
||||
},
|
||||
"mobile": {
|
||||
"width": 375,
|
||||
"height": 667,
|
||||
"description": "Mobile-Auflösung (iPhone)"
|
||||
},
|
||||
"large_desktop": {
|
||||
"width": 2560,
|
||||
"height": 1440,
|
||||
"description": "Große Desktop-Auflösung für 4K-Displays"
|
||||
}
|
||||
},
|
||||
"routes": {
|
||||
"include_patterns": [
|
||||
"/",
|
||||
"/dashboard",
|
||||
"/admin*",
|
||||
"/user/*",
|
||||
"/printers*",
|
||||
"/jobs*",
|
||||
"/stats*",
|
||||
"/reports*",
|
||||
"/maintenance*",
|
||||
"/guest*"
|
||||
],
|
||||
"exclude_patterns": [
|
||||
"/api/*",
|
||||
"/auth/api/*",
|
||||
"/static/*",
|
||||
"*/favicon.ico",
|
||||
"*/robots.txt",
|
||||
"*/sitemap.xml"
|
||||
],
|
||||
"custom_routes": [
|
||||
{
|
||||
"url": "/demo",
|
||||
"category": "benutzer",
|
||||
"description": "Komponenten-Demo-Seite"
|
||||
},
|
||||
{
|
||||
"url": "/socket-test",
|
||||
"category": "admin",
|
||||
"description": "WebSocket-Test-Seite"
|
||||
}
|
||||
]
|
||||
},
|
||||
"categories": {
|
||||
"admin": {
|
||||
"patterns": ["/admin", "/admin-dashboard", "/admin/*"],
|
||||
"description": "Administrator-Bereich"
|
||||
},
|
||||
"benutzer": {
|
||||
"patterns": ["/user/*", "/dashboard", "/printers", "/jobs", "/stats"],
|
||||
"description": "Allgemeiner Benutzerbereich"
|
||||
},
|
||||
"oeffentlich": {
|
||||
"patterns": ["/", "/guest", "/privacy", "/terms", "/imprint", "/legal"],
|
||||
"description": "Öffentlich zugängliche Seiten"
|
||||
},
|
||||
"berichte": {
|
||||
"patterns": ["/reports", "/stats"],
|
||||
"description": "Berichte und Statistiken"
|
||||
},
|
||||
"wartung": {
|
||||
"patterns": ["/maintenance"],
|
||||
"description": "Wartung und System-Tools"
|
||||
}
|
||||
},
|
||||
"advanced": {
|
||||
"take_full_page_screenshots": true,
|
||||
"wait_for_dynamic_content": true,
|
||||
"capture_hover_states": false,
|
||||
"include_browser_ui": false,
|
||||
"compress_images": false,
|
||||
"generate_thumbnails": true,
|
||||
"create_comparison_report": false
|
||||
},
|
||||
"schulung": {
|
||||
"create_presentation_slides": true,
|
||||
"generate_step_by_step_guide": true,
|
||||
"create_feature_overview": true,
|
||||
"include_annotations": false,
|
||||
"language": "de"
|
||||
}
|
||||
}
|
678
backend/scripts/screenshot_tool.py
Normal file
678
backend/scripts/screenshot_tool.py
Normal file
@ -0,0 +1,678 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
Automatisches Screenshot-Tool für Mitarbeiterschulungen
|
||||
======================================================
|
||||
|
||||
Dieses Tool erstellt automatisch Screenshots von allen verfügbaren Seiten
|
||||
der Webanwendung für Schulungszwecke und Präsentationsmaterial.
|
||||
|
||||
Funktionen:
|
||||
- Automatische Erkennung aller verfügbaren Routen
|
||||
- Screenshots in verschiedenen Auflösungen (Desktop, Tablet, Mobile)
|
||||
- Strukturierte Ordnerorganisation für Schulungen
|
||||
- Automatischer Login als Admin
|
||||
- Detaillierter Bericht über erstellte Screenshots
|
||||
- Behandlung von geschützten und öffentlichen Bereichen
|
||||
|
||||
Autor: KI-Assistant
|
||||
Datum: 2025-01-16
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import time
|
||||
import json
|
||||
import logging
|
||||
from datetime import datetime, timedelta
|
||||
from typing import List, Dict, Tuple, Optional, Set
|
||||
from urllib.parse import urljoin, urlparse
|
||||
import traceback
|
||||
import subprocess
|
||||
|
||||
# Selenium Imports
|
||||
try:
|
||||
from selenium import webdriver
|
||||
from selenium.webdriver.common.by import By
|
||||
from selenium.webdriver.support.ui import WebDriverWait
|
||||
from selenium.webdriver.support import expected_conditions as EC
|
||||
from selenium.webdriver.chrome.options import Options as ChromeOptions
|
||||
from selenium.webdriver.firefox.options import Options as FirefoxOptions
|
||||
from selenium.webdriver.chrome.service import Service as ChromeService
|
||||
from selenium.webdriver.firefox.service import Service as FirefoxService
|
||||
from selenium.common.exceptions import TimeoutException, WebDriverException, NoSuchElementException
|
||||
SELENIUM_AVAILABLE = True
|
||||
except ImportError as e:
|
||||
print(f"⚠️ Selenium nicht verfügbar: {e}")
|
||||
print("💡 Installieren Sie Selenium: pip install selenium")
|
||||
SELENIUM_AVAILABLE = False
|
||||
|
||||
# Flask App Import
|
||||
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||
|
||||
try:
|
||||
from app import app, User
|
||||
from models import get_db_session
|
||||
FLASK_APP_AVAILABLE = True
|
||||
except ImportError as e:
|
||||
print(f"⚠️ Flask-App nicht verfügbar: {e}")
|
||||
FLASK_APP_AVAILABLE = False
|
||||
|
||||
# Logging-Konfiguration
|
||||
logging.basicConfig(
|
||||
level=logging.INFO,
|
||||
format='%(asctime)s - %(levelname)s - %(message)s',
|
||||
handlers=[
|
||||
logging.FileHandler('screenshot_tool.log'),
|
||||
logging.StreamHandler()
|
||||
]
|
||||
)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
class ScreenshotConfiguration:
|
||||
"""Konfiguration für das Screenshot-Tool"""
|
||||
|
||||
# Basis-Konfiguration
|
||||
BASE_URL = "http://localhost:5000"
|
||||
ADMIN_EMAIL = "admin@example.com" # Anpassen falls nötig
|
||||
ADMIN_PASSWORD = "admin123" # Anpassen falls nötig
|
||||
|
||||
# Screenshot-Ordner
|
||||
SCREENSHOT_BASE_DIR = os.path.join("docs", "schulung", "screenshots")
|
||||
|
||||
# Browser-Konfiguration
|
||||
BROWSER_TYPE = "chrome" # "chrome" oder "firefox"
|
||||
HEADLESS = True # Für Server ohne GUI
|
||||
|
||||
# Auflösungen für verschiedene Geräte
|
||||
RESOLUTIONS = {
|
||||
"desktop": (1920, 1080),
|
||||
"tablet": (1024, 768),
|
||||
"mobile": (375, 667)
|
||||
}
|
||||
|
||||
# Wartezeiten
|
||||
PAGE_LOAD_TIMEOUT = 15
|
||||
ELEMENT_WAIT_TIMEOUT = 10
|
||||
SCREENSHOT_DELAY = 2
|
||||
|
||||
# Zu ignorierende Routen (z.B. API-Endpunkte)
|
||||
IGNORED_ROUTES = {
|
||||
"/api/", "/auth/api/", "/static/", "/favicon.ico",
|
||||
"/robots.txt", "/sitemap.xml"
|
||||
}
|
||||
|
||||
# Routen die spezielle Behandlung benötigen
|
||||
SPECIAL_ROUTES = {
|
||||
"/admin": "Benötigt Admin-Rechte",
|
||||
"/user/": "Benötigt User-Login",
|
||||
"/guest": "Öffentlich zugänglich"
|
||||
}
|
||||
|
||||
class RouteDiscovery:
|
||||
"""Klasse zur automatischen Erkennung aller verfügbaren Routen"""
|
||||
|
||||
def __init__(self):
|
||||
self.routes = set()
|
||||
self.protected_routes = set()
|
||||
self.public_routes = set()
|
||||
|
||||
def discover_routes_from_app(self) -> List[str]:
|
||||
"""Extrahiert alle Routen aus der Flask-App"""
|
||||
if not FLASK_APP_AVAILABLE:
|
||||
logger.warning("Flask-App nicht verfügbar - verwende Standard-Routen")
|
||||
return self._get_default_routes()
|
||||
|
||||
discovered_routes = []
|
||||
|
||||
try:
|
||||
with app.app_context():
|
||||
# Alle URL-Regeln der App durchgehen
|
||||
for rule in app.url_map.iter_rules():
|
||||
# Nur GET-Routen für Screenshots
|
||||
if 'GET' in rule.methods:
|
||||
route = str(rule.rule)
|
||||
|
||||
# Dynamische Parameter ersetzen
|
||||
if '<' in route:
|
||||
route = self._resolve_dynamic_route(route)
|
||||
if route:
|
||||
discovered_routes.extend(route if isinstance(route, list) else [route])
|
||||
else:
|
||||
discovered_routes.append(route)
|
||||
|
||||
# Kategorisierung der Routen
|
||||
self._categorize_route(route)
|
||||
|
||||
logger.info(f"✅ {len(discovered_routes)} Routen aus Flask-App extrahiert")
|
||||
return discovered_routes
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Fehler beim Extrahieren der Routen: {e}")
|
||||
return self._get_default_routes()
|
||||
|
||||
def _resolve_dynamic_route(self, route: str) -> Optional[List[str]]:
|
||||
"""Löst dynamische Routen mit Beispieldaten auf"""
|
||||
resolved_routes = []
|
||||
|
||||
try:
|
||||
if not FLASK_APP_AVAILABLE:
|
||||
return None
|
||||
|
||||
with app.app_context():
|
||||
db_session = get_db_session()
|
||||
|
||||
# User-ID Parameter
|
||||
if '<int:user_id>' in route:
|
||||
users = db_session.query(User).limit(3).all()
|
||||
for user in users:
|
||||
resolved_routes.append(route.replace('<int:user_id>', str(user.id)))
|
||||
|
||||
# Printer-ID Parameter
|
||||
elif '<int:printer_id>' in route:
|
||||
# Beispiel-Printer-IDs (1, 2, 3)
|
||||
for printer_id in [1, 2, 3]:
|
||||
resolved_routes.append(route.replace('<int:printer_id>', str(printer_id)))
|
||||
|
||||
# Job-ID Parameter
|
||||
elif '<int:job_id>' in route:
|
||||
# Beispiel-Job-IDs (1, 2, 3)
|
||||
for job_id in [1, 2, 3]:
|
||||
resolved_routes.append(route.replace('<int:job_id>', str(job_id)))
|
||||
|
||||
# Andere Parameter mit Standard-Werten
|
||||
elif '<' in route:
|
||||
# Generische Behandlung
|
||||
import re
|
||||
pattern = r'<[^>]+>'
|
||||
resolved_route = re.sub(pattern, '1', route)
|
||||
resolved_routes.append(resolved_route)
|
||||
|
||||
db_session.close()
|
||||
|
||||
except Exception as e:
|
||||
logger.warning(f"⚠️ Fehler beim Auflösen der dynamischen Route {route}: {e}")
|
||||
|
||||
return resolved_routes if resolved_routes else None
|
||||
|
||||
def _categorize_route(self, route: str):
|
||||
"""Kategorisiert Routen als öffentlich oder geschützt"""
|
||||
if any(protected in route for protected in ['/admin', '/user/', '/dashboard']):
|
||||
self.protected_routes.add(route)
|
||||
elif any(public in route for public in ['/guest', '/privacy', '/terms', '/imprint']):
|
||||
self.public_routes.add(route)
|
||||
else:
|
||||
# Standardmäßig als geschützt behandeln
|
||||
self.protected_routes.add(route)
|
||||
|
||||
def _get_default_routes(self) -> List[str]:
|
||||
"""Fallback-Routen falls automatische Erkennung fehlschlägt"""
|
||||
return [
|
||||
"/",
|
||||
"/dashboard",
|
||||
"/admin",
|
||||
"/admin-dashboard",
|
||||
"/printers",
|
||||
"/jobs",
|
||||
"/jobs/new",
|
||||
"/stats",
|
||||
"/user/profile",
|
||||
"/user/settings",
|
||||
"/admin/users/add",
|
||||
"/admin/printers/add",
|
||||
"/reports",
|
||||
"/maintenance",
|
||||
"/guest",
|
||||
"/guest-status",
|
||||
"/privacy",
|
||||
"/terms",
|
||||
"/imprint",
|
||||
"/demo"
|
||||
]
|
||||
|
||||
class BrowserManager:
|
||||
"""Verwaltet den Webbrowser für Screenshots"""
|
||||
|
||||
def __init__(self, config: ScreenshotConfiguration):
|
||||
self.config = config
|
||||
self.driver = None
|
||||
|
||||
def initialize_browser(self) -> bool:
|
||||
"""Initialisiert den Webbrowser"""
|
||||
try:
|
||||
if self.config.BROWSER_TYPE.lower() == "chrome":
|
||||
self.driver = self._setup_chrome()
|
||||
elif self.config.BROWSER_TYPE.lower() == "firefox":
|
||||
self.driver = self._setup_firefox()
|
||||
else:
|
||||
raise ValueError(f"Unbekannter Browser-Typ: {self.config.BROWSER_TYPE}")
|
||||
|
||||
# Browser-Konfiguration
|
||||
self.driver.set_page_load_timeout(self.config.PAGE_LOAD_TIMEOUT)
|
||||
self.driver.implicitly_wait(self.config.ELEMENT_WAIT_TIMEOUT)
|
||||
|
||||
logger.info(f"✅ Browser {self.config.BROWSER_TYPE} erfolgreich initialisiert")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Fehler beim Initialisieren des Browsers: {e}")
|
||||
return False
|
||||
|
||||
def _setup_chrome(self):
|
||||
"""Konfiguriert Chrome-Browser"""
|
||||
options = ChromeOptions()
|
||||
|
||||
if self.config.HEADLESS:
|
||||
options.add_argument('--headless')
|
||||
|
||||
# Weitere Chrome-Optionen für Stabilität
|
||||
options.add_argument('--no-sandbox')
|
||||
options.add_argument('--disable-dev-shm-usage')
|
||||
options.add_argument('--disable-gpu')
|
||||
options.add_argument('--window-size=1920,1080')
|
||||
options.add_argument('--disable-notifications')
|
||||
options.add_argument('--disable-popup-blocking')
|
||||
|
||||
try:
|
||||
# Automatische Erkennung des ChromeDriver
|
||||
return webdriver.Chrome(options=options)
|
||||
except Exception as e:
|
||||
logger.warning(f"⚠️ ChromeDriver automatisch nicht gefunden: {e}")
|
||||
logger.info("💡 Installieren Sie ChromeDriver oder verwenden Sie webdriver-manager")
|
||||
raise
|
||||
|
||||
def _setup_firefox(self):
|
||||
"""Konfiguriert Firefox-Browser"""
|
||||
options = FirefoxOptions()
|
||||
|
||||
if self.config.HEADLESS:
|
||||
options.add_argument('--headless')
|
||||
|
||||
try:
|
||||
return webdriver.Firefox(options=options)
|
||||
except Exception as e:
|
||||
logger.warning(f"⚠️ GeckoDriver nicht gefunden: {e}")
|
||||
logger.info("💡 Installieren Sie GeckoDriver für Firefox")
|
||||
raise
|
||||
|
||||
def set_resolution(self, resolution_name: str):
|
||||
"""Setzt die Browser-Auflösung"""
|
||||
if resolution_name in self.config.RESOLUTIONS:
|
||||
width, height = self.config.RESOLUTIONS[resolution_name]
|
||||
self.driver.set_window_size(width, height)
|
||||
logger.debug(f"📱 Auflösung gesetzt: {resolution_name} ({width}x{height})")
|
||||
|
||||
def close(self):
|
||||
"""Schließt den Browser"""
|
||||
if self.driver:
|
||||
try:
|
||||
self.driver.quit()
|
||||
logger.info("✅ Browser geschlossen")
|
||||
except Exception as e:
|
||||
logger.warning(f"⚠️ Fehler beim Schließen des Browsers: {e}")
|
||||
|
||||
class ScreenshotTool:
|
||||
"""Hauptklasse für das Screenshot-Tool"""
|
||||
|
||||
def __init__(self, config: ScreenshotConfiguration = None):
|
||||
self.config = config or ScreenshotConfiguration()
|
||||
self.browser = BrowserManager(self.config)
|
||||
self.route_discovery = RouteDiscovery()
|
||||
self.results = {
|
||||
"success": [],
|
||||
"failed": [],
|
||||
"skipped": [],
|
||||
"total_screenshots": 0,
|
||||
"start_time": None,
|
||||
"end_time": None
|
||||
}
|
||||
|
||||
def run_screenshot_session(self) -> Dict:
|
||||
"""Führt eine komplette Screenshot-Session durch"""
|
||||
logger.info("🚀 Starte automatische Screenshot-Erstellung für Schulungen")
|
||||
self.results["start_time"] = datetime.now()
|
||||
|
||||
try:
|
||||
# 1. Vorbereitung
|
||||
if not self._prepare_environment():
|
||||
return self.results
|
||||
|
||||
# 2. Browser initialisieren
|
||||
if not self.browser.initialize_browser():
|
||||
return self.results
|
||||
|
||||
# 3. Routen entdecken
|
||||
routes = self.route_discovery.discover_routes_from_app()
|
||||
logger.info(f"📋 {len(routes)} Routen gefunden")
|
||||
|
||||
# 4. Login durchführen
|
||||
if not self._perform_admin_login():
|
||||
logger.error("❌ Admin-Login fehlgeschlagen")
|
||||
return self.results
|
||||
|
||||
# 5. Screenshots erstellen
|
||||
self._create_screenshots_for_routes(routes)
|
||||
|
||||
# 6. Bericht generieren
|
||||
self._generate_report()
|
||||
|
||||
except KeyboardInterrupt:
|
||||
logger.info("⏹️ Screenshot-Session vom Benutzer abgebrochen")
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Unerwarteter Fehler: {e}")
|
||||
logger.debug(traceback.format_exc())
|
||||
finally:
|
||||
self.browser.close()
|
||||
self.results["end_time"] = datetime.now()
|
||||
|
||||
return self.results
|
||||
|
||||
def _prepare_environment(self) -> bool:
|
||||
"""Bereitet die Umgebung für Screenshots vor"""
|
||||
try:
|
||||
# Screenshot-Ordner erstellen
|
||||
os.makedirs(self.config.SCREENSHOT_BASE_DIR, exist_ok=True)
|
||||
|
||||
# Unterordner für verschiedene Kategorien
|
||||
categories = ["admin", "benutzer", "oeffentlich", "alle_auflösungen"]
|
||||
for category in categories:
|
||||
category_dir = os.path.join(self.config.SCREENSHOT_BASE_DIR, category)
|
||||
os.makedirs(category_dir, exist_ok=True)
|
||||
|
||||
# Auflösungs-Unterordner
|
||||
for resolution in self.config.RESOLUTIONS.keys():
|
||||
resolution_dir = os.path.join(category_dir, resolution)
|
||||
os.makedirs(resolution_dir, exist_ok=True)
|
||||
|
||||
logger.info(f"✅ Screenshot-Ordner vorbereitet: {self.config.SCREENSHOT_BASE_DIR}")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Fehler bei der Umgebungsvorbereitung: {e}")
|
||||
return False
|
||||
|
||||
def _perform_admin_login(self) -> bool:
|
||||
"""Führt den Admin-Login durch"""
|
||||
try:
|
||||
login_url = urljoin(self.config.BASE_URL, "/auth/login")
|
||||
self.browser.driver.get(login_url)
|
||||
|
||||
# Warten bis Login-Formular geladen ist
|
||||
wait = WebDriverWait(self.browser.driver, self.config.ELEMENT_WAIT_TIMEOUT)
|
||||
|
||||
# Email eingeben
|
||||
email_field = wait.until(EC.presence_of_element_located((By.NAME, "email")))
|
||||
email_field.clear()
|
||||
email_field.send_keys(self.config.ADMIN_EMAIL)
|
||||
|
||||
# Passwort eingeben
|
||||
password_field = self.browser.driver.find_element(By.NAME, "password")
|
||||
password_field.clear()
|
||||
password_field.send_keys(self.config.ADMIN_PASSWORD)
|
||||
|
||||
# Login-Button klicken
|
||||
login_button = self.browser.driver.find_element(By.CSS_SELECTOR, "button[type='submit']")
|
||||
login_button.click()
|
||||
|
||||
# Warten auf Weiterleitung zum Dashboard
|
||||
wait.until(lambda driver: "/dashboard" in driver.current_url or "/admin" in driver.current_url)
|
||||
|
||||
logger.info("✅ Admin-Login erfolgreich")
|
||||
return True
|
||||
|
||||
except TimeoutException:
|
||||
logger.error("❌ Timeout beim Admin-Login")
|
||||
return False
|
||||
except NoSuchElementException as e:
|
||||
logger.error(f"❌ Login-Element nicht gefunden: {e}")
|
||||
return False
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Fehler beim Admin-Login: {e}")
|
||||
return False
|
||||
|
||||
def _create_screenshots_for_routes(self, routes: List[str]):
|
||||
"""Erstellt Screenshots für alle angegebenen Routen"""
|
||||
total_routes = len(routes)
|
||||
|
||||
for i, route in enumerate(routes, 1):
|
||||
logger.info(f"📸 Screenshot {i}/{total_routes}: {route}")
|
||||
|
||||
# Route ignorieren falls in Ignore-Liste
|
||||
if any(ignored in route for ignored in self.config.IGNORED_ROUTES):
|
||||
logger.debug(f"⏭️ Route ignoriert: {route}")
|
||||
self.results["skipped"].append({"route": route, "reason": "In Ignore-Liste"})
|
||||
continue
|
||||
|
||||
# Screenshots für alle Auflösungen erstellen
|
||||
for resolution_name in self.config.RESOLUTIONS.keys():
|
||||
try:
|
||||
self._take_screenshot_for_route(route, resolution_name)
|
||||
self.results["total_screenshots"] += 1
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Fehler bei Screenshot für {route} ({resolution_name}): {e}")
|
||||
self.results["failed"].append({
|
||||
"route": route,
|
||||
"resolution": resolution_name,
|
||||
"error": str(e)
|
||||
})
|
||||
|
||||
def _take_screenshot_for_route(self, route: str, resolution_name: str):
|
||||
"""Erstellt einen Screenshot für eine spezifische Route und Auflösung"""
|
||||
# Browser-Auflösung setzen
|
||||
self.browser.set_resolution(resolution_name)
|
||||
|
||||
# Zur Seite navigieren
|
||||
full_url = urljoin(self.config.BASE_URL, route)
|
||||
self.browser.driver.get(full_url)
|
||||
|
||||
# Warten bis Seite geladen ist
|
||||
time.sleep(self.config.SCREENSHOT_DELAY)
|
||||
|
||||
# Kategorie bestimmen
|
||||
category = self._determine_route_category(route)
|
||||
|
||||
# Dateiname generieren
|
||||
safe_route_name = self._sanitize_filename(route)
|
||||
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
||||
filename = f"{safe_route_name}_{resolution_name}_{timestamp}.png"
|
||||
|
||||
# Screenshot-Pfad
|
||||
screenshot_path = os.path.join(
|
||||
self.config.SCREENSHOT_BASE_DIR,
|
||||
category,
|
||||
resolution_name,
|
||||
filename
|
||||
)
|
||||
|
||||
# Screenshot erstellen
|
||||
self.browser.driver.save_screenshot(screenshot_path)
|
||||
|
||||
# Erfolg protokollieren
|
||||
self.results["success"].append({
|
||||
"route": route,
|
||||
"resolution": resolution_name,
|
||||
"file_path": screenshot_path,
|
||||
"file_size": os.path.getsize(screenshot_path),
|
||||
"timestamp": datetime.now().isoformat()
|
||||
})
|
||||
|
||||
logger.debug(f"✅ Screenshot gespeichert: {screenshot_path}")
|
||||
|
||||
def _determine_route_category(self, route: str) -> str:
|
||||
"""Bestimmt die Kategorie einer Route für die Ordnerorganisation"""
|
||||
if any(admin_route in route for admin_route in ['/admin', '/admin-dashboard']):
|
||||
return "admin"
|
||||
elif any(user_route in route for user_route in ['/user/', '/dashboard', '/printers', '/jobs']):
|
||||
return "benutzer"
|
||||
elif any(public_route in route for public_route in ['/guest', '/privacy', '/terms', '/imprint', '/']):
|
||||
return "oeffentlich"
|
||||
else:
|
||||
return "benutzer" # Standard-Kategorie
|
||||
|
||||
def _sanitize_filename(self, route: str) -> str:
|
||||
"""Bereinigt Routennamen für Dateinamen"""
|
||||
import re
|
||||
# Entferne gefährliche Zeichen
|
||||
safe_name = re.sub(r'[<>:"/\\|?*]', '_', route)
|
||||
# Ersetze Slashes mit Unterstrichen
|
||||
safe_name = safe_name.replace('/', '_')
|
||||
# Entferne führende/nachfolgende Unterstriche
|
||||
safe_name = safe_name.strip('_')
|
||||
# Leere Namen behandeln
|
||||
if not safe_name or safe_name == '_':
|
||||
safe_name = "home"
|
||||
return safe_name
|
||||
|
||||
def _generate_report(self):
|
||||
"""Generiert einen detaillierten Bericht über die Screenshot-Session"""
|
||||
duration = self.results["end_time"] - self.results["start_time"]
|
||||
|
||||
report = {
|
||||
"session_info": {
|
||||
"start_time": self.results["start_time"].isoformat(),
|
||||
"end_time": self.results["end_time"].isoformat(),
|
||||
"duration_seconds": duration.total_seconds(),
|
||||
"duration_formatted": str(duration)
|
||||
},
|
||||
"statistics": {
|
||||
"total_screenshots": self.results["total_screenshots"],
|
||||
"successful_screenshots": len(self.results["success"]),
|
||||
"failed_screenshots": len(self.results["failed"]),
|
||||
"skipped_routes": len(self.results["skipped"])
|
||||
},
|
||||
"results": self.results
|
||||
}
|
||||
|
||||
# Bericht als JSON speichern
|
||||
report_path = os.path.join(self.config.SCREENSHOT_BASE_DIR, "screenshot_report.json")
|
||||
with open(report_path, 'w', encoding='utf-8') as f:
|
||||
json.dump(report, f, indent=2, ensure_ascii=False, default=str)
|
||||
|
||||
# Menschenlesbaren Bericht erstellen
|
||||
self._generate_human_readable_report(report)
|
||||
|
||||
logger.info(f"📊 Bericht gespeichert: {report_path}")
|
||||
|
||||
def _generate_human_readable_report(self, report: Dict):
|
||||
"""Erstellt einen menschenlesbaren Bericht"""
|
||||
report_path = os.path.join(self.config.SCREENSHOT_BASE_DIR, "screenshot_bericht.md")
|
||||
|
||||
with open(report_path, 'w', encoding='utf-8') as f:
|
||||
f.write("# Screenshot-Bericht für Mitarbeiterschulungen\n\n")
|
||||
f.write(f"**Erstellt am:** {datetime.now().strftime('%d.%m.%Y um %H:%M:%S')}\n\n")
|
||||
|
||||
# Zusammenfassung
|
||||
f.write("## Zusammenfassung\n\n")
|
||||
f.write(f"- **Gesamte Screenshots:** {report['statistics']['total_screenshots']}\n")
|
||||
f.write(f"- **Erfolgreich:** {report['statistics']['successful_screenshots']}\n")
|
||||
f.write(f"- **Fehlgeschlagen:** {report['statistics']['failed_screenshots']}\n")
|
||||
f.write(f"- **Übersprungen:** {report['statistics']['skipped_routes']}\n")
|
||||
f.write(f"- **Dauer:** {report['session_info']['duration_formatted']}\n\n")
|
||||
|
||||
# Erfolgreiche Screenshots
|
||||
if self.results["success"]:
|
||||
f.write("## Erfolgreich erstellte Screenshots\n\n")
|
||||
for item in self.results["success"]:
|
||||
f.write(f"- **{item['route']}** ({item['resolution']})\n")
|
||||
f.write(f" - Datei: `{item['file_path']}`\n")
|
||||
f.write(f" - Größe: {item['file_size']:,} Bytes\n\n")
|
||||
|
||||
# Fehlgeschlagene Screenshots
|
||||
if self.results["failed"]:
|
||||
f.write("## Fehlgeschlagene Screenshots\n\n")
|
||||
for item in self.results["failed"]:
|
||||
f.write(f"- **{item['route']}** ({item['resolution']})\n")
|
||||
f.write(f" - Fehler: {item['error']}\n\n")
|
||||
|
||||
# Verwendungshinweise
|
||||
f.write("## Verwendung für Schulungen\n\n")
|
||||
f.write("### Ordnerstruktur\n\n")
|
||||
f.write("```\n")
|
||||
f.write("docs/schulung/screenshots/\n")
|
||||
f.write("├── admin/ # Administrator-Bereich\n")
|
||||
f.write("│ ├── desktop/ # Desktop-Auflösung (1920x1080)\n")
|
||||
f.write("│ ├── tablet/ # Tablet-Auflösung (1024x768)\n")
|
||||
f.write("│ └── mobile/ # Mobile-Auflösung (375x667)\n")
|
||||
f.write("├── benutzer/ # Benutzer-Bereich\n")
|
||||
f.write("│ ├── desktop/\n")
|
||||
f.write("│ ├── tablet/\n")
|
||||
f.write("│ └── mobile/\n")
|
||||
f.write("└── oeffentlich/ # Öffentlicher Bereich\n")
|
||||
f.write(" ├── desktop/\n")
|
||||
f.write(" ├── tablet/\n")
|
||||
f.write(" └── mobile/\n")
|
||||
f.write("```\n\n")
|
||||
|
||||
f.write("### Empfehlungen für Präsentationen\n\n")
|
||||
f.write("1. **Desktop-Screenshots** für Hauptpräsentationen verwenden\n")
|
||||
f.write("2. **Mobile-Screenshots** für Responsive-Design-Demonstrationen\n")
|
||||
f.write("3. **Admin-Ordner** für Administratoren-Schulungen\n")
|
||||
f.write("4. **Benutzer-Ordner** für allgemeine Mitarbeiterschulungen\n")
|
||||
f.write("5. **Öffentlich-Ordner** für Gäste-/Kunden-Präsentationen\n\n")
|
||||
|
||||
logger.info(f"📝 Menschenlesbarer Bericht gespeichert: {report_path}")
|
||||
|
||||
def main():
|
||||
"""Hauptfunktion des Screenshot-Tools"""
|
||||
print("=" * 60)
|
||||
print("🎯 AUTOMATISCHES SCREENSHOT-TOOL FÜR SCHULUNGEN")
|
||||
print("=" * 60)
|
||||
|
||||
# Abhängigkeiten prüfen
|
||||
if not SELENIUM_AVAILABLE:
|
||||
print("❌ Selenium ist nicht installiert!")
|
||||
print("💡 Führen Sie aus: pip install selenium")
|
||||
return 1
|
||||
|
||||
# Konfiguration erstellen
|
||||
config = ScreenshotConfiguration()
|
||||
|
||||
# Benutzer-Eingaben für Konfiguration
|
||||
print("\n📋 KONFIGURATION")
|
||||
print("-" * 30)
|
||||
|
||||
base_url = input(f"Server-URL [{config.BASE_URL}]: ").strip()
|
||||
if base_url:
|
||||
config.BASE_URL = base_url
|
||||
|
||||
admin_email = input(f"Admin-Email [{config.ADMIN_EMAIL}]: ").strip()
|
||||
if admin_email:
|
||||
config.ADMIN_EMAIL = admin_email
|
||||
|
||||
admin_password = input(f"Admin-Passwort [{config.ADMIN_PASSWORD}]: ").strip()
|
||||
if admin_password:
|
||||
config.ADMIN_PASSWORD = admin_password
|
||||
|
||||
headless_input = input(f"Headless-Modus (ohne GUI) [{'Ja' if config.HEADLESS else 'Nein'}]: ").strip().lower()
|
||||
if headless_input in ['nein', 'n', 'no', 'false']:
|
||||
config.HEADLESS = False
|
||||
elif headless_input in ['ja', 'j', 'yes', 'true']:
|
||||
config.HEADLESS = True
|
||||
|
||||
print(f"\n✅ Konfiguration abgeschlossen")
|
||||
print(f"📂 Screenshots werden gespeichert in: {config.SCREENSHOT_BASE_DIR}")
|
||||
|
||||
# Screenshot-Tool starten
|
||||
tool = ScreenshotTool(config)
|
||||
results = tool.run_screenshot_session()
|
||||
|
||||
# Ergebnisse anzeigen
|
||||
print("\n" + "=" * 60)
|
||||
print("📊 ERGEBNISSE")
|
||||
print("=" * 60)
|
||||
print(f"✅ Erfolgreich: {len(results['success'])} Screenshots")
|
||||
print(f"❌ Fehlgeschlagen: {len(results['failed'])} Screenshots")
|
||||
print(f"⏭️ Übersprungen: {len(results['skipped'])} Routen")
|
||||
print(f"📸 Gesamt: {results['total_screenshots']} Screenshots")
|
||||
|
||||
if results["start_time"] and results["end_time"]:
|
||||
duration = results["end_time"] - results["start_time"]
|
||||
print(f"⏱️ Dauer: {duration}")
|
||||
|
||||
print(f"\n📁 Alle Screenshots verfügbar in:")
|
||||
print(f" {os.path.abspath(config.SCREENSHOT_BASE_DIR)}")
|
||||
|
||||
return 0 if results["total_screenshots"] > 0 else 1
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
1
backend/scripts/start_screenshot_tool.bat
Normal file
1
backend/scripts/start_screenshot_tool.bat
Normal file
@ -0,0 +1 @@
|
||||
|
Loading…
x
Reference in New Issue
Block a user