#!/usr/bin/env python3 """ Playwright E2E Test: Profile Address Persistence (Stairwell / Floor / Door) This test: 1. Logs in as the configured test user (via LoginModal on the landing page). 2. Navigates to the Profile page (/profile). 3. Enters edit mode, fills every address field (city, zip, street, house number, stairwell, floor, door), and saves. 4. Reloads the page and verifies that the stairwell, floor, and door values persisted after the page refresh. Requirements: - playwright (Python) + dotenv - .env.test file at the project root with TEST_USER_EMAIL and TEST_USER_PASS Usage: python tests/active/e2e_profile_address.py To see the browser visually, change HEADLESS=False below. """ import os import sys import re from pathlib import Path from dotenv import load_dotenv from playwright.sync_api import sync_playwright, TimeoutError as PwTimeout # ── Configuration ────────────────────────────────────────────────────────── BASE_URL = "https://app.servicefinder.hu" HEADLESS = True # Set to False for visual debugging SLOW_MO = 0 # ms delay between actions (useful for debugging) ACTION_TIMEOUT = 15_000 # ms – default timeout for element lookups # ── Load .env.test from project root ────────────────────────────────────── ENV_PATH = Path(__file__).resolve().parents[1] / ".env.test" if not ENV_PATH.exists(): raise FileNotFoundError( f".env.test not found at {ENV_PATH}. " "Create it with TEST_USER_EMAIL and TEST_USER_PASS." ) load_dotenv(dotenv_path=ENV_PATH, override=True) TEST_EMAIL = os.getenv("TEST_USER_EMAIL") TEST_PASS = os.getenv("TEST_USER_PASS") if not TEST_EMAIL or not TEST_PASS: raise ValueError( "TEST_USER_EMAIL and/or TEST_USER_PASS are missing from .env.test. " f"Loaded from: {ENV_PATH}" ) # ── Test data ────────────────────────────────────────────────────────────── ADDRESS_DATA = { "address_city": "Dunakeszi", "address_zip": "2120", "address_street_name": "Határ", "address_street_type": "utca", "address_house_number": "8", "address_stairwell": "A", "address_floor": "3", "address_door": "5", } # ═══════════════════════════════════════════════════════════════════════════ # Helper functions # ═══════════════════════════════════════════════════════════════════════════ def log(msg: str) -> None: """Simple stdout logger.""" print(f"[E2E] {msg}") def fill_input(page, placeholder: str, value: str) -> None: """Locate an by its placeholder and fill it.""" locator = page.locator(f'input[placeholder="{placeholder}"]') locator.wait_for(state="visible", timeout=ACTION_TIMEOUT) locator.fill(value) log(f" Filled '{placeholder}' → '{value}'") # ═══════════════════════════════════════════════════════════════════════════ # Main test flow # ═══════════════════════════════════════════════════════════════════════════ def main() -> None: with sync_playwright() as pw: # ── 1. Launch browser (local or remote via BROWSERLESS_URL) ── browserless_url = os.getenv("BROWSERLESS_URL") if browserless_url: # Connect to a remote Browserless / Chrome container browser = pw.chromium.connect_over_cdp(browserless_url) log(f"Connected to remote browser at {browserless_url}") else: # Local fallback browser = pw.chromium.launch( headless=os.getenv("HEADLESS", "True").lower() == "true", slow_mo=SLOW_MO, ) log(f"Browser launched locally (headless={os.getenv('HEADLESS', 'True')})") context = browser.new_context( viewport={"width": 1280, "height": 800}, locale="hu-HU", ) page = context.new_page() log(f"Browser launched (headless={HEADLESS})") # ── 2. Open landing page & login ───────────────────────────── log("Navigating to landing page …") page.goto(BASE_URL, wait_until="networkidle", timeout=30_000) # Click the "Garázs Nyitása" button to open the LoginModal login_trigger = page.locator("button:has-text('Garázs Nyitása')") login_trigger.wait_for(state="visible", timeout=ACTION_TIMEOUT) login_trigger.click() log("Login modal opened") # Fill email & password page.fill("#login-email", TEST_EMAIL) page.fill("#login-password", TEST_PASS) log("Login credentials entered") # Click the submit button (the first .btn-premium inside the visible form) submit_btn = page.locator("form button[type='submit']").first submit_btn.wait_for(state="visible", timeout=ACTION_TIMEOUT) submit_btn.click() log("Login submitted – waiting for navigation …") # Wait for redirect away from landing page (to /dashboard or /complete-kyc) try: page.wait_for_url( lambda url: "/dashboard" in url or "/complete-kyc" in url, timeout=15_000, ) except PwTimeout: # If still on landing, maybe login failed – dump state log(f"URL after login attempt: {page.url}") page.screenshot(path="/tmp/e2e_login_fail.png") raise AssertionError( "Login failed – did not redirect to dashboard. " "Check credentials or UI state." ) log(f"Login successful – current URL: {page.url}") # ── 3. Navigate to Profile page ────────────────────────────── log("Navigating to /profile …") page.goto(f"{BASE_URL}/profile", wait_until="networkidle", timeout=20_000) # Wait for the profile page to finish loading (spinner disappears) page.wait_for_selector("text=Profil beállítások", timeout=ACTION_TIMEOUT) # Also wait until the loading spinner is gone page.wait_for_function( "() => !document.querySelector('.animate-spin')", timeout=15_000, ) log("Profile page loaded") # ── 4. Enter edit mode ─────────────────────────────────────── edit_btn = page.locator("button:has-text('Szerkesztés')") edit_btn.wait_for(state="visible", timeout=ACTION_TIMEOUT) edit_btn.click() log("Edit mode activated") # ── 5. Fill in all address fields ──────────────────────────── log("Filling address form …") fill_input(page, "Irányítószám", ADDRESS_DATA["address_zip"]) fill_input(page, "Város", ADDRESS_DATA["address_city"]) fill_input(page, "Utca", ADDRESS_DATA["address_street_name"]) fill_input(page, "Közterület jellege", ADDRESS_DATA["address_street_type"]) fill_input(page, "Házszám", ADDRESS_DATA["address_house_number"]) fill_input(page, "Lépcsőház", ADDRESS_DATA["address_stairwell"]) fill_input(page, "Emelet", ADDRESS_DATA["address_floor"]) fill_input(page, "Ajtó", ADDRESS_DATA["address_door"]) # ── 6. Save ────────────────────────────────────────────────── save_btn = page.locator("button:has-text('Mentés')") save_btn.wait_for(state="visible", timeout=ACTION_TIMEOUT) save_btn.click() log("Save clicked – waiting for confirmation …") # Wait for the success message to appear success_locator = page.locator("text=A profil adatok és okmányok sikeresen frissítve!") try: success_locator.wait_for(state="visible", timeout=10_000) log("Save succeeded – success message visible") except PwTimeout: # Check for error message error_locator = page.locator(".text-red-400") if error_locator.is_visible(): error_text = error_locator.text_content() log(f"Save FAILED with error: {error_text}") page.screenshot(path="/tmp/e2e_save_fail.png") raise AssertionError(f"Save failed: {error_text}") page.screenshot(path="/tmp/e2e_save_timeout.png") raise AssertionError( "Save timeout – neither success nor error message appeared." ) # ── 7. Reload and verify persistence ───────────────────────── log("Reloading page to verify persistence …") page.reload(wait_until="networkidle") page.wait_for_selector("text=Profil beállítások", timeout=ACTION_TIMEOUT) page.wait_for_function( "() => !document.querySelector('.animate-spin')", timeout=15_000, ) log("Page reloaded") # Read the display text in the read-only address section # The read-only view shows: Lépcsőház: …, Emelet: …, Ajtó: … page_body = page.locator("body").inner_text() log("── Read-only address block ──") # Use a regex to extract values stairwell_match = re.search(r"Lépcsőház:\s*(\S+)", page_body) floor_match = re.search(r"Emelet:\s*(\S+)", page_body) door_match = re.search(r"Ajtó:\s*(\S+)", page_body) stairwell_val = stairwell_match.group(1) if stairwell_match else "" floor_val = floor_match.group(1) if floor_match else "" door_val = door_match.group(1) if door_match else "" log(f" Stairwell (Lépcsőház): '{stairwell_val}'") log(f" Floor (Emelet): '{floor_val}'") log(f" Door (Ajtó): '{door_val}'") # ── 8. Assertion: all three values must be present ─────────── errors = [] if not stairwell_val or stairwell_val in ("—", "null", "None"): errors.append(f"stairwell (expected '{ADDRESS_DATA['address_stairwell']}', got '{stairwell_val}')") if not floor_val or floor_val in ("—", "null", "None"): errors.append(f"floor (expected '{ADDRESS_DATA['address_floor']}', got '{floor_val}')") if not door_val or door_val in ("—", "null", "None"): errors.append(f"door (expected '{ADDRESS_DATA['address_door']}', got '{door_val}')") if errors: page.screenshot(path="/tmp/e2e_persistence_fail.png") raise AssertionError( "Hibás backend mentés! Az emelet/ajtó adatok elvesztek az oldal " f"frissítése után.\n Missing/empty fields: {', '.join(errors)}" ) log("Sikeres E2E teszt: Az adatok megmaradtak a backend frissítés után is!") log("ALL CHECKS PASSED.\n")