lazyflat: combined alert + apply behind authenticated web UI

Three isolated services (alert scraper, apply HTTP worker, web UI+DB)
with argon2 auth, signed cookies, CSRF, rate-limited login, kill switch,
apply circuit breaker, audit log, and strict CSP.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Moritz 2026-04-21 09:51:35 +02:00
commit 69f2f1f635
46 changed files with 4183 additions and 0 deletions

View file

@ -0,0 +1,18 @@
from providers._provider import Provider
import pkgutil
import importlib
# This code runs the moment anyone imports the 'providers' package
__all__ = []
for loader, module_name, is_pkg in pkgutil.walk_packages(__path__):
full_module_name = f"{__name__}.{module_name}"
importlib.import_module(full_module_name)
__all__.append(module_name)
PROVIDERS = dict()
for provider_class in Provider.__subclasses__():
provider_instance = provider_class()
PROVIDERS[provider_instance.domain] = provider_instance

View file

@ -0,0 +1,22 @@
import asyncio
from abc import ABC, abstractmethod
import logging
from classes.application_result import ApplicationResult
logger = logging.getLogger("flat-apply")
class Provider(ABC):
@property
@abstractmethod
def domain(self) -> str:
"""every flat provider needs a domain"""
pass
@abstractmethod
async def apply_for_flat(self, url: str) -> ApplicationResult:
"""every flat provider needs to be able to apply for flats"""
pass
def test_apply(self, url):
print(asyncio.run(self.apply_for_flat(url)))

129
apply/providers/degewo.py Normal file
View file

@ -0,0 +1,129 @@
from actions import *
from language import _
from classes.application_result import ApplicationResult
from providers._provider import Provider
from settings import *
import logging
logger = logging.getLogger("flat-apply")
class Degewo(Provider):
@property
def domain(self) -> str:
return "degewo.de"
async def apply_for_flat(self, url) -> ApplicationResult:
async with open_page(url) as page:
logger.info("\tSTEP 1: accepting cookies")
cookie_accept_btn = page.locator("#cookie-consent-submit-all")
if await cookie_accept_btn.is_visible():
await cookie_accept_btn.click()
logger.debug("\t\tcookie accept button clicked")
else:
logger.debug("\t\tno cookie accept button found")
logger.info("\tSTEP 2: check if the page was not found")
if page.url == "https://www.degewo.de/immosuche/404":
logger.debug("\t\t'page not found' message found - returning")
return ApplicationResult(
success=False,
message=_("ad_offline"))
logger.debug("\t\t'page not found' message not found")
logger.info("\tSTEP 3: check if the ad is deactivated")
if await page.locator("span", has_text="Inserat deaktiviert").is_visible():
logger.debug("\t\t'ad deactivated' message found - returning")
return ApplicationResult(
success=False,
message=_("ad_deactivated"))
logger.debug("\t\t'ad deactivated' message not found")
logger.info("\tSTEP 4: check if the page moved")
if await page.locator("h1", has_text="Diese Seite ist umgezogen!").is_visible():
logger.debug("\t\t'page moved' message found - returning")
return ApplicationResult(
success=False,
message=_("ad_offline"))
logger.debug("\t\t'page moved' message not found")
logger.info("\tSTEP 5: go to the application form")
await page.get_by_role("link", name="Kontakt").click()
logger.info("\tSTEP 6: find the form iframe")
form_frame = page.frame_locator("iframe[src*='wohnungshelden']")
logger.info("\tSTEP 7: fill the form")
await form_frame.locator("#salutation").fill(SALUTATION)
await form_frame.get_by_role("option", name=SALUTATION, exact=True).click()
await form_frame.locator("#firstName").fill(FIRSTNAME)
await form_frame.locator("#lastName").fill(LASTNAME)
await form_frame.locator("#email").fill(EMAIL)
await form_frame.locator("input[title='Telefonnummer']").fill(TELEPHONE)
await form_frame.locator("input[title='Anzahl einziehende Personen']").fill(str(PERSON_COUNT))
await page.wait_for_timeout(1000)
wbs_question = form_frame.locator("input[id*='wbs_available'][id$='Ja']")
if await wbs_question.is_visible():
if not IS_POSSESSING_WBS:
return ApplicationResult(False, _("wbs_required"))
await wbs_question.click()
await form_frame.locator("input[title='WBS gültig bis']").fill(WBS_VALID_TILL.strftime("%d.%m.%Y"))
await page.wait_for_timeout(1000)
wbs_rooms_select = form_frame.locator("ng-select[id*='wbs_max_number_rooms']")
await wbs_rooms_select.click()
await page.wait_for_timeout(1000)
correct_wbs_room_option = form_frame.get_by_role("option", name=str(WBS_ROOMS), exact=True)
await correct_wbs_room_option.click()
await page.wait_for_timeout(1000)
await form_frame.locator("ng-select[id*='fuer_wen_ist_wohnungsanfrage']").click()
await page.wait_for_timeout(1000)
await form_frame.get_by_role("option", name="Für mich selbst").click()
logger.info("\tSTEP 8: submit the form")
if not SUBMIT_FORMS:
logger.debug(f"\t\tdry run - not submitting")
return ApplicationResult(success=True, message=_("application_success_dry"))
await form_frame.locator("button[data-cy*='btn-submit']").click()
await page.wait_for_timeout(3000)
logger.info("\tSTEP 9: check the success")
if await page.locator("h4", has_text="Vielen Dank für das Übermitteln Ihrer Informationen. Sie können dieses Fenster jetzt schließen.").is_visible():
logger.info(f"\t\tsuccess detected by heading")
return ApplicationResult(success=True)
elif await self.is_missing_fields_warning(page):
logger.warning(f"\t\tmissing fields warning detected")
return ApplicationResult(success=False, message=_("missing_fields"))
elif await self.is_already_applied_warning(page):
logger.warning(f"\t\talready applied warning detected")
return ApplicationResult(success=False, message=_("already_applied"))
logger.warning(f"\t\tsubmit conformation not found")
return ApplicationResult(success=False, message=_("submit_conformation_msg_not_found"))
async def is_already_applied_warning(self, page):
await page.wait_for_timeout(1000)
form_iframe = page.frame_locator("iframe[src*='wohnungshelden']")
already_applied_warning = form_iframe.locator("span.ant-alert-message",
has_text="Es existiert bereits eine Anfrage mit dieser E-Mail Adresse")
if await already_applied_warning.first.is_visible():
return True
return False
async def is_missing_fields_warning(self, page):
await page.wait_for_timeout(1000)
form_iframe = page.frame_locator("iframe[src*='wohnungshelden']")
already_applied_warning = form_iframe.locator("span.ant-alert-message",
has_text="Es wurden nicht alle Felder korrekt befüllt. Bitte prüfen Sie ihre Eingaben")
if await already_applied_warning.first.is_visible():
return True
return False
if __name__ == "__main__":
# url = "https://www.degewo.de/immosuche/details/neubau-mit-wbs-140-160-180-220-mit-besonderem-wohnbedarf-1" # already applied
# url = "https://www.degewo.de/immosuche/details/wohnung-sucht-neuen-mieter-1" # angebot geschlossen
# url = "https://www.degewo.de/immosuche/details/wohnung-sucht-neuen-mieter-145" # seite nicht gefunden
# url = "https://www.degewo.de/immosuche/details/1-zimmer-mit-balkon-3"
# url = "https://www.degewo.de/immosuche/details/2-zimmer-in-gropiusstadt-4"
url = "https://www.degewo.de/immosuche/details/2-zimmer-in-gropiusstadt-4"
provider = Degewo()
provider.test_apply(url)

View file

@ -0,0 +1,81 @@
from actions import *
from language import _
from classes.application_result import ApplicationResult
from providers._provider import Provider
from settings import *
import logging
logger = logging.getLogger("flat-apply")
class Gesobau(Provider):
@property
def domain(self) -> str:
return "gesobau.de"
async def apply_for_flat(self, url) -> ApplicationResult:
async with open_page(url) as page:
logger.info("\tSTEP 1: extracting immomio link")
immomio_link = await page.get_by_role("link", name="Jetzt bewerben").get_attribute("href")
logger.info("\tSTEP 2: going to auth page")
await page.goto("https://tenant.immomio.com/de/auth/login")
await page.wait_for_timeout(1000)
logger.info("\tSTEP 3: logging in")
await page.locator('input[name="email"]').fill(IMMOMIO_EMAIL)
await page.get_by_role("button", name="Anmelden").click()
await page.wait_for_timeout(1000)
await page.locator("#password").fill(IMMOMIO_PASSWORD)
await page.locator("#kc-login").click()
await page.wait_for_timeout(1000)
logger.info("\tSTEP 4: going back to immomio")
await page.goto(immomio_link)
await page.wait_for_timeout(1000)
logger.info("\tSTEP 5: accepting cookies")
cookie_accept_btn = page.get_by_role("button", name="Alle erlauben")
if await cookie_accept_btn.is_visible():
await cookie_accept_btn.click()
logger.debug("\t\tcookie accept button clicked")
else:
logger.debug("\t\tno cookie accept button found")
logger.info("\tSTEP 6: click apply now")
await page.get_by_role("button", name="Jetzt bewerben").click()
await page.wait_for_timeout(3000)
logger.info("\tSTEP 7: check if already applied")
if page.url == "https://tenant.immomio.com/de/properties/applications":
return ApplicationResult(False, message=_("already_applied"))
logger.info("\tSTEP 8: clicking answer questions")
answer_questions_btn = page.get_by_role("button", name="Fragen beantworten")
if await answer_questions_btn.is_visible():
await answer_questions_btn.click()
logger.debug("\t\tanswer questions button clicked")
await page.wait_for_timeout(2000)
else:
logger.debug("\t\tno answer questions button found")
if await answer_questions_btn.is_visible(): # sometimes this button must be clicked twice
await answer_questions_btn.click()
logger.debug("\t\tanswer questions button clicked")
await page.wait_for_timeout(2000)
logger.info("\tSTEP 9: verifying success by answer button vanishing")
if not await answer_questions_btn.is_visible(): # TODO better verify success
logger.info("\t\tsuccess detected by answer button vanishing")
return ApplicationResult(True)
logger.info("\t\tsubmit conformation not found")
return ApplicationResult(False, _("submit_conformation_msg_not_found"))
if __name__ == "__main__":
# url = "https://www.gesobau.de/?immo_ref=10-03239-00007-1185" # already applied
# url = "https://www.gesobau.de/mieten/wohnungssuche/detailseite/florastrasse-10-12179-00002-1002-1d4d1a94-b555-48f8-b06d-d6fc02aecb0d/"
url = "https://www.gesobau.de/mieten/wohnungssuche/detailseite/rolandstrasse-10-03020-00007-1052-7f47d893-e659-4e4f-a7cd-5dcd53f4e6d7/"
provider = Gesobau()
provider.test_apply(url)

168
apply/providers/gewobag.py Normal file
View file

@ -0,0 +1,168 @@
from actions import *
from language import _
from classes.application_result import ApplicationResult
from providers._provider import Provider
from settings import *
import logging
logger = logging.getLogger("flat-apply")
class Gewobag(Provider):
@property
def domain(self) -> str:
return "gewobag.de"
async def apply_for_flat(self, url) -> ApplicationResult:
async with open_page(url) as page:
logger.info("\tSTEP 1: accepting cookies")
cookie_accept_btn = page.get_by_text("Alle Cookies akzeptieren")
if await cookie_accept_btn.is_visible():
await cookie_accept_btn.click()
logger.debug("\t\tcookie accept button clicked")
else:
logger.debug("\t\tno cookie accept button found")
logger.info("\tSTEP 2: check if the page was not found")
if await page.get_by_text("Mietangebot nicht gefunden").first.is_visible():
logger.debug("\t\t'page not found' message found - returning")
return ApplicationResult(
success=False,
message=_("not_found"))
logger.debug("\t\t'page not found' message not found")
logger.info("\tSTEP 3: check if ad is still open")
if await page.locator('#immo-mediation-notice').is_visible():
logger.debug("\t\tad closed notice found - returning")
return ApplicationResult(
success=False,
message=_("ad_deactivated"))
logger.debug("\t\tno ad closed notice found")
logger.info("\tSTEP 4: go to the application form")
await page.get_by_role("button", name="Anfrage senden").first.click()
logger.info("\tSTEP 5: check if the flat is for seniors only")
if await self.is_senior_flat(page):
logger.debug("\t\tflat is for seniors only - returning")
return ApplicationResult(False, _("senior_flat"))
logger.debug("\t\tflat is not seniors only")
logger.info("\tSTEP 6: check if the flat is for special needs wbs only")
if await self.is_special_needs_wbs(page):
logger.debug("\t\tflat is for special needs wbs only - returning")
return ApplicationResult(False, _("special_need_wbs_flat"))
logger.debug("\t\tflat is not for special needs wbs only")
logger.info("\tSTEP 7: find the form iframe")
form_iframe = page.frame_locator("#contact-iframe")
logger.info("\tSTEP 8: define helper functions")
async def fill_field(locator, filling):
logger.debug(f"\t\tfill_field('{locator}', '{filling}')")
field = form_iframe.locator(locator)
if await field.is_visible():
await field.fill(filling)
await page.wait_for_timeout(100)
else:
logger.debug(f"\t\t\tfield was not found")
async def select_field(locator, selection):
logger.debug(f"\t\tselect_field('{locator}', '{selection}')")
field = form_iframe.locator(locator)
if await field.is_visible():
await field.click()
await page.wait_for_timeout(100)
await form_iframe.get_by_role("option", name=selection, exact=True).click()
await page.wait_for_timeout(100)
else:
logger.debug(f"\t\t\tfield was not found")
async def check_checkbox(locator):
logger.debug(f"\t\tcheck_checkbox('{locator}')")
field = form_iframe.locator(locator)
if await field.first.is_visible():
await field.evaluate_all("elements => elements.forEach(el => el.click())")
await page.wait_for_timeout(100)
else:
logger.debug(f"\t\t\tfield was not found")
async def upload_files(locator, files=None):
if not files:
create_dummy_pdf()
files = ["DummyPDF.pdf"]
logger.debug(f"\t\tupload_files('{locator}', {str(files)})")
wbs_upload_section = form_iframe.locator(locator)
if await wbs_upload_section.count() > 0:
await wbs_upload_section.locator("input[type='file']").set_input_files(files)
await page.wait_for_timeout(2000)
else:
logger.debug(f"\t\t\tfield was not found")
logger.info("\tSTEP 9: fill the form")
await select_field("#salutation-dropdown", SALUTATION)
await fill_field("#firstName", FIRSTNAME)
await fill_field("#lastName", LASTNAME)
await fill_field("#email", EMAIL)
await fill_field("#phone-number", TELEPHONE)
await fill_field("#street", STREET)
await fill_field("#house-number", HOUSE_NUMBER)
await fill_field("#zip-code", POSTCODE)
await fill_field("#city", CITY)
await fill_field("input[id*='anzahl_erwachsene']", str(ADULT_COUNT))
await fill_field("input[id*='anzahl_kinder']", str(CHILDREN_COUNT))
await fill_field("input[id*='gesamtzahl_der_einziehenden_personen']", str(PERSON_COUNT))
await check_checkbox("[data-cy*='wbs_available'][data-cy*='-Ja']")
await fill_field("input[id*='wbs_valid_until']", WBS_VALID_TILL.strftime("%d.%m.%Y"))
await select_field("input[id*='wbs_max_number_rooms']", f"{WBS_ROOMS} Räume")
await select_field("input[id*='art_bezeichnung_des_wbs']", f"WBS {WBS_TYPE}")
await select_field("input[id*='fuer_wen']", "Für mich selbst")
await fill_field("input[id*='telephone_number']", TELEPHONE)
await check_checkbox("input[id*='datenschutzhinweis']")
await upload_files("el-application-form-document-upload", files=None)
logger.info("\tSTEP 10: submit the form")
if not SUBMIT_FORMS:
logger.debug(f"\t\tdry run - not submitting")
return ApplicationResult(True, _("application_success_dry"))
await form_iframe.get_by_role("button", name="Anfrage versenden").click()
await page.wait_for_timeout(5000)
logger.info("\tSTEP 11: check the success")
if page.url.startswith("https://www.gewobag.de/daten-uebermittelt/"):
logger.info(f"\t\tsuccess detected by page url")
return ApplicationResult(True)
elif self.is_missing_fields_warning(page):
logger.warning(f"\t\tmissing fields warning detected")
return ApplicationResult(False, _("missing_fields"))
else:
logger.warning(f"\t\tneither missing fields nor success detected")
return ApplicationResult(False, _("submit_conformation_msg_not_found"))
async def is_senior_flat(self, page):
form_iframe = page.frame_locator("#contact-iframe")
return await form_iframe.locator("label[for*='mindestalter_seniorenwohnhaus_erreicht']").first.is_visible()
async def is_special_needs_wbs(self, page):
form_iframe = page.frame_locator("#contact-iframe")
return await form_iframe.locator("label[for*='wbs_mit_besonderem_wohnbedarf_vorhanden']").first.is_visible()
async def is_missing_fields_warning(self, page):
form_iframe = page.frame_locator("#contact-iframe")
missing_field_msg = form_iframe.locator("span.ant-alert-message",
has_text="Es wurden nicht alle Felder korrekt befüllt.")
if await missing_field_msg.first.is_visible():
return True
return False
if __name__ == "__main__":
#url = "https://www.gewobag.de/fuer-mietinteressentinnen/mietangebote/0100-01036-0601-0286-vms1/" # wbs
#url = "https://www.gewobag.de/fuer-mietinteressentinnen/mietangebote/7100-72401-0101-0011/" # senior
url = "https://www.gewobag.de/fuer-mietinteressentinnen/mietangebote/6011-31046-0105-0045/" # more wbs fields
#url = "https://www.gewobag.de/fuer-mietinteressentinnen/mietangebote/0100-01036-0401-0191/" # special need wbs
#url = "https://www.gewobag.de/fuer-mietinteressentinnen/mietangebote/0100-02571-0103-0169/"
# url = "https://www.gewobag.de/fuer-mietinteressentinnen/mietangebote/0100-02571-0103-169/" # page not found
provider = Gewobag()
provider.test_apply(url)

62
apply/providers/howoge.py Normal file
View file

@ -0,0 +1,62 @@
from actions import *
from language import _
from classes.application_result import ApplicationResult
from providers._provider import Provider
from settings import *
import logging
logger = logging.getLogger("flat-apply")
class Howoge(Provider):
@property
def domain(self) -> str:
return "howoge.de"
async def apply_for_flat(self, url) -> ApplicationResult:
async with open_page(url) as page:
logger.info("\tSTEP 1: accepting cookies")
cookie_accept_btn = page.get_by_role("button", name="Alles akzeptieren")
if await cookie_accept_btn.is_visible():
await cookie_accept_btn.click()
logger.debug("\t\tcookie accept button clicked")
else:
logger.debug("\t\tno cookie accept button found")
logger.info("\tSTEP 2: check if the page was not found")
if page.url == "https://www.howoge.de/404":
logger.debug("\t\t'page not found' url found - returning")
return ApplicationResult(
success=False,
message=_("not_found"))
logger.debug("\t\t'page not found' url not found")
logger.info("\tSTEP 3: go to the application form")
await page.get_by_role("link", name="Besichtigung anfragen").click()
logger.info("\tSTEP 4: fill the form")
await page.get_by_text("Ja, ich habe die Hinweise zum WBS zur Kenntnis genommen.").click()
await page.get_by_role("button", name="Weiter").click()
await page.get_by_text("Ja, ich habe den Hinweis zum Haushaltsnettoeinkommen zur Kenntnis genommen.").click()
await page.get_by_role("button", name="Weiter").click()
await page.get_by_text("Ja, ich habe den Hinweis zur Bonitätsauskunft zur Kenntnis genommen.").click()
await page.get_by_role("button", name="Weiter").click()
await page.locator("#immo-form-firstname").fill(FIRSTNAME)
await page.locator("#immo-form-lastname").fill(LASTNAME)
await page.locator("#immo-form-email").fill(EMAIL)
logger.info("\tSTEP 5: submit the form")
if not SUBMIT_FORMS:
logger.debug(f"\t\tdry run - not submitting")
return ApplicationResult(True, _("application_success_dry"))
await page.get_by_role("button", name="Anfrage senden").click()
logger.info("\tSTEP 6: check the success")
if await page.get_by_role("heading", name="Vielen Dank.").is_visible():
return ApplicationResult(True)
return ApplicationResult(False, _("submit_conformation_msg_not_found"))
if __name__ == "__main__":
# url = "https://www.howoge.de/wohnungen-gewerbe/wohnungssuche/detail/1770-26279-6.html" # not found
url = "https://www.howoge.de/immobiliensuche/wohnungssuche/detail/1770-27695-194.html"
provider = Howoge()
provider.test_apply(url)

View file

@ -0,0 +1,65 @@
from actions import *
from language import _
from classes.application_result import ApplicationResult
from providers._provider import Provider
from settings import *
import logging
logger = logging.getLogger("flat-apply")
class Stadtundland(Provider):
@property
def domain(self) -> str:
return "stadtundland.de"
async def apply_for_flat(self, url) -> ApplicationResult:
async with open_page(url) as page:
logger.info("\tSTEP 1: accepting cookies")
cookie_accept_btn = page.get_by_text("Alle akzeptieren")
if await cookie_accept_btn.is_visible():
await cookie_accept_btn.click()
logger.debug("\t\tcookie accept button clicked")
else:
logger.debug("\t\tno cookie accept button found")
logger.info("\tSTEP 2: check if ad is still open")
if await page.get_by_role("heading", name="Hier ist etwas schief gelaufen").is_visible():
logger.debug("\t\tsomething went wrong notice found - returning")
return ApplicationResult(
success=False,
message=_("ad_offline"))
logger.debug("\t\tsomething went wrong notice not found")
logger.info("\tSTEP 3: fill the form")
await page.locator("#name").fill(FIRSTNAME)
await page.locator("#surname").fill(LASTNAME)
await page.locator("#street").fill(STREET)
await page.locator("#houseNo").fill(HOUSE_NUMBER)
await page.locator("#postalCode").fill(POSTCODE)
await page.locator("#city").fill(CITY)
await page.locator("#phone").fill(TELEPHONE)
await page.locator("#email").fill(EMAIL)
await page.locator("#privacy").check()
await page.locator("#provision").check()
logger.info("\tSTEP 4: submit the form")
if not SUBMIT_FORMS:
logger.debug(f"\t\tdry run - not submitting")
return ApplicationResult(False, _("application_success_dry"))
await page.get_by_role("button", name="Eingaben prüfen").click()
await page.get_by_role("button", name="Absenden").click()
await page.wait_for_timeout(2000)
logger.info("\tSTEP 5: check the success")
if await page.locator("p").filter(has_text="Vielen Dank!").is_visible():
logger.info(f"\t\tsuccess detected by paragraph text")
return ApplicationResult(True)
logger.warning(f"\t\tsuccess message not found")
return ApplicationResult(success=False, message=_("submit_conformation_msg_not_found"))
if __name__ == "__main__":
# url = "https://stadtundland.de/wohnungssuche/1001%2F0203%2F00310" # offline
url = "https://stadtundland.de/wohnungssuche/1050%2F8222%2F00091" # wbs
provider = Stadtundland()
provider.test_apply(url)

100
apply/providers/wbm.py Normal file
View file

@ -0,0 +1,100 @@
from actions import *
from language import _
from classes.application_result import ApplicationResult
from providers._provider import Provider
from settings import *
import logging
logger = logging.getLogger("flat-apply")
class Wbm(Provider):
@property
def domain(self) -> str:
return "wbm.de"
async def apply_for_flat(self, url) -> ApplicationResult:
async with open_page(url) as page:
logger.info("\tSTEP 1: checking if page not found")
if await page.get_by_role("heading", name="Page Not Found").is_visible():
logger.debug("\t\t'page not found' message found - returning")
return ApplicationResult(
success=False,
message=_("not_found"))
logger.debug("\t\t'page not found' message not found")
logger.info("\tSTEP 2: accepting cookies")
cookie_accept_btn = page.get_by_text("Alle zulassen")
if await cookie_accept_btn.is_visible():
await cookie_accept_btn.click()
logger.debug("\t\tcookie accept button clicked")
else:
logger.debug("\t\tno cookie accept button found")
logger.info("\tSTEP 3: removing chatbot help icon")
await page.locator('#removeConvaiseChat').click()
logger.info("\tSTEP 4: checking if ad is offline")
if page.url == "https://www.wbm.de/wohnungen-berlin/angebote/":
logger.debug("\t\t'page not found' url found - returning")
return ApplicationResult(
success=False,
message=_("ad_offline"))
logger.debug("\t\t'page not found' url not found")
logger.info("\tSTEP 5: go to the application form")
await page.locator('.openimmo-detail__contact-box-button').click()
logger.info("\tSTEP 6: filling the application form")
if IS_POSSESSING_WBS:
await page.locator('label[for="powermail_field_wbsvorhanden_1"]').click()
await page.locator("input[name*='[wbsgueltigbis]']").fill(WBS_VALID_TILL.strftime("%Y-%m-%d"))
await page.locator("select[name*='[wbszimmeranzahl]']").select_option(str(WBS_ROOMS))
await page.locator("#powermail_field_einkommensgrenzenacheinkommensbescheinigung9").select_option(WBS_TYPE)
if IS_PRIO_WBS:
await page.locator("#powermail_field_wbsmitbesonderemwohnbedarf_1").check(force=True)
else:
await page.locator('label[for="powermail_field_wbsvorhanden_2"]').click()
await page.locator("#powermail_field_anrede").select_option(SALUTATION)
await page.locator("#powermail_field_name").fill(LASTNAME)
await page.locator("#powermail_field_vorname").fill(FIRSTNAME)
await page.locator("#powermail_field_strasse").fill(STREET)
await page.locator("#powermail_field_plz").fill(POSTCODE)
await page.locator("#powermail_field_ort").fill(CITY)
await page.locator("#powermail_field_e_mail").fill(EMAIL)
await page.locator("#powermail_field_telefon").fill(TELEPHONE)
await page.locator("#powermail_field_datenschutzhinweis_1").check(force=True)
logger.info("\tSTEP 7: submit the form")
if not SUBMIT_FORMS:
logger.debug(f"\t\tdry run - not submitting")
return ApplicationResult(success=True, message=_("application_success_dry"))
await page.get_by_role("button", name="Anfrage absenden").click()
logger.info("\tSTEP 8: check the success")
if await page.get_by_text("Wir haben Ihre Anfrage für das Wohnungsangebot erhalten.").is_visible():
logger.info(f"\t\tsuccess detected by text")
return ApplicationResult(True)
elif await self.is_missing_fields_warning(page):
logger.warning(f"\t\tmissing fields warning detected")
return ApplicationResult(False, _("missing_fields"))
else:
logger.warning(f"\t\tneither missing fields nor success detected")
return ApplicationResult(success=False, message=_("submit_conformation_msg_not_found"))
async def is_missing_fields_warning(self, page):
missing_field_msg = page.get_by_text("Dieses Feld muss ausgefüllt werden!").first
if await missing_field_msg.first.is_visible():
return True
return False
if __name__ == "__main__":
# url = "https://www.wbm.de/wohnungen-berlin/angebote/details/4-zimmer-wohnung-in-spandau-1/" # not found
url = "https://www.wbm.de/wohnungen-berlin/angebote/details/wbs-160-180-220-perfekt-fuer-kleine-familien-3-zimmer-wohnung-mit-balkon/"
provider = Wbm()
provider.test_apply(url)