From fca7acb5a3808739d52987b41f847f5389a5d3c9 Mon Sep 17 00:00:00 2001 From: Jared Vititoe Date: Sat, 3 Oct 2026 16:58:52 -0400 Subject: [PATCH] scripts: tidy capture-screenshots.py --- .gitignore | 1 + scripts/capture-screenshots.py | 189 ++++++++++++++++++++++----------- 2 files changed, 129 insertions(+), 61 deletions(-) diff --git a/.gitignore b/.gitignore index d935835..96fa206 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules/ .env *.env +scripts/__pycache__/ diff --git a/scripts/capture-screenshots.py b/scripts/capture-screenshots.py index 4a7c0d8..a71bfbd 100644 --- a/scripts/capture-screenshots.py +++ b/scripts/capture-screenshots.py @@ -5,86 +5,153 @@ Serve: mkdir /tmp/wt && cd /tmp/wt && ln -s web_template && ln -s && python3 -m http.server 8100 Run: python3 scripts/capture-screenshots.py """ -import asyncio, io + +# flake8: noqa: E501 (demo data and canned output contain long literal lines) + +import asyncio +import io from PIL import Image from playwright.async_api import async_playwright import pathlib -OUT=str(pathlib.Path(__file__).resolve().parent.parent/'docs'/'img') -URL='http://127.0.0.1:8100/base.html' # serve so that /web_template/base.css and /base.html both resolve -TOP="document.activeElement&&document.activeElement.blur();document.documentElement.style.scrollBehavior='auto';window.scrollTo(0,0)" + +OUT = str(pathlib.Path(__file__).resolve().parent.parent / "docs" / "img") +URL = "http://127.0.0.1:8100/base.html" # serve so that /web_template/base.css and /base.html both resolve +TOP = ( + "document.activeElement&&document.activeElement.blur();" + "document.documentElement.style.scrollBehavior='auto';window.scrollTo(0,0)" +) HIDE = "document.addEventListener('DOMContentLoaded',()=>{document.querySelectorAll('div').forEach(d=>{if(d.style.position==='fixed'&&d.style.bottom==='1rem'&&d.style.left==='1rem')d.style.display='none'})})" -async def hdr(pg,text): - h=pg.locator('.lt-section-header', has_text=text).first - return h.locator('xpath=..') if await h.count() else None + + +async def hdr(pg, text): + h = pg.locator(".lt-section-header", has_text=text).first + return h.locator("xpath=..") if await h.count() else None + + async def theme_run(theme): async with async_playwright() as p: - b=await p.chromium.launch() - ctx=await b.new_context(viewport={'width':1440,'height':900}) + b = await p.chromium.launch() + ctx = await b.new_context(viewport={"width": 1440, "height": 900}) await ctx.add_init_script(HIDE) await ctx.add_init_script(f"try{{localStorage.setItem('lt_theme','{theme}')}}catch(e){{}}") - pg=await ctx.new_page(); await pg.goto(URL); await pg.wait_for_timeout(5000) - await pg.evaluate(TOP); await pg.wait_for_timeout(500) - await pg.screenshot(path=f'{OUT}/{theme}-dashboard.png', full_page=True, clip={'x':0,'y':0,'width':1440,'height':900}) - if theme=='dark': - async def clipsec(text,name,extra=None,fixed=None): - h=pg.locator('.lt-section-header', has_text=text).first - if not await h.count(): print('missing',text); return + pg = await ctx.new_page() + await pg.goto(URL) + await pg.wait_for_timeout(5000) + await pg.evaluate(TOP) + await pg.wait_for_timeout(500) + await pg.screenshot( + path=f"{OUT}/{theme}-dashboard.png", full_page=True, clip={"x": 0, "y": 0, "width": 1440, "height": 900} + ) + if theme == "dark": + + async def clipsec(text, name, extra=None, fixed=None): + h = pg.locator(".lt-section-header", has_text=text).first + if not await h.count(): + print("missing", text) + return await h.scroll_into_view_if_needed() - hb=await h.bounding_box(); sy=await pg.evaluate('window.scrollY') - top=hb['y']+sy - if fixed: bot=top+fixed + hb = await h.bounding_box() + sy = await pg.evaluate("window.scrollY") + top = hb["y"] + sy + if fixed: + bot = top + fixed else: - body=h.locator('xpath=following-sibling::*[1]'); bb=await body.bounding_box(); bot=bb['y']+sy+bb['height'] - await pg.screenshot(path=f'{OUT}/{name}.png', full_page=True, clip={'x':0,'y':top-4,'width':1440,'height':bot-top+8}); print('ok',name) + body = h.locator("xpath=following-sibling::*[1]") + bb = await body.bounding_box() + bot = bb["y"] + sy + bb["height"] + await pg.screenshot( + path=f"{OUT}/{name}.png", + full_page=True, + clip={"x": 0, "y": top - 4, "width": 1440, "height": bot - top + 8}, + ) + print("ok", name) + await pg.evaluate(TOP) - await clipsec('Ticket Queue','ticket-queue') - await clipsec('Component Reference','components',fixed=520) - await clipsec('Alert Banners','alerts') - await clipsec('SLA Banners','sla-banners') - await clipsec('Data Grid','data-grid') - await clipsec('Progress Bars','progress-bars') - await pg.evaluate(TOP); await pg.wait_for_timeout(300) - kb=pg.get_by_text('Kanban',exact=False).first + await clipsec("Ticket Queue", "ticket-queue") + await clipsec("Component Reference", "components", fixed=520) + await clipsec("Alert Banners", "alerts") + await clipsec("SLA Banners", "sla-banners") + await clipsec("Data Grid", "data-grid") + await clipsec("Progress Bars", "progress-bars") + await pg.evaluate(TOP) + await pg.wait_for_timeout(300) + kb = pg.get_by_text("Kanban", exact=False).first try: - await kb.click(timeout=3000); await pg.wait_for_timeout(900); await pg.evaluate(TOP); await pg.wait_for_timeout(500) - await pg.screenshot(path=f'{OUT}/kanban.png', full_page=True, clip={'x':0,'y':0,'width':1440,'height':900}); print('ok kanban') - except Exception as e: print('kanban fail',str(e)[:80]) - await pg.evaluate(TOP); await pg.keyboard.press('Control+k'); await pg.wait_for_timeout(500) - await pg.keyboard.type('ti',delay=80); await pg.wait_for_timeout(500) - await pg.screenshot(path=f'{OUT}/command-palette.png', clip={'x':320,'y':60,'width':800,'height':420}) + await kb.click(timeout=3000) + await pg.wait_for_timeout(900) + await pg.evaluate(TOP) + await pg.wait_for_timeout(500) + await pg.screenshot( + path=f"{OUT}/kanban.png", full_page=True, clip={"x": 0, "y": 0, "width": 1440, "height": 900} + ) + print("ok kanban") + except Exception as e: + print("kanban fail", str(e)[:80]) + await pg.evaluate(TOP) + await pg.keyboard.press("Control+k") + await pg.wait_for_timeout(500) + await pg.keyboard.type("ti", delay=80) + await pg.wait_for_timeout(500) + await pg.screenshot( + path=f"{OUT}/command-palette.png", clip={"x": 320, "y": 60, "width": 800, "height": 420} + ) await b.close() + + async def gif(): - frames=[]; + frames = [] async with async_playwright() as p: - b=await p.chromium.launch() - ctx=await b.new_context(viewport={'width':1280,'height':720}) + b = await p.chromium.launch() + ctx = await b.new_context(viewport={"width": 1280, "height": 720}) await ctx.add_init_script(HIDE) await ctx.add_init_script("try{localStorage.setItem('lt_theme','dark')}catch(e){}") - pg=await ctx.new_page(); await pg.goto(URL); await pg.wait_for_timeout(1200) - 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 snap(2,300) # boot sequence - await snap(2,1200); await snap(1,1500) - await pg.evaluate(TOP); await pg.wait_for_timeout(600); await snap(3) - await pg.keyboard.press('Control+k'); await snap(2,500) - for ch in 'new': - await pg.keyboard.type(ch); await snap(1,250) - await snap(3,300) - await pg.keyboard.press('Enter'); await snap(3,600) # toast + pg = await ctx.new_page() + await pg.goto(URL) + await pg.wait_for_timeout(1200) + + 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 snap(2, 300) # boot sequence + await snap(2, 1200) + await snap(1, 1500) + await pg.evaluate(TOP) + await pg.wait_for_timeout(600) + await snap(3) + await pg.keyboard.press("Control+k") + await snap(2, 500) + for ch in "new": + await pg.keyboard.type(ch) + await snap(1, 250) + await snap(3, 300) + await pg.keyboard.press("Enter") + await snap(3, 600) # toast await pg.wait_for_timeout(1500) - await pg.evaluate("lt.theme.set('light')"); await snap(3,600) - await pg.evaluate("lt.theme.set('dark')"); await snap(2,600) - l=await hdr(pg,'Component Reference') + await pg.evaluate("lt.theme.set('light')") + await snap(3, 600) + await pg.evaluate("lt.theme.set('dark')") + await snap(2, 600) + l = await hdr(pg, "Component Reference") if l: - await l.scroll_into_view_if_needed(); await snap(3,500) - for y in (500,1000,1500): - await pg.mouse.wheel(0,500); await snap(2,500) + await l.scroll_into_view_if_needed() + await snap(3, 500) + for y in (500, 1000, 1500): + await pg.mouse.wheel(0, 500) + await snap(2, 500) 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=450,loop=0,optimize=True) - print('gif frames',len(frames)) + 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=450, loop=0, optimize=True) + print("gif frames", len(frames)) + + async def main(): - await theme_run('dark'); await theme_run('light'); await gif() + await theme_run("dark") + await theme_run("light") + await gif() + + asyncio.run(main())