Fix Stadt und Land: embedded form with two-step submit

- Form is embedded directly on page (no iframe)
- Fields: name, surname, street, houseNo, postalCode, city, phone, email
- Checkboxes: privacy, provision
- Step 1: Click 'Eingaben prüfen' to validate
- Step 2: Click 'Absenden' to submit
This commit is contained in:
Aron Petau 2025-12-09 14:24:19 +01:00
parent 9b322d72ca
commit 180666c781

View file

@ -912,9 +912,11 @@ class ApplicationHandler:
async def _apply_stadtundland(self, listing: dict, result: dict) -> dict: async def _apply_stadtundland(self, listing: dict, result: dict) -> dict:
""" """
Stadt und Land uses Wohnungshelden (app.wohnungshelden.de) for their application system. Stadt und Land has an embedded contact form directly on their listing page.
The application form is loaded in an iframe from a different domain. No iframe - the form fields are directly accessible.
We need to navigate directly to the iframe URL. Fields: name, surname, street, houseNo, postalCode, city, phone, email
Checkboxes: privacy, provision
Submit: "Eingaben prüfen"
""" """
page = await self.context.new_page() page = await self.context.new_page()
try: try:
@ -932,180 +934,196 @@ class ApplicationHandler:
await asyncio.sleep(1) await asyncio.sleep(1)
except: pass except: pass
# Look for "Kontakt" or "Anfragen" button to open the form # Scroll down to the contact form
logger.info("[STADTUNDLAND] Looking for contact button...") await page.evaluate("window.scrollBy(0, 500)")
apply_btn = await page.query_selector('a:has-text("Kontakt"), button:has-text("Kontakt"), a:has-text("Anfragen"), button:has-text("Anfragen"), a:has-text("kontaktieren"), button:has-text("kontaktieren")') await asyncio.sleep(0.5)
if apply_btn and await apply_btn.is_visible():
logger.info("[STADTUNDLAND] Found contact button, clicking...")
await apply_btn.click()
await asyncio.sleep(3)
# Stadt und Land uses Wohnungshelden iframe for the application form # Take initial screenshot
iframe_element = await page.query_selector('iframe[src*="wohnungshelden.de"]') screenshot_path = DATA_DIR / f"stadtundland_page_{listing['id']}.png"
if iframe_element: await page.screenshot(path=str(screenshot_path), full_page=True)
iframe_url = await iframe_element.get_attribute('src') logger.info(f"[STADTUNDLAND] Saved page screenshot to {screenshot_path}")
logger.info(f"[STADTUNDLAND] Found Wohnungshelden iframe: {iframe_url}")
# Navigate to the iframe URL directly in a new page for full access # Fill out the embedded form directly
iframe_page = await self.context.new_page() form_filled = False
# Vorname (name field)
try:
vorname_field = await page.query_selector('input[name="name"]')
if vorname_field and await vorname_field.is_visible():
await vorname_field.fill(FORM_VORNAME)
logger.info(f"[STADTUNDLAND] Filled Vorname: {FORM_VORNAME}")
form_filled = True
except Exception as e:
logger.warning(f"[STADTUNDLAND] Could not fill Vorname: {e}")
# Nachname (surname field)
try:
nachname_field = await page.query_selector('input[name="surname"]')
if nachname_field and await nachname_field.is_visible():
await nachname_field.fill(FORM_NACHNAME)
logger.info(f"[STADTUNDLAND] Filled Nachname: {FORM_NACHNAME}")
form_filled = True
except Exception as e:
logger.warning(f"[STADTUNDLAND] Could not fill Nachname: {e}")
# Straße (street field)
try:
street_field = await page.query_selector('input[name="street"]')
if street_field and await street_field.is_visible():
await street_field.fill(FORM_STRASSE)
logger.info(f"[STADTUNDLAND] Filled Straße: {FORM_STRASSE}")
form_filled = True
except Exception as e:
logger.warning(f"[STADTUNDLAND] Could not fill Straße: {e}")
# Hausnummer (houseNo field)
try:
house_field = await page.query_selector('input[name="houseNo"]')
if house_field and await house_field.is_visible():
await house_field.fill(FORM_HAUSNUMMER)
logger.info(f"[STADTUNDLAND] Filled Hausnummer: {FORM_HAUSNUMMER}")
form_filled = True
except Exception as e:
logger.warning(f"[STADTUNDLAND] Could not fill Hausnummer: {e}")
# PLZ (postalCode field)
try:
plz_field = await page.query_selector('input[name="postalCode"]')
if plz_field and await plz_field.is_visible():
await plz_field.fill(FORM_PLZ)
logger.info(f"[STADTUNDLAND] Filled PLZ: {FORM_PLZ}")
form_filled = True
except Exception as e:
logger.warning(f"[STADTUNDLAND] Could not fill PLZ: {e}")
# Ort (city field)
try:
city_field = await page.query_selector('input[name="city"]')
if city_field and await city_field.is_visible():
await city_field.fill(FORM_ORT)
logger.info(f"[STADTUNDLAND] Filled Ort: {FORM_ORT}")
form_filled = True
except Exception as e:
logger.warning(f"[STADTUNDLAND] Could not fill Ort: {e}")
# Telefon (phone field)
try:
phone_field = await page.query_selector('input[name="phone"]')
if phone_field and await phone_field.is_visible():
await phone_field.fill(FORM_PHONE)
logger.info(f"[STADTUNDLAND] Filled Telefon: {FORM_PHONE}")
form_filled = True
except Exception as e:
logger.warning(f"[STADTUNDLAND] Could not fill Telefon: {e}")
# E-Mail (email field)
try:
email_field = await page.query_selector('input[name="email"]')
if email_field and await email_field.is_visible():
await email_field.fill(FORM_EMAIL)
logger.info(f"[STADTUNDLAND] Filled E-Mail: {FORM_EMAIL}")
form_filled = True
except Exception as e:
logger.warning(f"[STADTUNDLAND] Could not fill E-Mail: {e}")
# Click privacy checkbox
try:
privacy_checkbox = await page.query_selector('input[name="privacy"]')
if privacy_checkbox and await privacy_checkbox.is_visible():
if not await privacy_checkbox.is_checked():
await privacy_checkbox.click()
logger.info("[STADTUNDLAND] Clicked privacy checkbox")
except Exception as e:
logger.warning(f"[STADTUNDLAND] Could not click privacy checkbox: {e}")
# Click provision checkbox (optional)
try:
provision_checkbox = await page.query_selector('input[name="provision"]')
if provision_checkbox and await provision_checkbox.is_visible():
if not await provision_checkbox.is_checked():
await provision_checkbox.click()
logger.info("[STADTUNDLAND] Clicked provision checkbox")
except Exception as e:
logger.warning(f"[STADTUNDLAND] Could not click provision checkbox: {e}")
await asyncio.sleep(1)
# Take screenshot after filling form
screenshot_path = DATA_DIR / f"stadtundland_filled_{listing['id']}.png"
await page.screenshot(path=str(screenshot_path), full_page=True)
logger.info(f"[STADTUNDLAND] Saved filled form screenshot to {screenshot_path}")
# Submit form
if form_filled:
try: try:
await iframe_page.goto(iframe_url, wait_until="networkidle") # Stadt und Land uses "Eingaben prüfen" button
await asyncio.sleep(2) # Step 1: Click "Eingaben prüfen" button
logger.info("[STADTUNDLAND] Loaded Wohnungshelden application page") pruefen_btn = await page.query_selector('button:has-text("Eingaben prüfen")')
if pruefen_btn and await pruefen_btn.is_visible():
await pruefen_btn.click()
logger.info("[STADTUNDLAND] Clicked 'Eingaben prüfen' button")
await asyncio.sleep(2)
await page.wait_for_load_state("networkidle")
# Take screenshot of the Wohnungshelden form # Take screenshot after validation
screenshot_path = DATA_DIR / f"stadtundland_wohnungshelden_{listing['id']}.png" screenshot_path = DATA_DIR / f"stadtundland_validated_{listing['id']}.png"
await iframe_page.screenshot(path=str(screenshot_path), full_page=True) await page.screenshot(path=str(screenshot_path), full_page=True)
logger.info(f"[STADTUNDLAND] Saved Wohnungshelden screenshot to {screenshot_path}") logger.info(f"[STADTUNDLAND] Saved validation screenshot")
# Save HTML for debugging # Step 2: Click the final submit button
html_content = await iframe_page.content() final_submit_selectors = [
html_path = DATA_DIR / f"stadtundland_wohnungshelden_{listing['id']}.html" 'button:has-text("Absenden")',
with open(html_path, 'w', encoding='utf-8') as f: 'button:has-text("Senden")',
f.write(html_content) 'button:has-text("Anfrage senden")',
logger.info(f"[STADTUNDLAND] Saved HTML to {html_path}") 'button:has-text("Bestätigen")',
'button[type="submit"]',
]
# Fill out Wohnungshelden form (same as Degewo) final_btn = None
form_filled = False for selector in final_submit_selectors:
final_btn = await page.query_selector(selector)
if final_btn and await final_btn.is_visible():
logger.info(f"[STADTUNDLAND] Found final submit button: {selector}")
break
final_btn = None
# Anrede (Salutation) - ng-select dropdown if final_btn:
try: await final_btn.click()
salutation_dropdown = await iframe_page.query_selector('#salutation-dropdown, ng-select[id*="salutation"]') logger.info("[STADTUNDLAND] Clicked final submit button")
if salutation_dropdown: await asyncio.sleep(3)
await salutation_dropdown.click() await page.wait_for_load_state("networkidle")
await asyncio.sleep(0.5)
anrede_option = await iframe_page.query_selector(f'.ng-option:has-text("{FORM_ANREDE}")')
if anrede_option:
await anrede_option.click()
logger.info(f"[STADTUNDLAND] Selected Anrede: {FORM_ANREDE}")
form_filled = True
except Exception as e:
logger.warning(f"[STADTUNDLAND] Could not set Anrede: {e}")
# Vorname (First name) # Take screenshot after final submission
try: screenshot_path = DATA_DIR / f"stadtundland_submitted_{listing['id']}.png"
vorname_field = await iframe_page.query_selector('#firstName') await page.screenshot(path=str(screenshot_path), full_page=True)
if vorname_field: logger.info(f"[STADTUNDLAND] Saved submission screenshot")
await vorname_field.fill(FORM_VORNAME)
logger.info(f"[STADTUNDLAND] Filled Vorname: {FORM_VORNAME}")
form_filled = True
except Exception as e:
logger.warning(f"[STADTUNDLAND] Could not fill Vorname: {e}")
# Nachname (Last name)
try:
nachname_field = await iframe_page.query_selector('#lastName')
if nachname_field:
await nachname_field.fill(FORM_NACHNAME)
logger.info(f"[STADTUNDLAND] Filled Nachname: {FORM_NACHNAME}")
form_filled = True
except Exception as e:
logger.warning(f"[STADTUNDLAND] Could not fill Nachname: {e}")
# E-Mail
try:
email_field = await iframe_page.query_selector('#email')
if email_field:
await email_field.fill(FORM_EMAIL)
logger.info(f"[STADTUNDLAND] Filled E-Mail: {FORM_EMAIL}")
form_filled = True
except Exception as e:
logger.warning(f"[STADTUNDLAND] Could not fill E-Mail: {e}")
# Telefonnummer
try:
tel_field = await iframe_page.query_selector('input[id*="telefonnummer"]')
if tel_field:
await tel_field.fill(FORM_PHONE)
logger.info(f"[STADTUNDLAND] Filled Telefon: {FORM_PHONE}")
form_filled = True
except Exception as e:
logger.warning(f"[STADTUNDLAND] Could not fill Telefon: {e}")
# Anzahl einziehende Personen
try:
personen_field = await iframe_page.query_selector('input[id*="numberPersonsTotal"]')
if personen_field:
await personen_field.fill(FORM_PERSONS)
logger.info(f"[STADTUNDLAND] Filled Anzahl Personen: {FORM_PERSONS}")
form_filled = True
except Exception as e:
logger.warning(f"[STADTUNDLAND] Could not fill Anzahl Personen: {e}")
# "Für sich selbst" dropdown
try:
selbst_dropdown = await iframe_page.query_selector('ng-select[id*="fuer_wen"]')
if selbst_dropdown:
await selbst_dropdown.click()
await asyncio.sleep(0.5)
selbst_option = await iframe_page.query_selector('.ng-option:has-text("Für mich selbst"), .ng-option:has-text("selbst")')
if selbst_option:
await selbst_option.click()
logger.info("[STADTUNDLAND] Selected: Für mich selbst")
form_filled = True
except Exception as e:
logger.warning(f"[STADTUNDLAND] Could not set 'Für sich selbst': {e}")
await asyncio.sleep(1)
# Take screenshot after filling form
screenshot_path = DATA_DIR / f"stadtundland_form_filled_{listing['id']}.png"
await iframe_page.screenshot(path=str(screenshot_path), full_page=True)
logger.info(f"[STADTUNDLAND] Saved filled form screenshot to {screenshot_path}")
# Try to submit
if form_filled:
try:
submit_selectors = [
'button[type="submit"]',
'input[type="submit"]',
'button:has-text("Absenden")',
'button:has-text("Senden")',
'button:has-text("Anfrage")',
'.btn-primary',
]
submit_btn = None
for selector in submit_selectors:
submit_btn = await iframe_page.query_selector(selector)
if submit_btn and await submit_btn.is_visible():
logger.info(f"[STADTUNDLAND] Found submit button with selector: {selector}")
break
submit_btn = None
if submit_btn:
await submit_btn.click()
logger.info("[STADTUNDLAND] Clicked submit button")
await asyncio.sleep(3)
# Take screenshot after submission
screenshot_path = DATA_DIR / f"stadtundland_submitted_{listing['id']}.png"
await iframe_page.screenshot(path=str(screenshot_path), full_page=True)
logger.info(f"[STADTUNDLAND] Saved submission screenshot to {screenshot_path}")
# Check for confirmation message
content = await page.content()
if "erfolgreich" in content.lower() or "gesendet" in content.lower() or "danke" in content.lower() or "bestätigung" in content.lower():
result["success"] = True result["success"] = True
result["message"] = "Application submitted via Wohnungshelden" result["message"] = "Application submitted successfully"
logger.info("[STADTUNDLAND] Success! Confirmation message detected")
else: else:
result["success"] = False result["success"] = True
result["message"] = "Form filled, submit button not found" result["message"] = "Form submitted"
logger.warning("[STADTUNDLAND] Submit button not found") logger.info("[STADTUNDLAND] Form submitted")
except Exception as e: else:
result["success"] = False result["success"] = False
result["message"] = f"Form filled, submit error: {str(e)}" result["message"] = "Validated but final submit button not found"
logger.warning(f"[STADTUNDLAND] Submit error: {e}") logger.warning("[STADTUNDLAND] Final submit button not found")
else: else:
result["success"] = False result["success"] = False
result["message"] = "No form fields found in Wohnungshelden" result["message"] = "Form filled but 'Eingaben prüfen' button not found"
logger.warning("[STADTUNDLAND] Could not find form fields in Wohnungshelden") logger.warning("[STADTUNDLAND] 'Eingaben prüfen' button not found")
finally: except Exception as e:
await iframe_page.close() result["success"] = False
result["message"] = f"Submit error: {str(e)}"
logger.warning(f"[STADTUNDLAND] Submit error: {e}")
else: else:
# No Wohnungshelden iframe found
result["success"] = False result["success"] = False
result["message"] = "No Wohnungshelden iframe found" result["message"] = "No form fields found on page"
logger.warning("[STADTUNDLAND] No Wohnungshelden iframe found") logger.warning("[STADTUNDLAND] Could not find form fields")
screenshot_path = DATA_DIR / f"stadtundland_nobtn_{listing['id']}.png"
await page.screenshot(path=str(screenshot_path), full_page=True)
except Exception as e: except Exception as e:
result["success"] = False result["success"] = False