"""Open OpenLane in a real browser for manual exploration. Logs ALL network requests/responses so we can understand auth flow and API structure. Usage: python scripts/explore_site.py """ import json import sys from pathlib import Path from datetime import datetime from playwright.sync_api import sync_playwright, Page, Request, Response LOG_DIR = Path("artifacts/explore") LOG_DIR.mkdir(parents=True, exist_ok=True) REQUESTS_LOG = LOG_DIR / f"requests_{datetime.now().strftime('%H%M%S')}.jsonl" captured = [] def on_request(request: Request) -> None: entry = { "type": "request", "ts": datetime.now().isoformat(), "method": request.method, "url": request.url, "resource_type": request.resource_type, "headers": dict(request.headers) if request.resource_type in ("xhr", "fetch", "document") else {}, } # Log API and auth requests to console url = request.url.lower() if any(k in url for k in ("/api/", "/auth", "/token", "/session", "/sign_in", "/login", "/oauth")): print(f"\n>>> {request.method} {request.url}") if request.post_data: # Don't print passwords post = request.post_data[:500] if "password" in post.lower(): post = "[CONTAINS PASSWORD - HIDDEN]" print(f" body: {post}") entry["post_data"] = post if "password" not in post.lower() else "[HIDDEN]" captured.append(entry) def on_response(response: Response) -> None: url = response.url.lower() if any(k in url for k in ("/api/", "/auth", "/token", "/session", "/sign_in", "/login", "/oauth")): print(f"<<< {response.status} {response.url}") entry = { "type": "response", "ts": datetime.now().isoformat(), "status": response.status, "url": response.url, "headers": dict(response.headers), } # Try to capture response body for API/auth endpoints try: body = response.text() if len(body) > 2000: entry["body_preview"] = body[:2000] + "..." else: entry["body"] = body # Print short preview preview = body[:300] if len(body) > 300 else body print(f" body: {preview}") except Exception: entry["body"] = "[could not read]" captured.append(entry) def save_log(): with open(REQUESTS_LOG, "w", encoding="utf-8") as f: for entry in captured: f.write(json.dumps(entry, ensure_ascii=False) + "\n") print(f"\n[*] Saved {len(captured)} entries to {REQUESTS_LOG}") def main(): print("=" * 60) print("OpenLane Site Explorer") print("=" * 60) print("1. Browser will open at OpenLane sign_in page") print("2. Log in manually") print("3. Browse around — all API/auth requests are logged") print("4. When done, close the browser or press Ctrl+C here") print("=" * 60) with sync_playwright() as p: browser = p.chromium.launch( headless=False, args=[ "--disable-blink-features=AutomationControlled", "--no-default-browser-check", ], ) context = browser.new_context( viewport={"width": 1920, "height": 1080}, locale="en-US", timezone_id="America/New_York", user_agent=( "Mozilla/5.0 (Windows NT 10.0; Win64; x64) " "AppleWebKit/537.36 (KHTML, like Gecko) " "Chrome/135.0.0.0 Safari/537.36" ), ) page = context.new_page() page.on("request", on_request) page.on("response", on_response) print("\n[*] Opening https://app.openlane.com/sign_in ...") page.goto("https://app.openlane.com/sign_in", wait_until="domcontentloaded") print("\n[*] Browser is open. Log in and explore the site.") print("[*] I'm watching all network requests...") print("[*] When you're done, just close the browser window.\n") try: # Wait until the browser is closed by user page.wait_for_event("close", timeout=0) except KeyboardInterrupt: print("\n[*] Interrupted by user") except Exception: pass # Capture cookies and storage state before closing try: cookies = context.cookies() storage = context.storage_state() cookies_file = LOG_DIR / "cookies.json" storage_file = LOG_DIR / "storage_state.json" with open(cookies_file, "w", encoding="utf-8") as f: json.dump(cookies, f, indent=2, ensure_ascii=False) with open(storage_file, "w", encoding="utf-8") as f: json.dump(storage, f, indent=2, ensure_ascii=False) print(f"\n[*] Cookies saved to {cookies_file}") print(f"[*] Storage state saved to {storage_file}") # Print cookie names print(f"\n[*] Cookies ({len(cookies)}):") for c in cookies: print(f" {c['name']} = {c['value'][:30]}... (domain={c['domain']}, httpOnly={c.get('httpOnly', '?')})") except Exception as e: print(f"[!] Could not save state: {e}") browser.close() save_log() print("\n[*] Done! Check artifacts/explore/ for logs.") if __name__ == "__main__": main()