Lint / PHP (phpcs PSR-12) (push) Failing after 31s
Lint / JS (eslint) (push) Successful in 8s
Lint / PHP requirements (version + extensions) (push) Successful in 22s
Lint / Deploy (push) Skipped
Security / PHP Security (semgrep) (push) Successful in 1m17s
Lint / Notify on failure (push) Successful in 8s
79 lines
5.3 KiB
Python
79 lines
5.3 KiB
Python
import asyncio, io, 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'<svg xmlns="http://www.w3.org/2000/svg" width="64" height="64"><rect width="64" height="64" fill="#050a10"/><text x="32" y="40" font-family="monospace" font-size="24" font-weight="700" fill="{col}" text-anchor="middle">{ini}</text></svg>'
|
|
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())
|