- 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>
196 lines
7.3 KiB
Python
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()
|