#!/usr/bin/env python3 """ diagnose-vp-compliance.py -- Reproduce the vp-compliance login with full DOM dumps at every step to find where it diverges from the other agents. Usage: docker compose run --rm --entrypoint python3 provision diagnose-vp-compliance.py """ import os, sys, time from pathlib import Path from playwright.sync_api import sync_playwright sys.path.insert(0, os.path.dirname(__file__)) from bw_helper import BitwardenHelper CLOUDRON_BASE = "https://my.knownelement.com" STATE_DIR = Path("/app/state") ITEM = "vp-compliance Cloudron" EMAIL = "tsgstaff-coo-vpcompliance@turnsys.com" def dump(page, label): ts = time.strftime("%H%M%S") page.screenshot(path=str(STATE_DIR / f"diag-vpc-{label}-{ts}.png"), full_page=True) body = page.evaluate("() => document.body.innerText.substring(0, 300)") els = page.evaluate("""() => Array.from(document.querySelectorAll( 'input, [role="button"], button, .ui.message')).map(el => ({ tag: el.tagName, id: el.id, type: el.type||'', vis: el.offsetParent !== null, text: (el.textContent||'').trim().substring(0,50)}))""") print(f"--- {label} ---") print(f"URL: {page.url}") print(f"BODY: {body[:200]}") for e in els: print(f" <{e['tag']}> id={e['id']} type={e['type']} vis={e['vis']} text={e['text']!r}") def main(): bw = BitwardenHelper( client_id=os.environ["BW_CLIENTID"], client_secret=os.environ["BW_CLIENTSECRET"], password=os.environ["BW_PASSWORD"], server_url=os.environ.get("BW_SERVER", ""), ) bw.login() # Resolve by exact name to dodge the fuzzy-search ambiguity item_id = bw.get_item_id(ITEM) item = json.loads(bw._run_bw(["get", "item", item_id])) password = item["login"]["password"] with sync_playwright() as p: browser = p.chromium.launch(headless=True) context = browser.new_context(viewport={"width": 1280, "height": 1024}) page = context.new_page() page.goto(f"{CLOUDRON_BASE}/login.html", wait_until="networkidle", timeout=20000) page.wait_for_timeout(2000) dump(page, "01-login-page") page.wait_for_selector("#inputPassword", timeout=15000) page.click("#inputUsername") page.keyboard.type(EMAIL) page.click("#inputPassword") page.keyboard.type(password) dump(page, "02-filled") btn = page.locator('[role="button"]:has-text("Log in")') print(f"login button count={btn.count()} visible={btn.first.is_visible() if btn.count() else '?'}") print(f"login button classes: {btn.first.get_attribute('class') if btn.count() else '?'}") btn.first.click() page.wait_for_timeout(5000) dump(page, "03-after-submit") totp = page.query_selector("#inputTotpToken") print(f"TOTP field present={totp is not None} visible={totp.is_visible() if totp else False}") if totp and totp.is_visible(): code = bw._run_bw(["get", "totp", item_id]) totp.click() page.keyboard.type(code) page.locator("#totpTokenSubmitButton").click() page.wait_for_timeout(5000) dump(page, "04-after-totp") browser.close() import json # noqa: E402 if __name__ == "__main__": main()