🎉 Improved form testing infrastructure with new files: 'simple_form_tester.py', 'ui_components.html' templates, and updated test suite in 'form_test_automator.py'. 📚
This commit is contained in:
635
backend/simple_form_tester.py
Normal file
635
backend/simple_form_tester.py
Normal file
@@ -0,0 +1,635 @@
|
||||
#!/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()
|
Reference in New Issue
Block a user