*** Keywords *** Login Via HTTP [Documentation] Login via HTTP and store session cookie for RequestsLibrary. ... Call this before any RequestsLibrary keyword that needs auth. ${headers}= Create Dictionary X-BanGUI-Request 1 Create Session bangsess ${BACKEND_URL} headers=${headers} ${login_payload}= Create Dictionary password Hallo123! ${login_resp}= POST On Session bangsess /api/v1/auth/login ... json=${login_payload} ... expected_status=200 Log HTTP login done. cookies=${login_resp.cookies} RETURN bangsess Login As Admin [Documentation] Creates a new context and page and logs in via UI. ... Caller should NOT call New Context/New Page before this. # Check setup status via HTTP API. ${response}= GET ${BACKEND_URL}/api/v1/setup ${body}= Set Variable ${response.json()} Log Setup completed: ${body}[completed] IF not ${body}[completed] # Complete setup wizard via HTTP API. ${setup_payload}= Create Dictionary ... master_password=Hallo123! ... database_path=bangui.db ... fail2ban_socket=/var/run/fail2ban/fail2ban.sock ... timezone=UTC ... session_duration_minutes=${60} POST ${BACKEND_URL}/api/v1/setup json=${setup_payload} Log Setup POST completed. END # Create browser context. New Context New Page Go To ${FRONTEND_URL} Wait For Load State domcontentloaded # Wait for React to fully initialize before login attempt Sleep 5s # Use fetch to call login API with browser credentials so the session cookie # gets stored in the browser context. Use relative URL so Vite proxy handles it. ${login_result}= Evaluate JavaScript ${None} ... async () => { ... try { ... // Wait for React to fully initialize. ... await new Promise(r => setTimeout(r, 2000)); ... const res = await fetch('/api/v1/auth/login', { ... method: 'POST', ... headers: { 'Content-Type': 'application/json' }, ... body: JSON.stringify({ password: 'Hallo123!' }), ... credentials: 'include' ... }); ... const data = await res.json().catch(() => ({})); ... return { ok: res.ok, status: res.status, data }; ... } catch(e) { ... return { ok: false, error: String(e) }; ... } ... } Log API login result: ${login_result} # Check if login actually succeeded before marking as authenticated ${login_ok}= Set Variable ${login_result}[ok] IF not ${login_ok} Fatal Error Login API failed: ${login_result} END # Set sessionStorage so AuthProvider considers us authenticated without waiting # for API re-validation on the next navigation. Evaluate JavaScript ${None} () => sessionStorage.setItem('bangui_authenticated', 'true') # Navigate directly to the dashboard instead of Reload. Reload causes a race # where useSessionValidation's API call may redirect to /login before main renders. # Going to / forces the SPA router to resolve routes while sessionStorage is already set. Go To ${FRONTEND_URL}/ Wait For Load State domcontentloaded # Poll for main to appear. The SPA remounts on navigation so domcontentloaded fires # before React has finished authenticating and rendering the protected route. ${login_ok}= Set Variable ${TRUE} FOR ${i} IN RANGE 1 16 ${url}= Get URL IF '/login' in '${url}' # Still on /login after navigation — login did not succeed. ${login_ok}= Set Variable ${FALSE} EXIT FOR LOOP END ${found}= Run Keyword And Return Status Wait For Elements State css=main visible timeout=2s IF ${found} BREAK END END IF not ${login_ok} ${last_result}= Set Variable ${login_result} Fatal Error Login failed: ${last_result} END ${final_url}= Get URL Log Login complete. URL: ${final_url}