ibo/autofill.py
timu 650d7f3394 ibo: containerize + Gitea-CI + dev/prod-Deploy + Zitadel-SSO + noVNC-Live-View
- Dockerfile/compose: Flask-App + selenium/standalone-chrome (noVNC), Healthcheck
- app.py: /healthz + eingebettete noVNC-Live-Ansicht (NOVNC_URL, lokal:7900 / Cluster:/novnc/)
- Helm-Chart infra/k8s/base/ibo: App + Selenium-Sidecar, 2 Ports, Ingress
  (/ + /novnc via stripPrefix), SSO-Middlewares aus dem CD-Repo
- Gitea-CI: version -> test -> build-push -> helm-validate -> deploy-dev (Write-back)
- .gitignore/.dockerignore (Dev-Samples raus)
- infra/RUNBOOK-ibo.md: Zitadel/DNS/Deploy/Prod-Promote

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-14 01:03:32 +02:00

196 lines
7.3 KiB
Python

#!/usr/bin/env python3
"""
Autofill-Script fuer das IBO-Angular-Formular (ng-zorro / Formly).
Fuellt alle Inputfelder mit Mock-Daten. Weil es ein Angular-Formular ist,
reicht ein einfaches value-Setzen nicht -> es werden echte input/change-Events
via Selenium ausgeloest, damit Angular die Werte uebernimmt.
Setup:
pip install selenium
(Chrome + passender chromedriver muessen installiert sein;
bei aktuellem Selenium 4 wird der Driver automatisch verwaltet.)
Benutzung:
python autofill.py "http://localhost:4200/dein/formular"
# oder eine lokale Datei:
python autofill.py "file:///Users/timu/programming/momentrum/projekte/ibo/index.html"
"""
import sys
import time
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
# ---------------------------------------------------------------------------
# Felder werden aus config.json (application) gebaut. Siehe config.py.
# Die formly-IDs stammen aus dem gerenderten Formular (data-cy / id).
# ---------------------------------------------------------------------------
import config as _config
def _build_fields(app):
t1, t2 = app["tenant1"], app["tenant2"]
text_fields = {
# Vorgang
"formly_2_input_reserveNumber_0": app["reserveNumber"],
"formly_2_input_validUntil_1": app["validUntil"],
# Mieter 1
"formly_3_input_firstName_0": t1["firstName"],
"formly_3_input_lastName_1": t1["lastName"],
"formly_3_input_dateOfBirth_2": t1["dateOfBirth"],
"formly_4_input_street_0": t1["street"],
"formly_4_input_housenumber_1": t1["housenumber"],
"formly_4_input_zipcode_2": t1["zipcode"],
"formly_4_input_city_3": t1["city"],
"formly_5_input_phoneNumberPrivate_0": t1["phonePrivate"],
"formly_5_input_phoneNumberBusiness_1": t1["phoneBusiness"],
# Mieter 2
"formly_6_input_firstName2_0": t2["firstName"],
"formly_6_input_lastName2_1": t2["lastName"],
"formly_6_input_dateOfBirth2_2": t2["dateOfBirth"],
"formly_7_input_street2_0": t2["street"],
"formly_7_input_housenumber2_1": t2["housenumber"],
"formly_7_input_zipcode2_2": t2["zipcode"],
"formly_7_input_city2_3": t2["city"],
"formly_8_input_phoneNumberPrivate2_0": t2["phonePrivate"],
"formly_8_input_phoneNumberBusiness2_1": t2["phoneBusiness"],
}
checkboxes = []
if app.get("secondTenant", True):
checkboxes.append("formly_5_checkbox_secondTenant_5")
if app.get("consent", True):
checkboxes += ["formly_18_checkbox_dataCorrectness_20",
"formly_18_checkbox_dataPrivacy_21"]
selects = {"formly_2_select_numberRooms_2": str(app["numberRooms"])}
return text_fields, checkboxes, selects
TEXT_FIELDS, CHECKBOXES, SELECTS = _build_fields(_config.CFG["application"])
def reload_fields():
"""Uebernimmt geaenderte config.json-Werte."""
global TEXT_FIELDS, CHECKBOXES, SELECTS
TEXT_FIELDS, CHECKBOXES, SELECTS = _build_fields(
_config.reload()["application"])
# ---------------------------------------------------------------------------
# Fuell-Logik
# ---------------------------------------------------------------------------
def fill_text(driver, wait, field_id, value):
"""Fuellt ein Textfeld und loest Angular-Events aus."""
try:
el = wait.until(EC.presence_of_element_located((By.ID, field_id)))
driver.execute_script("arguments[0].scrollIntoView({block:'center'});", el)
el.clear()
el.click()
el.send_keys(value)
# blur -> Angular validiert / uebernimmt
el.send_keys(Keys.TAB)
print(f" [ok] {field_id} = {value}")
except Exception as e:
print(f" [skip] {field_id}: {e.__class__.__name__}")
def set_checkbox(driver, field_id, checked=True):
try:
el = driver.find_element(By.ID, field_id)
driver.execute_script("arguments[0].scrollIntoView({block:'center'});", el)
if el.is_selected() != checked:
driver.execute_script("arguments[0].click();", el)
print(f" [ok] checkbox {field_id} -> {checked}")
except Exception as e:
print(f" [skip] checkbox {field_id}: {e.__class__.__name__}")
def select_option(driver, wait, field_id, option_text):
"""Bedient ein @ng-select/ng-select Dropdown (nicht ng-zorro!).
Struktur: Input role=combobox oeffnet ein .ng-dropdown-panel mit
Optionen als <div class="ng-option">...</div>.
"""
try:
trigger = wait.until(EC.element_to_be_clickable((By.ID, field_id)))
driver.execute_script("arguments[0].scrollIntoView({block:'center'});", trigger)
# Den umschliessenden .ng-select-Container klicken -> oeffnet Panel zuverlaessig
container = driver.execute_script(
"return arguments[0].closest('.ng-select');", trigger) or trigger
container.click()
time.sleep(0.4)
# Passende Option im Dropdown-Panel finden (erst exakt, dann enthaelt)
opt_xpath = (
"//div[contains(@class,'ng-option')]"
f"[normalize-space()='{option_text}']"
" | //div[contains(@class,'ng-option')]"
f"[contains(normalize-space(),'{option_text}')]"
)
option = wait.until(EC.element_to_be_clickable((By.XPATH, opt_xpath)))
option.click()
print(f" [ok] select {field_id} = {option_text}")
except Exception as e:
# Fallback: ng-select ist durchsuchbar -> tippen + Enter
try:
trigger.send_keys(option_text)
time.sleep(0.4)
trigger.send_keys(Keys.ENTER)
print(f" [ok] select {field_id} = {option_text} (getippt)")
except Exception:
print(f" [skip] select {field_id}: {e.__class__.__name__}")
def main():
url = sys.argv[1] if len(sys.argv) > 1 else \
"file:///Users/timu/programming/momentrum/projekte/ibo/index.html"
options = webdriver.ChromeOptions()
# options.add_argument("--headless=new") # bei Bedarf ohne sichtbares Fenster
driver = webdriver.Chrome(options=options)
wait = WebDriverWait(driver, 10)
try:
print(f"Oeffne {url}")
driver.get(url)
time.sleep(1.5) # Angular-Bootstrap abwarten
# Erst zweiten Mieter aktivieren (schaltet dessen Felder frei)
print("Checkbox 'zweiter Mieter':")
set_checkbox(driver, "formly_5_checkbox_secondTenant_5", True)
time.sleep(0.5)
print("Dropdowns:")
for fid, opt in SELECTS.items():
select_option(driver, wait, fid, opt)
print("Textfelder:")
for fid, val in TEXT_FIELDS.items():
fill_text(driver, wait, fid, val)
print("Zustimmungs-Checkboxen:")
for cid in CHECKBOXES:
if cid == "formly_5_checkbox_secondTenant_5":
continue # bereits gesetzt
set_checkbox(driver, cid, True)
print("\nFertig. Fenster bleibt offen. Programm laeuft weiter - "
"mit Strg+C beenden (oder Browser-Fenster schliessen).")
# Programm bewusst NICHT beenden -> Browser bleibt offen.
while True:
time.sleep(3600)
except KeyboardInterrupt:
print("\nBeende ...")
driver.quit()
if __name__ == "__main__":
main()