scripts: tidy capture-screenshots.py
Lint / JS (eslint) (push) Successful in 10s

This commit is contained in:
2026-10-03 16:59:02 -04:00
parent dfb45f1230
commit fca7acb5a3
2 changed files with 129 additions and 61 deletions
+1
View File
@@ -1,3 +1,4 @@
node_modules/
.env
*.env
scripts/__pycache__/
+128 -61
View File
@@ -5,86 +5,153 @@ Serve: mkdir /tmp/wt && cd /tmp/wt && ln -s <repo> web_template && ln -s <repo>
&& 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())