# flake8: noqa: E501 (demo data and canned output contain long literal lines) import asyncio import io import sys from PIL import Image from playwright.async_api import async_playwright import pathlib OUT = str(pathlib.Path(__file__).resolve().parents[2] / "docs" / "img") B = "http://127.0.0.1:8300" H = { "Remote-User": "alex.rivera", "Remote-Name": "Alex Rivera", "Remote-Email": "alex@example.com", "Remote-Groups": "admin,employee", } P1 = "152847156" CEPH = "524938499" JS_TOP = ( "document.activeElement&&document.activeElement.blur();" "document.documentElement.style.scrollBehavior='auto';window.scrollTo(0,0)" ) async def ctxmk(p, theme="dark", w=1440, h=900): b = await p.chromium.launch() c = await b.new_context(viewport={"width": w, "height": h}, extra_http_headers=H) await c.add_init_script(f"try{{localStorage.setItem('lt_theme','{theme}')}}catch(e){{}}") async def avatar(route): import re uid = int((re.search(r"user_id=(\d+)", route.request.url) or [0, 0])[1] or 0) ini = {1: "AR", 2: "SC", 3: "RB", 4: "JK", 5: "HW"}.get(uid, "??") col = {1: "#ff6b00", 2: "#00d4ff", 3: "#b44aff", 4: "#00ff88", 5: "#ffc800"}.get(uid, "#888") svg = f'{ini}' await route.fulfill(status=200, content_type="image/svg+xml", body=svg) await c.route("**/api/user_avatar.php*", avatar) return b, c async def go(pg, path, wait=3500): await pg.goto(B + path) await pg.wait_for_timeout(wait) await pg.evaluate(JS_TOP) await pg.wait_for_timeout(300) async def tab(pg, name): t = pg.get_by_role("tab", name=name).first if await t.count() == 0: t = pg.get_by_text(name, exact=False).first await t.click(timeout=4000) await pg.wait_for_timeout(900) async def shots(): async with async_playwright() as p: b, c = await ctxmk(p) pg = await c.new_page() await go(pg, "/", 5000) await pg.screenshot(path=f"{OUT}/dashboard.png") # table crop await pg.set_viewport_size({"width": 1840, "height": 900}) await pg.wait_for_timeout(600) await pg.evaluate("document.querySelector('.lt-tabs, [role=tablist]')?.scrollIntoView({block:'start'})") await pg.wait_for_timeout(500) await pg.screenshot(path=f"{OUT}/ticket-queue.png") await pg.set_viewport_size({"width": 1440, "height": 900}) await pg.wait_for_timeout(600) try: await tab(pg, "Kanban") await pg.screenshot(path=f"{OUT}/kanban.png") print("kanban ok") except Exception as e: print("kanban fail", str(e)[:80]) await go(pg, "/ticket/" + P1) await pg.screenshot(path=f"{OUT}/ticket-sla.png") await go(pg, "/ticket/" + CEPH) for name, fn in ( ("Comments", "ticket-comments"), ("Dependencies", "ticket-dependencies"), ("Activity", "ticket-activity"), ): try: await tab(pg, name) await pg.evaluate("document.querySelector('.lt-tabs, [role=tablist]')?.scrollIntoView({block:'start'})") await pg.wait_for_timeout(500) await pg.screenshot(path=f"{OUT}/{fn}.png") print(fn, "ok") except Exception as e: print(fn, "fail", str(e)[:80]) await go(pg, "/ticket/create", 3000) await pg.screenshot(path=f"{OUT}/new-ticket.png") await go(pg, "/") await pg.keyboard.press("Control+k") await pg.wait_for_timeout(500) await pg.keyboard.type("ticket", delay=70) await pg.wait_for_timeout(900) await pg.screenshot(path=f"{OUT}/command-palette.png", clip={"x": 320, "y": 60, "width": 800, "height": 460}) await pg.keyboard.press("Escape") for path, name in ( ("/admin/workflow", "admin-workflow"), ("/admin/api-keys", "admin-api-keys"), ("/admin/audit-log", "admin-audit-log"), ("/admin/recurring-tickets", "admin-recurring"), ): await go(pg, path, 3500) await pg.screenshot(path=f"{OUT}/{name}.png") print(name, "ok") await b.close() b, c = await ctxmk(p, "light") pg = await c.new_page() await go(pg, "/", 5000) await pg.screenshot(path=f"{OUT}/dashboard-light.png") await b.close() async def gif(): frames = [] async with async_playwright() as p: b, c = await ctxmk(p, "dark", 1280, 720) pg = await c.new_page() async def snap(n=1, ms=0): if ms: await pg.wait_for_timeout(ms) im = Image.open(io.BytesIO(await pg.screenshot())).convert("RGB").resize((960, 540), Image.LANCZOS) for _ in range(n): frames.append(im) await go(pg, "/", 4500) await snap(3) await pg.mouse.wheel(0, 520) await snap(2, 500) try: await tab(pg, "Kanban") await snap(3, 300) except Exception: pass await go(pg, "/ticket/" + P1, 3000) await snap(3) await go(pg, "/ticket/" + CEPH, 3000) await snap(2) for name in ("Comments", "Dependencies", "Activity"): try: await tab(pg, name) await snap(3, 300) except Exception: pass await go(pg, "/", 3000) await pg.keyboard.press("Control+k") await snap(2, 500) for ch in "tick": await pg.keyboard.type(ch) await snap(1, 200) await snap(3, 300) await go(pg, "/admin/workflow", 3000) await snap(3) await b.close() pal = [f.quantize(colors=96, method=Image.MEDIANCUT, dither=Image.NONE) for f in frames] pal[0].save(f"{OUT}/demo.gif", save_all=True, append_images=pal[1:], duration=1100, loop=0, optimize=True) print("gif", len(frames)) async def main(): if "gif" not in sys.argv: await shots() if "shots" not in sys.argv: await gif() asyncio.run(main())