Files
2026-09-25 18:11:14 +02:00

83 lines
4.0 KiB
Python

"""Interactive read-only SmartTime UI probe (separate from the app).
Reads JSON commands from stdin: snapshot, login, click (text), goto (url), quit.
Artifacts stay local; no credential or cookie values are printed.
"""
import json
import sys
from datetime import datetime
from pathlib import Path
ROOT = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(ROOT / "stempelbot"))
from playwright.sync_api import sync_playwright
from settings import settings
def main():
output = ROOT / ".local" / "smarttime" / datetime.now().strftime("%Y%m%d-%H%M%S")
output.mkdir(parents=True, exist_ok=True)
with sync_playwright() as pw:
browser = pw.chromium.launch(headless=True)
context = browser.new_context(ignore_https_errors=True, locale="de-DE", viewport={"width": 1440, "height": 1100})
page = context.new_page()
page.set_default_timeout(8000)
page.goto(settings.SMART_TIME_URL, wait_until="domcontentloaded", timeout=20000)
print(f"ARTIFACTS {output}", flush=True)
count = 0
while True:
try:
command = json.loads(input("COMMAND> "))
action = command["action"]
if action == "quit":
break
if action == "evaluate":
print(json.dumps(page.evaluate(command["js"]), ensure_ascii=True), flush=True)
continue
if action == "run":
import runpy
runpy.run_path(command["file"])["run"](page, output)
continue
if action == "login":
page.locator(command["user"]).fill(settings.USERNAME)
page.locator(command["password"]).fill(settings.SMART_TIME_PASSWORD)
page.locator(command["password"]).press("Enter")
elif action == "click":
page.get_by_text(command["text"], exact=True).click()
elif action == "selector":
page.locator(command["selector"]).click()
elif action == "fill":
page.locator(command["selector"]).fill(command["value"])
page.locator(command["selector"]).press("Enter")
elif action == "goto":
page.goto(command["url"], wait_until="domcontentloaded", timeout=20000)
elif action == "inspect":
print(page.locator(command["selector"]).evaluate_all("els => els.map(e => e.outerHTML)"), flush=True)
count += 1
name = f"{count:02d}-{action}"
page.screenshot(path=str(output / f"{name}.png"), full_page=True, timeout=10000)
# Clone DOM and remove input values before persisting snapshots.
html = page.evaluate("""() => { const root = document.documentElement.cloneNode(true);
root.querySelectorAll('input').forEach(e => e.removeAttribute('value'));
return '<!DOCTYPE html>' + root.outerHTML; }""")
(output / f"{name}.html").write_text(html, encoding="utf-8")
print("URL", page.url, flush=True)
print(page.locator("body").inner_text(timeout=5000), flush=True)
if action == "inspect" or "login" in page.url:
print("CONTROLS", page.locator("input,button,a,select,[role=tab]").evaluate_all("els => els.map(e => ({tag:e.tagName, name:e.getAttribute('name'), type:e.getAttribute('type'), id:e.id, text:e.innerText, title:e.title, href:e.getAttribute('href')}))"), flush=True)
except EOFError:
break
except Exception as exc:
message = str(exc)
for secret in (settings.SMART_TIME_PASSWORD, settings.PASSWORD):
if secret:
message = message.replace(secret, "***")
print(type(exc).__name__, message, flush=True)
context.close()
browser.close()
if __name__ == "__main__":
main()