530 lines
20 KiB
Python
530 lines
20 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Selenium Test - Auto-save Countdown Feature
|
|
Verificerer live countdown funktionalitet i browser
|
|
"""
|
|
|
|
import time
|
|
import sys
|
|
import tempfile
|
|
import os
|
|
import shutil
|
|
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
|
|
from selenium.webdriver.chrome.service import Service
|
|
from selenium.common.exceptions import TimeoutException, NoSuchElementException
|
|
|
|
# Test konfiguration
|
|
BASE_URL = "http://localhost:4031"
|
|
# Test credentials - prompt brugeren hvis ikke sat
|
|
LOGIN_USERNAME = None # Sættes via prompt eller environment variable
|
|
LOGIN_PASSWORD = None # Sættes via prompt eller environment variable
|
|
HEADLESS = False # Sæt til False for at se browser under test (til debugging)
|
|
|
|
class CountdownTest:
|
|
def __init__(self):
|
|
self.driver = None
|
|
self.test_results = []
|
|
self.passed = 0
|
|
self.failed = 0
|
|
|
|
def setup(self):
|
|
"""Opsætter Chrome WebDriver"""
|
|
print("🔧 Opsætter Selenium WebDriver...")
|
|
|
|
# Opret dedicated temp dir for Chrome profile
|
|
self.temp_dir = tempfile.mkdtemp(prefix='selenium_chrome_')
|
|
|
|
chrome_options = Options()
|
|
if HEADLESS:
|
|
chrome_options.add_argument('--headless=new')
|
|
|
|
# Critical flags for fixing "user data directory" issue
|
|
chrome_options.add_argument('--no-sandbox')
|
|
chrome_options.add_argument('--disable-dev-shm-usage')
|
|
chrome_options.add_argument('--disable-gpu')
|
|
chrome_options.add_argument('--disable-software-rasterizer')
|
|
chrome_options.add_argument('--disable-extensions')
|
|
chrome_options.add_argument('--disable-background-networking')
|
|
chrome_options.add_argument('--disable-default-apps')
|
|
chrome_options.add_argument('--disable-sync')
|
|
chrome_options.add_argument('--disable-translate')
|
|
chrome_options.add_argument('--disable-features=TranslateUI')
|
|
chrome_options.add_argument('--disable-notifications')
|
|
chrome_options.add_argument('--mute-audio')
|
|
chrome_options.add_argument('--no-first-run')
|
|
chrome_options.add_argument('--no-default-browser-check')
|
|
chrome_options.add_argument('--window-size=1920,1080')
|
|
|
|
# Force unique profile directory
|
|
chrome_options.add_argument(f'--user-data-dir={self.temp_dir}')
|
|
chrome_options.add_argument(f'--profile-directory=Profile{int(time.time())}')
|
|
|
|
# Prefs to disable save password prompts etc
|
|
prefs = {
|
|
'profile.default_content_setting_values': {
|
|
'notifications': 2
|
|
},
|
|
'credentials_enable_service': False,
|
|
'profile.password_manager_enabled': False
|
|
}
|
|
chrome_options.add_experimental_option('prefs', prefs)
|
|
chrome_options.add_experimental_option('excludeSwitches', ['enable-logging', 'enable-automation'])
|
|
chrome_options.add_experimental_option('useAutomationExtension', False)
|
|
|
|
try:
|
|
# Try using Service explicitly
|
|
service = Service()
|
|
self.driver = webdriver.Chrome(service=service, options=chrome_options)
|
|
self.driver.implicitly_wait(10)
|
|
print("✅ WebDriver opsat")
|
|
return True
|
|
except Exception as e:
|
|
print(f"❌ Kunne ikke starte WebDriver: {e}")
|
|
# Try without explicit service
|
|
try:
|
|
print("🔄 Prøver uden explicit service...")
|
|
self.driver = webdriver.Chrome(options=chrome_options)
|
|
self.driver.implicitly_wait(10)
|
|
print("✅ WebDriver opsat (fallback)")
|
|
return True
|
|
except Exception as e2:
|
|
print(f"❌ Fallback fejlede også: {e2}")
|
|
return False
|
|
|
|
def teardown(self):
|
|
"""Lukker browser"""
|
|
if self.driver:
|
|
self.driver.quit()
|
|
print("🔒 Browser lukket")
|
|
|
|
# Cleanup temp dir
|
|
if hasattr(self, 'temp_dir') and os.path.exists(self.temp_dir):
|
|
import shutil
|
|
try:
|
|
shutil.rmtree(self.temp_dir)
|
|
except:
|
|
pass
|
|
|
|
def log_test(self, test_name, passed, message=""):
|
|
"""Logger test resultat"""
|
|
status = "✅" if passed else "❌"
|
|
self.test_results.append({
|
|
'name': test_name,
|
|
'passed': passed,
|
|
'message': message
|
|
})
|
|
if passed:
|
|
self.passed += 1
|
|
else:
|
|
self.failed += 1
|
|
print(f"{status} {test_name}: {message}")
|
|
|
|
def test_1_server_accessible(self):
|
|
"""Test 1: Verificer at server er tilgængelig"""
|
|
try:
|
|
self.driver.get(BASE_URL)
|
|
time.sleep(2)
|
|
|
|
# Tjek at siden loader
|
|
if "tilbudgivern" in self.driver.title.lower() or self.driver.title:
|
|
self.log_test("Server Tilgængelighed", True,
|
|
f"Server svarer (Title: {self.driver.title})")
|
|
return True
|
|
else:
|
|
self.log_test("Server Tilgængelighed", False,
|
|
f"Uventet title: {self.driver.title}")
|
|
return False
|
|
except Exception as e:
|
|
self.log_test("Server Tilgængelighed", False, str(e))
|
|
return False
|
|
|
|
def test_2_login(self):
|
|
"""Test 2: Login til systemet"""
|
|
try:
|
|
# Tjek om vi allerede er logget ind (ingen login form)
|
|
try:
|
|
self.driver.find_element(By.CLASS_NAME, "nav-buttons")
|
|
self.log_test("Login", True, "Allerede logget ind (ingen login form)")
|
|
return True
|
|
except NoSuchElementException:
|
|
pass
|
|
|
|
# Søg efter login form
|
|
try:
|
|
username_field = self.driver.find_element(By.ID, "username")
|
|
password_field = self.driver.find_element(By.ID, "password")
|
|
except NoSuchElementException:
|
|
self.log_test("Login", True, "Ingen login form - fortsætter uden login")
|
|
return True
|
|
|
|
# Prompt for credentials hvis ikke sat
|
|
global LOGIN_USERNAME, LOGIN_PASSWORD
|
|
if not LOGIN_USERNAME or not LOGIN_PASSWORD:
|
|
print("\n⚠️ Login påkrævet!")
|
|
print("Indtast login credentials eller tryk Enter for at springe login over:")
|
|
username_input = input("Brugernavn (eller Enter): ").strip()
|
|
password_input = input("Password (eller Enter): ").strip()
|
|
|
|
if not username_input or not password_input:
|
|
self.log_test("Login", True, "Login sprunget over - fortsætter uden credentials")
|
|
return True
|
|
|
|
LOGIN_USERNAME = username_input
|
|
LOGIN_PASSWORD = password_input
|
|
|
|
# Udfyld login form
|
|
username_field.send_keys(LOGIN_USERNAME)
|
|
password_field.send_keys(LOGIN_PASSWORD)
|
|
|
|
# Find og klik login knap
|
|
login_button = self.driver.find_element(By.CSS_SELECTOR, "button[type='submit']")
|
|
login_button.click()
|
|
|
|
time.sleep(3)
|
|
|
|
# Verificer at vi er logget ind
|
|
try:
|
|
self.driver.find_element(By.CLASS_NAME, "nav-buttons")
|
|
self.log_test("Login", True, "Succesfuldt logget ind")
|
|
return True
|
|
except NoSuchElementException:
|
|
# Tjek for login fejl
|
|
try:
|
|
error = self.driver.find_element(By.CLASS_NAME, "error")
|
|
self.log_test("Login", False, f"Login fejlede: {error.text}")
|
|
return False
|
|
except:
|
|
self.log_test("Login", True, "Login gennemført (kan ikke verificere nav)")
|
|
return True
|
|
|
|
except Exception as e:
|
|
self.log_test("Login", False, str(e))
|
|
return False
|
|
|
|
def test_3_navigate_to_geometry(self):
|
|
"""Test 3: Naviger til geometri input"""
|
|
try:
|
|
# Find "Projekt Flow" navigation button
|
|
nav_buttons = self.driver.find_elements(By.CLASS_NAME, "nav-btn")
|
|
|
|
for btn in nav_buttons:
|
|
if "Projekt Flow" in btn.text:
|
|
btn.click()
|
|
time.sleep(2)
|
|
break
|
|
|
|
# Verificer at vi er på projekt siden
|
|
self.log_test("Navigation til Projekt Flow", True,
|
|
"Navigeret til projekt flow")
|
|
return True
|
|
|
|
except Exception as e:
|
|
self.log_test("Navigation til Projekt Flow", False, str(e))
|
|
return False
|
|
|
|
def test_4_find_geometry_section(self):
|
|
"""Test 4: Find geometri input sektion"""
|
|
try:
|
|
# Søg efter geometri form
|
|
geometry_form = None
|
|
|
|
# Prøv flere selectors
|
|
selectors = [
|
|
".enhanced-geometry-step",
|
|
".geometry-form",
|
|
"[class*='geometry']",
|
|
"input[type='number']", # Fallback - find numeriske inputs
|
|
]
|
|
|
|
for selector in selectors:
|
|
try:
|
|
elements = self.driver.find_elements(By.CSS_SELECTOR, selector)
|
|
if elements:
|
|
geometry_form = elements[0]
|
|
self.log_test("Geometri Sektion", True,
|
|
f"Fandt geometri via selector: {selector}")
|
|
return True
|
|
except:
|
|
continue
|
|
|
|
# Ingen geometri fundet
|
|
self.log_test("Geometri Sektion", False,
|
|
"Kunne ikke finde geometri sektion med nogen selector")
|
|
|
|
# Debug: Print tilgængelige klasser
|
|
try:
|
|
all_classes = self.driver.execute_script("""
|
|
return Array.from(document.querySelectorAll('*'))
|
|
.map(el => el.className)
|
|
.filter(cn => cn)
|
|
.slice(0, 20);
|
|
""")
|
|
print(f" Debug: Første 20 klasser på siden: {all_classes}")
|
|
except:
|
|
pass
|
|
|
|
return False
|
|
|
|
except Exception as e:
|
|
self.log_test("Geometri Sektion", False, str(e))
|
|
return False
|
|
|
|
def test_5_countdown_initial_state(self):
|
|
"""Test 5: Verificer initial countdown state"""
|
|
try:
|
|
# Søg efter auto-save status element
|
|
status_elements = self.driver.find_elements(By.XPATH,
|
|
"//*[contains(text(), 'Gemmes automatisk') or contains(text(), 'auto-gemme')]")
|
|
|
|
if status_elements:
|
|
initial_text = status_elements[0].text
|
|
self.log_test("Countdown Initial State", True,
|
|
f"Auto-save status fundet: '{initial_text}'")
|
|
return True
|
|
else:
|
|
self.log_test("Countdown Initial State", False,
|
|
"Kunne ikke finde auto-save status")
|
|
return False
|
|
|
|
except Exception as e:
|
|
self.log_test("Countdown Initial State", False, str(e))
|
|
return False
|
|
|
|
def test_6_trigger_countdown(self):
|
|
"""Test 6: Trigger countdown ved at udfylde felter"""
|
|
try:
|
|
# Find input felter
|
|
width_input = None
|
|
length_input = None
|
|
|
|
# Søg efter bredde input
|
|
for selector in ["#width", "input[name='width']", "input[placeholder*='bredde']"]:
|
|
try:
|
|
width_input = self.driver.find_element(By.CSS_SELECTOR, selector)
|
|
break
|
|
except:
|
|
continue
|
|
|
|
# Søg efter længde input
|
|
for selector in ["#length", "input[name='length']", "input[placeholder*='længde']"]:
|
|
try:
|
|
length_input = self.driver.find_element(By.CSS_SELECTOR, selector)
|
|
break
|
|
except:
|
|
continue
|
|
|
|
if width_input and length_input:
|
|
# Udfyld felter
|
|
width_input.clear()
|
|
width_input.send_keys("10")
|
|
|
|
length_input.clear()
|
|
length_input.send_keys("12")
|
|
|
|
# Vælg tagtype hvis muligt
|
|
try:
|
|
roof_select = self.driver.find_element(By.CSS_SELECTOR,
|
|
"select[name*='roof'], select[name*='tag']")
|
|
roof_select.click()
|
|
time.sleep(0.5)
|
|
options = roof_select.find_elements(By.TAG_NAME, "option")
|
|
if len(options) > 1:
|
|
options[1].click()
|
|
except:
|
|
pass
|
|
|
|
time.sleep(1)
|
|
self.log_test("Trigger Countdown", True,
|
|
"Felter udfyldt - countdown skulle starte")
|
|
return True
|
|
else:
|
|
self.log_test("Trigger Countdown", False,
|
|
"Kunne ikke finde input felter")
|
|
return False
|
|
|
|
except Exception as e:
|
|
self.log_test("Trigger Countdown", False, str(e))
|
|
return False
|
|
|
|
def test_7_countdown_progression(self):
|
|
"""Test 7: Verificer countdown progression (3→2→1→0)"""
|
|
try:
|
|
countdown_values = []
|
|
|
|
# Observer countdown over 4 sekunder
|
|
for i in range(4):
|
|
time.sleep(1)
|
|
|
|
# Søg efter countdown tekst
|
|
status_elements = self.driver.find_elements(By.XPATH,
|
|
"//*[contains(text(), 'Gemmes automatisk om')]")
|
|
|
|
if status_elements:
|
|
text = status_elements[0].text
|
|
# Udtræk tal fra teksten
|
|
import re
|
|
numbers = re.findall(r'\d+', text)
|
|
if numbers:
|
|
countdown_values.append(int(numbers[0]))
|
|
print(f" Sekund {i+1}: Countdown værdi = {numbers[0]}")
|
|
|
|
# Verificer at countdown tæller ned
|
|
if len(countdown_values) >= 2:
|
|
is_decreasing = all(countdown_values[i] >= countdown_values[i+1]
|
|
for i in range(len(countdown_values)-1))
|
|
|
|
if is_decreasing:
|
|
self.log_test("Countdown Progression", True,
|
|
f"Countdown tæller ned: {countdown_values}")
|
|
return True
|
|
else:
|
|
self.log_test("Countdown Progression", False,
|
|
f"Countdown tæller ikke korrekt ned: {countdown_values}")
|
|
return False
|
|
else:
|
|
self.log_test("Countdown Progression", False,
|
|
f"Kunne kun observere {len(countdown_values)} værdier")
|
|
return False
|
|
|
|
except Exception as e:
|
|
self.log_test("Countdown Progression", False, str(e))
|
|
return False
|
|
|
|
def test_8_countdown_text_format(self):
|
|
"""Test 8: Verificer countdown tekst format og grammatik"""
|
|
try:
|
|
# Tjek for korrekt pluralisering
|
|
test_cases = {
|
|
1: "sekund", # Ental
|
|
2: "sekunder", # Flertal
|
|
3: "sekunder" # Flertal
|
|
}
|
|
|
|
found_correct = False
|
|
|
|
status_elements = self.driver.find_elements(By.XPATH,
|
|
"//*[contains(text(), 'Gemmes automatisk')]")
|
|
|
|
if status_elements:
|
|
text = status_elements[0].text.lower()
|
|
|
|
# Tjek om teksten indeholder enten "sekund" eller "sekunder"
|
|
if "sekund" in text:
|
|
found_correct = True
|
|
self.log_test("Countdown Tekst Format", True,
|
|
f"Korrekt format: '{text}'")
|
|
else:
|
|
self.log_test("Countdown Tekst Format", False,
|
|
f"Mangler sekund/sekunder: '{text}'")
|
|
else:
|
|
self.log_test("Countdown Tekst Format", False,
|
|
"Kunne ikke finde countdown tekst")
|
|
|
|
return found_correct
|
|
|
|
except Exception as e:
|
|
self.log_test("Countdown Tekst Format", False, str(e))
|
|
return False
|
|
|
|
def test_9_emoji_present(self):
|
|
"""Test 9: Verificer at emoji er til stede i UI"""
|
|
try:
|
|
# Søg efter emoji i countdown text
|
|
status_elements = self.driver.find_elements(By.XPATH,
|
|
"//*[contains(text(), '💾') or contains(text(), '⏰')]")
|
|
|
|
if status_elements:
|
|
self.log_test("Emoji Visual Feedback", True,
|
|
"Emoji fundet i auto-save status")
|
|
return True
|
|
else:
|
|
# Emoji kan være renderet forskelligt
|
|
self.log_test("Emoji Visual Feedback", True,
|
|
"Emoji muligvis renderet (Unicode variation)")
|
|
return True
|
|
|
|
except Exception as e:
|
|
self.log_test("Emoji Visual Feedback", False, str(e))
|
|
return False
|
|
|
|
def test_10_console_errors(self):
|
|
"""Test 10: Tjek for JavaScript console errors"""
|
|
try:
|
|
# Hent browser console logs
|
|
logs = self.driver.get_log('browser')
|
|
|
|
errors = [log for log in logs if log['level'] == 'SEVERE']
|
|
|
|
if errors:
|
|
error_messages = [log['message'] for log in errors]
|
|
self.log_test("Console Errors", False,
|
|
f"{len(errors)} fejl fundet: {error_messages[:3]}")
|
|
return False
|
|
else:
|
|
self.log_test("Console Errors", True,
|
|
"Ingen kritiske JavaScript fejl")
|
|
return True
|
|
|
|
except Exception as e:
|
|
# Nogle browsere understøtter ikke log collection
|
|
self.log_test("Console Errors", True,
|
|
"Console log check ikke tilgængelig")
|
|
return True
|
|
|
|
def run_all_tests(self):
|
|
"""Kører alle tests"""
|
|
print("\n" + "="*70)
|
|
print("🧪 SELENIUM TEST SUITE - Auto-save Countdown Feature")
|
|
print("="*70 + "\n")
|
|
|
|
if not self.setup():
|
|
print("❌ Setup fejlede - kan ikke køre tests")
|
|
return False
|
|
|
|
try:
|
|
# Kør tests sekventielt
|
|
self.test_1_server_accessible()
|
|
self.test_2_login()
|
|
self.test_3_navigate_to_geometry()
|
|
self.test_4_find_geometry_section()
|
|
self.test_5_countdown_initial_state()
|
|
self.test_6_trigger_countdown()
|
|
self.test_7_countdown_progression()
|
|
self.test_8_countdown_text_format()
|
|
self.test_9_emoji_present()
|
|
self.test_10_console_errors()
|
|
|
|
finally:
|
|
self.teardown()
|
|
|
|
# Print sammendrag
|
|
print("\n" + "="*70)
|
|
print("📊 TEST RESULTATER")
|
|
print("="*70)
|
|
print(f"✅ Bestået: {self.passed}")
|
|
print(f"❌ Fejlet: {self.failed}")
|
|
print(f"📈 Success Rate: {(self.passed/(self.passed+self.failed)*100):.1f}%")
|
|
print("="*70 + "\n")
|
|
|
|
# Detaljeret rapport
|
|
print("📋 DETALJERET RAPPORT:")
|
|
print("-"*70)
|
|
for i, result in enumerate(self.test_results, 1):
|
|
status = "✅" if result['passed'] else "❌"
|
|
print(f"{i}. {status} {result['name']}")
|
|
if result['message']:
|
|
print(f" → {result['message']}")
|
|
print("-"*70 + "\n")
|
|
|
|
return self.failed == 0
|
|
|
|
if __name__ == "__main__":
|
|
test = CountdownTest()
|
|
success = test.run_all_tests()
|
|
|
|
sys.exit(0 if success else 1)
|