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()
try:
await iframe_page.goto(iframe_url, wait_until="networkidle")
await asyncio.sleep(2)
logger.info("[STADTUNDLAND] Loaded Wohnungshelden application page")
# Take screenshot of the Wohnungshelden form
screenshot_path = DATA_DIR / f"stadtundland_wohnungshelden_{listing['id']}.png"
await iframe_page.screenshot(path=str(screenshot_path), full_page=True)
logger.info(f"[STADTUNDLAND] Saved Wohnungshelden screenshot to {screenshot_path}")
# Save HTML for debugging
html_content = await iframe_page.content()
html_path = DATA_DIR / f"stadtundland_wohnungshelden_{listing['id']}.html"
with open(html_path, 'w', encoding='utf-8') as f:
f.write(html_content)
logger.info(f"[STADTUNDLAND] Saved HTML to {html_path}")
# Fill out Wohnungshelden form (same as Degewo)
form_filled = False form_filled = False
# Anrede (Salutation) - ng-select dropdown # Vorname (name field)
try: try:
salutation_dropdown = await iframe_page.query_selector('#salutation-dropdown, ng-select[id*="salutation"]') vorname_field = await page.query_selector('input[name="name"]')
if salutation_dropdown: if vorname_field and await vorname_field.is_visible():
await salutation_dropdown.click()
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)
try:
vorname_field = await iframe_page.query_selector('#firstName')
if vorname_field:
await vorname_field.fill(FORM_VORNAME) await vorname_field.fill(FORM_VORNAME)
logger.info(f"[STADTUNDLAND] Filled Vorname: {FORM_VORNAME}") logger.info(f"[STADTUNDLAND] Filled Vorname: {FORM_VORNAME}")
form_filled = True form_filled = True
except Exception as e: except Exception as e:
logger.warning(f"[STADTUNDLAND] Could not fill Vorname: {e}") logger.warning(f"[STADTUNDLAND] Could not fill Vorname: {e}")
# Nachname (Last name) # Nachname (surname field)
try: try:
nachname_field = await iframe_page.query_selector('#lastName') nachname_field = await page.query_selector('input[name="surname"]')
if nachname_field: if nachname_field and await nachname_field.is_visible():
await nachname_field.fill(FORM_NACHNAME) await nachname_field.fill(FORM_NACHNAME)
logger.info(f"[STADTUNDLAND] Filled Nachname: {FORM_NACHNAME}") logger.info(f"[STADTUNDLAND] Filled Nachname: {FORM_NACHNAME}")
form_filled = True form_filled = True
except Exception as e: except Exception as e:
logger.warning(f"[STADTUNDLAND] Could not fill Nachname: {e}") logger.warning(f"[STADTUNDLAND] Could not fill Nachname: {e}")
# E-Mail # Straße (street field)
try: try:
email_field = await iframe_page.query_selector('#email') street_field = await page.query_selector('input[name="street"]')
if email_field: 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) await email_field.fill(FORM_EMAIL)
logger.info(f"[STADTUNDLAND] Filled E-Mail: {FORM_EMAIL}") logger.info(f"[STADTUNDLAND] Filled E-Mail: {FORM_EMAIL}")
form_filled = True form_filled = True
except Exception as e: except Exception as e:
logger.warning(f"[STADTUNDLAND] Could not fill E-Mail: {e}") logger.warning(f"[STADTUNDLAND] Could not fill E-Mail: {e}")
# Telefonnummer # Click privacy checkbox
try: try:
tel_field = await iframe_page.query_selector('input[id*="telefonnummer"]') privacy_checkbox = await page.query_selector('input[name="privacy"]')
if tel_field: if privacy_checkbox and await privacy_checkbox.is_visible():
await tel_field.fill(FORM_PHONE) if not await privacy_checkbox.is_checked():
logger.info(f"[STADTUNDLAND] Filled Telefon: {FORM_PHONE}") await privacy_checkbox.click()
form_filled = True logger.info("[STADTUNDLAND] Clicked privacy checkbox")
except Exception as e: except Exception as e:
logger.warning(f"[STADTUNDLAND] Could not fill Telefon: {e}") logger.warning(f"[STADTUNDLAND] Could not click privacy checkbox: {e}")
# Anzahl einziehende Personen # Click provision checkbox (optional)
try: try:
personen_field = await iframe_page.query_selector('input[id*="numberPersonsTotal"]') provision_checkbox = await page.query_selector('input[name="provision"]')
if personen_field: if provision_checkbox and await provision_checkbox.is_visible():
await personen_field.fill(FORM_PERSONS) if not await provision_checkbox.is_checked():
logger.info(f"[STADTUNDLAND] Filled Anzahl Personen: {FORM_PERSONS}") await provision_checkbox.click()
form_filled = True logger.info("[STADTUNDLAND] Clicked provision checkbox")
except Exception as e: except Exception as e:
logger.warning(f"[STADTUNDLAND] Could not fill Anzahl Personen: {e}") logger.warning(f"[STADTUNDLAND] Could not click provision checkbox: {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) await asyncio.sleep(1)
# Take screenshot after filling form # Take screenshot after filling form
screenshot_path = DATA_DIR / f"stadtundland_form_filled_{listing['id']}.png" screenshot_path = DATA_DIR / f"stadtundland_filled_{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 filled form screenshot to {screenshot_path}") logger.info(f"[STADTUNDLAND] Saved filled form screenshot to {screenshot_path}")
# Try to submit # Submit form
if form_filled: if form_filled:
try: try:
submit_selectors = [ # Stadt und Land uses "Eingaben prüfen" button
'button[type="submit"]', # Step 1: Click "Eingaben prüfen" button
'input[type="submit"]', 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 after validation
screenshot_path = DATA_DIR / f"stadtundland_validated_{listing['id']}.png"
await page.screenshot(path=str(screenshot_path), full_page=True)
logger.info(f"[STADTUNDLAND] Saved validation screenshot")
# Step 2: Click the final submit button
final_submit_selectors = [
'button:has-text("Absenden")', 'button:has-text("Absenden")',
'button:has-text("Senden")', 'button:has-text("Senden")',
'button:has-text("Anfrage")', 'button:has-text("Anfrage senden")',
'.btn-primary', 'button:has-text("Bestätigen")',
'button[type="submit"]',
] ]
submit_btn = None final_btn = None
for selector in submit_selectors: for selector in final_submit_selectors:
submit_btn = await iframe_page.query_selector(selector) final_btn = await page.query_selector(selector)
if submit_btn and await submit_btn.is_visible(): if final_btn and await final_btn.is_visible():
logger.info(f"[STADTUNDLAND] Found submit button with selector: {selector}") logger.info(f"[STADTUNDLAND] Found final submit button: {selector}")
break break
submit_btn = None final_btn = None
if submit_btn: if final_btn:
await submit_btn.click() await final_btn.click()
logger.info("[STADTUNDLAND] Clicked submit button") logger.info("[STADTUNDLAND] Clicked final submit button")
await asyncio.sleep(3) await asyncio.sleep(3)
await page.wait_for_load_state("networkidle")
# Take screenshot after submission # Take screenshot after final submission
screenshot_path = DATA_DIR / f"stadtundland_submitted_{listing['id']}.png" screenshot_path = DATA_DIR / f"stadtundland_submitted_{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 submission screenshot to {screenshot_path}") logger.info(f"[STADTUNDLAND] Saved submission screenshot")
# 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:
result["success"] = True
result["message"] = "Form submitted"
logger.info("[STADTUNDLAND] Form submitted")
else: else:
result["success"] = False result["success"] = False
result["message"] = "Form filled, submit button not found" result["message"] = "Validated but final submit button not found"
logger.warning("[STADTUNDLAND] Submit button not found") logger.warning("[STADTUNDLAND] Final submit button not found")
else:
result["success"] = False
result["message"] = "Form filled but 'Eingaben prüfen' button not found"
logger.warning("[STADTUNDLAND] 'Eingaben prüfen' button not found")
except Exception as e: except Exception as e:
result["success"] = False result["success"] = False
result["message"] = f"Form filled, submit error: {str(e)}" result["message"] = f"Submit error: {str(e)}"
logger.warning(f"[STADTUNDLAND] Submit error: {e}") logger.warning(f"[STADTUNDLAND] Submit error: {e}")
else: else:
result["success"] = False result["success"] = False
result["message"] = "No form fields found in Wohnungshelden" result["message"] = "No form fields found on page"
logger.warning("[STADTUNDLAND] Could not find form fields in Wohnungshelden") logger.warning("[STADTUNDLAND] Could not find form fields")
finally:
await iframe_page.close()
else:
# No Wohnungshelden iframe found
result["success"] = False
result["message"] = "No Wohnungshelden iframe found"
logger.warning("[STADTUNDLAND] No Wohnungshelden iframe found")
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