635 lines
22 KiB
Python
635 lines
22 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Vereinfachter HTML-Formular Tester für MYP System
|
|
===============================================
|
|
|
|
Ein schlanker Formular-Tester ohne externe Dependencies,
|
|
der grundlegende Formular-Validierung mit Standard-Python-Libraries testet.
|
|
|
|
Autor: Till Tomczak
|
|
Mercedes-Benz Projektarbeit MYP
|
|
"""
|
|
|
|
import asyncio
|
|
import json
|
|
import time
|
|
import urllib.request
|
|
import urllib.parse
|
|
import urllib.error
|
|
from urllib.parse import urljoin, urlparse
|
|
from html.parser import HTMLParser
|
|
from dataclasses import dataclass
|
|
from typing import Dict, List, Optional
|
|
import sys
|
|
import re
|
|
|
|
|
|
@dataclass
|
|
class FormField:
|
|
"""Repräsentiert ein Formular-Feld"""
|
|
name: str
|
|
field_type: str
|
|
required: bool = False
|
|
pattern: str = ""
|
|
placeholder: str = ""
|
|
value: str = ""
|
|
|
|
|
|
@dataclass
|
|
class Form:
|
|
"""Repräsentiert ein HTML-Formular"""
|
|
action: str
|
|
method: str
|
|
fields: List[FormField]
|
|
name: str = ""
|
|
|
|
|
|
class FormParser(HTMLParser):
|
|
"""Parst HTML und extrahiert Formular-Informationen"""
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.forms = []
|
|
self.current_form = None
|
|
self.in_form = False
|
|
|
|
def handle_starttag(self, tag, attrs):
|
|
attrs_dict = dict(attrs)
|
|
|
|
if tag == 'form':
|
|
self.in_form = True
|
|
self.current_form = Form(
|
|
action=attrs_dict.get('action', ''),
|
|
method=attrs_dict.get('method', 'GET').upper(),
|
|
fields=[],
|
|
name=attrs_dict.get('name', attrs_dict.get('id', f'form_{len(self.forms)}'))
|
|
)
|
|
|
|
elif self.in_form and tag in ['input', 'textarea', 'select']:
|
|
field = FormField(
|
|
name=attrs_dict.get('name', attrs_dict.get('id', f'field_{len(self.current_form.fields)}')),
|
|
field_type=attrs_dict.get('type', tag),
|
|
required='required' in attrs_dict,
|
|
pattern=attrs_dict.get('pattern', ''),
|
|
placeholder=attrs_dict.get('placeholder', ''),
|
|
value=attrs_dict.get('value', '')
|
|
)
|
|
self.current_form.fields.append(field)
|
|
|
|
def handle_endtag(self, tag):
|
|
if tag == 'form' and self.in_form:
|
|
self.in_form = False
|
|
if self.current_form:
|
|
self.forms.append(self.current_form)
|
|
self.current_form = None
|
|
|
|
|
|
class SimpleFormTester:
|
|
"""
|
|
Vereinfachter Formular-Tester ohne Browser-Dependencies.
|
|
Führt HTTP-basierte Tests durch.
|
|
"""
|
|
|
|
def __init__(self, base_url: str):
|
|
self.base_url = base_url.rstrip('/')
|
|
self.session_cookies = {}
|
|
|
|
def fetch_page(self, path: str) -> str:
|
|
"""Lädt eine Seite und gibt den HTML-Inhalt zurück"""
|
|
url = urljoin(self.base_url, path)
|
|
|
|
try:
|
|
request = urllib.request.Request(url)
|
|
|
|
# Cookies hinzufügen
|
|
if self.session_cookies:
|
|
cookie_header = '; '.join([f'{k}={v}' for k, v in self.session_cookies.items()])
|
|
request.add_header('Cookie', cookie_header)
|
|
|
|
with urllib.request.urlopen(request) as response:
|
|
html = response.read().decode('utf-8')
|
|
|
|
# Cookies aus Response extrahieren
|
|
cookie_header = response.getheader('Set-Cookie')
|
|
if cookie_header:
|
|
self._parse_cookies(cookie_header)
|
|
|
|
return html
|
|
|
|
except urllib.error.URLError as e:
|
|
print(f"❌ Fehler beim Laden von {url}: {e}")
|
|
return ""
|
|
|
|
def _parse_cookies(self, cookie_header: str):
|
|
"""Parst Set-Cookie Header"""
|
|
for cookie in cookie_header.split(','):
|
|
if '=' in cookie:
|
|
name, value = cookie.split('=', 1)
|
|
self.session_cookies[name.strip()] = value.split(';')[0].strip()
|
|
|
|
def find_forms(self, html: str) -> List[Form]:
|
|
"""Findet alle Formulare in HTML"""
|
|
parser = FormParser()
|
|
parser.feed(html)
|
|
return parser.forms
|
|
|
|
def generate_test_data(self, field: FormField) -> str:
|
|
"""Generiert Test-Daten für ein Feld"""
|
|
|
|
if field.field_type == 'email':
|
|
return "test@mercedes-benz.com"
|
|
elif field.field_type == 'password':
|
|
return "TestPassword123!"
|
|
elif field.field_type == 'tel':
|
|
return "+49 711 17-0"
|
|
elif field.field_type == 'url':
|
|
return "https://www.mercedes-benz.com"
|
|
elif field.field_type == 'number':
|
|
return "42"
|
|
elif field.field_type == 'date':
|
|
return "2024-06-18"
|
|
elif field.field_type == 'checkbox':
|
|
return "on"
|
|
elif field.field_type == 'radio':
|
|
return field.value or "option1"
|
|
elif 'name' in field.name.lower():
|
|
return "Test Benutzer"
|
|
elif 'username' in field.name.lower():
|
|
return "admin"
|
|
elif 'ip' in field.name.lower():
|
|
return "192.168.1.100"
|
|
elif 'port' in field.name.lower():
|
|
return "80"
|
|
else:
|
|
return f"Test_{field.name}"
|
|
|
|
def submit_form(self, form: Form, test_data: Dict[str, str], base_url: str) -> Dict:
|
|
"""Sendet Formular-Daten und gibt Response zurück"""
|
|
|
|
# Action URL bestimmen
|
|
if form.action.startswith('http'):
|
|
url = form.action
|
|
else:
|
|
url = urljoin(base_url, form.action)
|
|
|
|
# Daten vorbereiten
|
|
form_data = {}
|
|
for field in form.fields:
|
|
if field.name in test_data:
|
|
form_data[field.name] = test_data[field.name]
|
|
elif field.field_type not in ['submit', 'button']:
|
|
form_data[field.name] = self.generate_test_data(field)
|
|
|
|
try:
|
|
if form.method == 'GET':
|
|
# GET-Request mit Query-Parametern
|
|
query_string = urllib.parse.urlencode(form_data)
|
|
full_url = f"{url}?{query_string}"
|
|
request = urllib.request.Request(full_url)
|
|
else:
|
|
# POST-Request
|
|
data = urllib.parse.urlencode(form_data).encode('utf-8')
|
|
request = urllib.request.Request(url, data=data)
|
|
request.add_header('Content-Type', 'application/x-www-form-urlencoded')
|
|
|
|
# Cookies hinzufügen
|
|
if self.session_cookies:
|
|
cookie_header = '; '.join([f'{k}={v}' for k, v in self.session_cookies.items()])
|
|
request.add_header('Cookie', cookie_header)
|
|
|
|
with urllib.request.urlopen(request) as response:
|
|
response_html = response.read().decode('utf-8')
|
|
status_code = response.getcode()
|
|
|
|
return {
|
|
'success': 200 <= status_code < 400,
|
|
'status_code': status_code,
|
|
'html': response_html,
|
|
'form_data': form_data
|
|
}
|
|
|
|
except urllib.error.HTTPError as e:
|
|
return {
|
|
'success': False,
|
|
'status_code': e.code,
|
|
'error': str(e),
|
|
'form_data': form_data
|
|
}
|
|
except Exception as e:
|
|
return {
|
|
'success': False,
|
|
'error': str(e),
|
|
'form_data': form_data
|
|
}
|
|
|
|
def validate_field(self, field: FormField, value: str) -> Dict:
|
|
"""Validiert ein Feld mit gegebenem Wert"""
|
|
|
|
errors = []
|
|
|
|
# Required-Validierung
|
|
if field.required and not value.strip():
|
|
errors.append(f"Feld '{field.name}' ist erforderlich")
|
|
|
|
# Pattern-Validierung
|
|
if field.pattern and value:
|
|
try:
|
|
if not re.match(field.pattern, value):
|
|
errors.append(f"Feld '{field.name}' entspricht nicht dem Pattern '{field.pattern}'")
|
|
except re.error:
|
|
errors.append(f"Ungültiges Pattern für Feld '{field.name}': {field.pattern}")
|
|
|
|
# Type-spezifische Validierung
|
|
if value and field.field_type == 'email':
|
|
email_pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
|
|
if not re.match(email_pattern, value):
|
|
errors.append(f"Ungültige Email-Adresse: {value}")
|
|
|
|
elif value and field.field_type == 'url':
|
|
try:
|
|
parsed = urlparse(value)
|
|
if not parsed.scheme or not parsed.netloc:
|
|
errors.append(f"Ungültige URL: {value}")
|
|
except:
|
|
errors.append(f"Ungültige URL: {value}")
|
|
|
|
elif value and field.field_type == 'number':
|
|
try:
|
|
float(value)
|
|
except ValueError:
|
|
errors.append(f"'{value}' ist keine gültige Zahl")
|
|
|
|
return {
|
|
'valid': len(errors) == 0,
|
|
'errors': errors,
|
|
'field': field.name,
|
|
'value': value
|
|
}
|
|
|
|
def test_form_validation(self, form: Form) -> List[Dict]:
|
|
"""Testet Formular-Validierung mit verschiedenen Daten"""
|
|
|
|
validation_results = []
|
|
|
|
# 1. Test mit leeren Required-Feldern
|
|
for field in form.fields:
|
|
if field.required:
|
|
result = self.validate_field(field, "")
|
|
validation_results.append({
|
|
'test_type': 'required_field_empty',
|
|
'field': field.name,
|
|
'expected_invalid': True,
|
|
'actual_valid': result['valid'],
|
|
'passed': not result['valid'], # Sollte ungültig sein
|
|
'errors': result['errors']
|
|
})
|
|
|
|
# 2. Test mit ungültigen Email-Adressen
|
|
for field in form.fields:
|
|
if field.field_type == 'email':
|
|
invalid_emails = ['invalid-email', 'test@', '@domain.com', 'test..test@domain.com']
|
|
for invalid_email in invalid_emails:
|
|
result = self.validate_field(field, invalid_email)
|
|
validation_results.append({
|
|
'test_type': 'invalid_email',
|
|
'field': field.name,
|
|
'value': invalid_email,
|
|
'expected_invalid': True,
|
|
'actual_valid': result['valid'],
|
|
'passed': not result['valid'],
|
|
'errors': result['errors']
|
|
})
|
|
|
|
# 3. Test mit gültigen Daten
|
|
valid_test_data = {}
|
|
for field in form.fields:
|
|
if field.field_type not in ['submit', 'button']:
|
|
valid_test_data[field.name] = self.generate_test_data(field)
|
|
|
|
for field in form.fields:
|
|
if field.name in valid_test_data:
|
|
result = self.validate_field(field, valid_test_data[field.name])
|
|
validation_results.append({
|
|
'test_type': 'valid_data',
|
|
'field': field.name,
|
|
'value': valid_test_data[field.name],
|
|
'expected_invalid': False,
|
|
'actual_valid': result['valid'],
|
|
'passed': result['valid'],
|
|
'errors': result['errors']
|
|
})
|
|
|
|
return validation_results
|
|
|
|
def test_form_submission(self, form: Form, base_url: str) -> Dict:
|
|
"""Testet Formular-Submission"""
|
|
|
|
print(f"🧪 Teste Formular: {form.name} ({form.method} → {form.action})")
|
|
|
|
# Test-Daten generieren
|
|
test_data = {}
|
|
for field in form.fields:
|
|
if field.field_type not in ['submit', 'button']:
|
|
test_data[field.name] = self.generate_test_data(field)
|
|
|
|
print(f" 📝 Test-Daten: {list(test_data.keys())}")
|
|
|
|
# Formular senden
|
|
start_time = time.time()
|
|
result = self.submit_form(form, test_data, base_url)
|
|
execution_time = time.time() - start_time
|
|
|
|
# Validierungs-Tests
|
|
validation_results = self.test_form_validation(form)
|
|
|
|
return {
|
|
'form_name': form.name,
|
|
'method': form.method,
|
|
'action': form.action,
|
|
'fields_count': len(form.fields),
|
|
'test_data': test_data,
|
|
'submission_result': result,
|
|
'validation_results': validation_results,
|
|
'execution_time': execution_time,
|
|
'timestamp': time.strftime('%Y-%m-%d %H:%M:%S')
|
|
}
|
|
|
|
def test_page_forms(self, path: str) -> List[Dict]:
|
|
"""Testet alle Formulare auf einer Seite"""
|
|
|
|
print(f"🔍 Scanne Formulare auf: {self.base_url}{path}")
|
|
|
|
# Seite laden
|
|
html = self.fetch_page(path)
|
|
if not html:
|
|
return []
|
|
|
|
# Formulare finden
|
|
forms = self.find_forms(html)
|
|
print(f"✅ {len(forms)} Formulare gefunden")
|
|
|
|
# Jedes Formular testen
|
|
results = []
|
|
for form in forms:
|
|
result = self.test_form_submission(form, self.base_url + path)
|
|
results.append(result)
|
|
|
|
return results
|
|
|
|
def generate_report(self, test_results: List[Dict], output_file: str = "simple_form_test_report.html"):
|
|
"""Generiert einen einfachen HTML-Report"""
|
|
|
|
total_forms = len(test_results)
|
|
successful_submissions = len([r for r in test_results if r['submission_result'].get('success', False)])
|
|
|
|
# Validierungs-Statistiken
|
|
total_validations = sum(len(r['validation_results']) for r in test_results)
|
|
passed_validations = sum(len([v for v in r['validation_results'] if v['passed']]) for r in test_results)
|
|
|
|
html_report = f"""<!DOCTYPE html>
|
|
<html lang="de">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>MYP Formular Test Report</title>
|
|
<style>
|
|
body {{
|
|
font-family: Arial, sans-serif;
|
|
margin: 20px;
|
|
background-color: #f5f5f5;
|
|
}}
|
|
.container {{
|
|
max-width: 1200px;
|
|
margin: 0 auto;
|
|
background: white;
|
|
padding: 20px;
|
|
border-radius: 8px;
|
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
|
}}
|
|
.header {{
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
color: white;
|
|
padding: 20px;
|
|
text-align: center;
|
|
margin: -20px -20px 20px -20px;
|
|
border-radius: 8px 8px 0 0;
|
|
}}
|
|
.stats {{
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
gap: 15px;
|
|
margin-bottom: 30px;
|
|
}}
|
|
.stat-card {{
|
|
background: #f8f9fa;
|
|
padding: 15px;
|
|
border-radius: 6px;
|
|
text-align: center;
|
|
border: 1px solid #dee2e6;
|
|
}}
|
|
.stat-number {{
|
|
font-size: 2em;
|
|
font-weight: bold;
|
|
color: #333;
|
|
}}
|
|
.form-result {{
|
|
border: 1px solid #dee2e6;
|
|
border-radius: 6px;
|
|
margin-bottom: 20px;
|
|
overflow: hidden;
|
|
}}
|
|
.form-header {{
|
|
background: #e9ecef;
|
|
padding: 15px;
|
|
border-bottom: 1px solid #dee2e6;
|
|
}}
|
|
.form-content {{
|
|
padding: 15px;
|
|
}}
|
|
.success {{ color: #28a745; }}
|
|
.failure {{ color: #dc3545; }}
|
|
.warning {{ color: #ffc107; }}
|
|
.test-data {{
|
|
background: #f8f9fa;
|
|
padding: 10px;
|
|
border-radius: 4px;
|
|
margin: 10px 0;
|
|
font-family: monospace;
|
|
font-size: 0.9em;
|
|
}}
|
|
.validation-result {{
|
|
margin: 5px 0;
|
|
padding: 5px 10px;
|
|
border-radius: 4px;
|
|
border-left: 4px solid #dee2e6;
|
|
}}
|
|
.validation-passed {{
|
|
background: #d4edda;
|
|
border-left-color: #28a745;
|
|
}}
|
|
.validation-failed {{
|
|
background: #f8d7da;
|
|
border-left-color: #dc3545;
|
|
}}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
<div class="header">
|
|
<h1>🧪 MYP Formular Test Report</h1>
|
|
<p>Generiert am {time.strftime('%d.%m.%Y um %H:%M:%S')}</p>
|
|
</div>
|
|
|
|
<div class="stats">
|
|
<div class="stat-card">
|
|
<div class="stat-number">{total_forms}</div>
|
|
<div>Formulare getestet</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-number {'success' if successful_submissions == total_forms else 'warning' if successful_submissions > 0 else 'failure'}">{successful_submissions}</div>
|
|
<div>Erfolgreiche Submissions</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-number">{total_validations}</div>
|
|
<div>Validierungs-Tests</div>
|
|
</div>
|
|
<div class="stat-card">
|
|
<div class="stat-number {'success' if passed_validations == total_validations else 'warning' if passed_validations > 0 else 'failure'}">{passed_validations}</div>
|
|
<div>Validierungen bestanden</div>
|
|
</div>
|
|
</div>
|
|
|
|
<h2>📋 Detaillierte Ergebnisse</h2>
|
|
"""
|
|
|
|
# Formular-spezifische Ergebnisse
|
|
for result in test_results:
|
|
submission = result['submission_result']
|
|
success_class = 'success' if submission.get('success', False) else 'failure'
|
|
|
|
html_report += f"""
|
|
<div class="form-result">
|
|
<div class="form-header">
|
|
<h3>📝 {result['form_name']}</h3>
|
|
<p><strong>Method:</strong> {result['method']} | <strong>Action:</strong> {result['action']} | <strong>Felder:</strong> {result['fields_count']}</p>
|
|
</div>
|
|
<div class="form-content">
|
|
<h4>Submission-Ergebnis: <span class="{success_class}">{'✅ Erfolgreich' if submission.get('success', False) else '❌ Fehlgeschlagen'}</span></h4>
|
|
|
|
{f'<p><strong>Status Code:</strong> {submission.get("status_code", "N/A")}</p>' if 'status_code' in submission else ''}
|
|
{f'<p><strong>Fehler:</strong> {submission.get("error", "N/A")}</p>' if 'error' in submission else ''}
|
|
|
|
<h4>Test-Daten:</h4>
|
|
<div class="test-data">{json.dumps(result['test_data'], indent=2, ensure_ascii=False)}</div>
|
|
|
|
<h4>Validierungs-Ergebnisse:</h4>
|
|
"""
|
|
|
|
# Validierungs-Ergebnisse
|
|
for validation in result['validation_results']:
|
|
validation_class = 'validation-passed' if validation['passed'] else 'validation-failed'
|
|
status_icon = '✅' if validation['passed'] else '❌'
|
|
|
|
html_report += f"""
|
|
<div class="validation-result {validation_class}">
|
|
<strong>{status_icon} {validation['test_type']}</strong> - Feld: {validation['field']}
|
|
{f"<br>Wert: <code>{validation.get('value', 'N/A')}</code>" if 'value' in validation else ''}
|
|
{f"<br>Fehler: {', '.join(validation['errors'])}" if validation['errors'] else ''}
|
|
</div>
|
|
"""
|
|
|
|
html_report += f"""
|
|
<p><small>⏱️ Ausführungszeit: {result['execution_time']:.2f}s</small></p>
|
|
</div>
|
|
</div>
|
|
"""
|
|
|
|
html_report += """
|
|
</div>
|
|
</body>
|
|
</html>"""
|
|
|
|
try:
|
|
with open(output_file, 'w', encoding='utf-8') as f:
|
|
f.write(html_report)
|
|
print(f"📊 Report erstellt: {output_file}")
|
|
return output_file
|
|
except Exception as e:
|
|
print(f"❌ Fehler beim Report-Erstellen: {e}")
|
|
return ""
|
|
|
|
|
|
def main():
|
|
"""Haupt-CLI-Interface"""
|
|
|
|
if len(sys.argv) < 2:
|
|
print("""
|
|
🧪 Vereinfachter MYP Formular Tester
|
|
|
|
Verwendung:
|
|
python simple_form_tester.py <base_url> [path]
|
|
|
|
Beispiele:
|
|
python simple_form_tester.py http://localhost:5000
|
|
python simple_form_tester.py http://localhost:5000 /login
|
|
python simple_form_tester.py http://localhost:5000 /admin/add_printer
|
|
|
|
Testet alle Formulare auf der angegebenen Seite und generiert einen HTML-Report.
|
|
""")
|
|
return
|
|
|
|
base_url = sys.argv[1]
|
|
path = sys.argv[2] if len(sys.argv) > 2 else '/'
|
|
|
|
print(f"""
|
|
{'='*60}
|
|
🧪 MYP FORMULAR TESTER (Vereinfacht)
|
|
{'='*60}
|
|
|
|
🎯 Basis-URL: {base_url}
|
|
📍 Pfad: {path}
|
|
🕒 Gestartet: {time.strftime('%d.%m.%Y um %H:%M:%S')}
|
|
|
|
{'='*60}
|
|
""")
|
|
|
|
# Tester initialisieren
|
|
tester = SimpleFormTester(base_url)
|
|
|
|
# Tests ausführen
|
|
try:
|
|
results = tester.test_page_forms(path)
|
|
|
|
if results:
|
|
# Report generieren
|
|
report_file = tester.generate_report(results)
|
|
|
|
# Zusammenfassung
|
|
total_forms = len(results)
|
|
successful = len([r for r in results if r['submission_result'].get('success', False)])
|
|
|
|
print(f"""
|
|
{'='*60}
|
|
🎉 TESTS ABGESCHLOSSEN
|
|
{'='*60}
|
|
|
|
📊 ZUSAMMENFASSUNG:
|
|
• Formulare getestet: {total_forms}
|
|
• Erfolgreiche Submissions: {successful}
|
|
• Erfolgsrate: {successful/total_forms*100:.1f}%
|
|
|
|
📋 Report: {report_file}
|
|
|
|
{'='*60}
|
|
""")
|
|
else:
|
|
print("⚠️ Keine Formulare gefunden oder Fehler beim Laden der Seite")
|
|
|
|
except Exception as e:
|
|
print(f"❌ Fehler beim Testen: {e}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main() |