diff --git a/backend/form_test_automator.py b/backend/form_test_automator.py index b7005aa26..13e3738c4 100644 --- a/backend/form_test_automator.py +++ b/backend/form_test_automator.py @@ -30,6 +30,10 @@ try: except ImportError: PLAYWRIGHT_AVAILABLE = False print("⚠️ Playwright nicht verfügbar. Installiere mit: pip install playwright") + # Fallback-Klassen für Type Hints + class Page: pass + class Browser: pass + class BrowserContext: pass # Test-Daten-Generierung try: @@ -38,6 +42,20 @@ try: except ImportError: FAKER_AVAILABLE = False print("⚠️ Faker nicht verfügbar. Installiere mit: pip install faker") + # Fallback-Klasse + class Faker: + def __init__(self, locale='de_DE'): pass + def email(self): return "test@example.com" + def name(self): return "Test User" + def first_name(self): return "Test" + def last_name(self): return "User" + def company(self): return "Test Company" + def city(self): return "Test City" + def street_address(self): return "Test Street 123" + def phone_number(self): return "+49 123 4567890" + def url(self): return "https://example.com" + def date(self): return "2024-06-18" + def text(self, max_nb_chars=200): return "Test text" # HTML-Parsing try: @@ -46,6 +64,9 @@ try: except ImportError: BS4_AVAILABLE = False print("⚠️ BeautifulSoup nicht verfügbar. Installiere mit: pip install beautifulsoup4") + # Fallback-Klasse + class BeautifulSoup: + def __init__(self, *args, **kwargs): pass # Rich Console für schöne Ausgabe try: @@ -59,6 +80,13 @@ try: except ImportError: RICH_AVAILABLE = False console = None + # Fallback-Klassen + class Console: + def print(self, *args, **kwargs): print(*args) + class Table: + def __init__(self, *args, **kwargs): pass + def add_column(self, *args, **kwargs): pass + def add_row(self, *args, **kwargs): pass class TestStatus(Enum): diff --git a/backend/simple_form_tester.py b/backend/simple_form_tester.py new file mode 100644 index 000000000..fa279be23 --- /dev/null +++ b/backend/simple_form_tester.py @@ -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""" + +
+ + +Generiert am {time.strftime('%d.%m.%Y um %H:%M:%S')}
+Method: {result['method']} | Action: {result['action']} | Felder: {result['fields_count']}
+Status Code: {submission.get("status_code", "N/A")}
' if 'status_code' in submission else ''} + {f'Fehler: {submission.get("error", "N/A")}
' if 'error' in submission else ''} + +{validation.get('value', 'N/A')}
" if 'value' in validation else ''}
+ {f"⏱️ Ausführungszeit: {result['execution_time']:.2f}s
+{{ message }}