mockup_agent / playwright_model.py
dina1's picture
Update playwright_model.py
fbc91e5 verified
import asyncio
import os
from playwright.async_api import async_playwright
from PIL import Image
from fpdf import FPDF
OUTPUT_DIR = "static/outputs"
async def capture_workflows(public_url: str, pdf_filename: str = "workflow_screens.pdf"):
os.makedirs(OUTPUT_DIR, exist_ok=True)
pdf_path = os.path.join(OUTPUT_DIR, pdf_filename)
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
page = await browser.new_page()
print(f"Opening page: {public_url}")
await page.goto(public_url, wait_until="load")
# --- Utility: wait for stable layout ---
async def wait_for_layout_stable():
await page.evaluate("""
(async () => {
let stableCount = 0;
let lastHeight = document.body.scrollHeight;
await new Promise((resolve) => {
const check = setInterval(() => {
const current = document.body.scrollHeight;
if (Math.abs(current - lastHeight) < 1) {
stableCount++;
if (stableCount >= 5) {
clearInterval(check);
setTimeout(resolve, 300);
}
} else {
stableCount = 0;
lastHeight = current;
}
}, 100);
});
})();
""")
# --- Fix only sidebar; keep top bar dynamic per screen ---
async def fix_layout_dynamically():
try:
await page.evaluate("""
const sidebar = document.querySelector('aside, .sidebar, nav');
const topbar = document.querySelector('.top-bar, header, .app-header, .navbar, .page-header');
// Reset old fixed styles
[sidebar, topbar].forEach(el => {
if (!el) return;
el.style.position = '';
el.style.top = '';
el.style.left = '';
el.style.width = '';
el.style.height = '';
el.style.zIndex = '';
el.style.overflow = '';
el.style.transition = '';
});
// Fix sidebar only (topbar remains dynamic)
if (sidebar) {
const rect = sidebar.getBoundingClientRect();
const sidebarWidth = rect.width || sidebar.offsetWidth || 220;
const topOffset = topbar ? topbar.offsetHeight || 60 : 60;
sidebar.style.position = 'fixed';
sidebar.style.top = topOffset + 'px';
sidebar.style.left = '0';
sidebar.style.height = `calc(100vh - ${topOffset}px)`;
sidebar.style.width = sidebarWidth + 'px';
sidebar.style.zIndex = '1000';
sidebar.style.overflow = 'auto';
sidebar.style.transition = 'none';
sidebar.dataset.locked = 'true';
}
const main = document.querySelector('main, .main-content, .app-body, .content');
if (main && sidebar) {
main.style.marginLeft = sidebar.offsetWidth + 'px';
main.style.marginTop = topbar ? (topbar.offsetHeight || 60) + 'px' : '0';
main.style.position = 'relative';
}
window.scrollTo(0, 0);
""")
except Exception as e:
print(f"[WARN] Layout fix failed: {e}")
# --- Wait until sidebar exists ---
await page.wait_for_selector(
"aside, .sidebar, nav, .side-panel, .left-menu, .menu",
timeout=20000
)
await wait_for_layout_stable()
await fix_layout_dynamically()
await asyncio.sleep(1)
# --- JS Logic: detect workflows and tabs dynamically ---
js_logic = """
(function(){
const menus=[...document.querySelectorAll('.menu-item')];
const screens=[...document.querySelectorAll('.screen')];
window.__ordered=[];
const seen=new Set();
for(const m of menus){
let id=m.dataset.screen||m.dataset.target;
if(!id){
const href=m.getAttribute('href');
if(href && href.startsWith('#')) id=href.substring(1);
}
const s=screens.find(x=>x.id===id);
if(s && !seen.has(s)){seen.add(s);window.__ordered.push({menu:m,screen:s});}
}
for(const s of screens)
if(!seen.has(s))
window.__ordered.push({menu:null,screen:s});
window.__visitedWorkflows=[];
window.__currentIndex=0;
window.__done=false;
window.__getSubScreens = function(screen){
const tabs=[...screen.querySelectorAll('.tab, .nav-link, [role="tab"], [data-tab], .sub-tab, .tab-item')];
const list=[];
for(const t of tabs){
const sub=t.textContent?.trim();
if(sub && !list.includes(sub)) list.push(sub);
}
return list;
};
window.__captureNext=async function(){
if(window.__done) return false;
if(window.__currentIndex>=window.__ordered.length){window.__done=true;return false;}
const pair=window.__ordered[window.__currentIndex];
const {menu,screen}=pair;
if(!screen){window.__done=true;return false;}
const wfName=screen.id || screen.getAttribute('data-name') || ('screen_'+window.__currentIndex);
if(window.__visitedWorkflows.includes(wfName)){
window.__currentIndex++;
return window.__captureNext();
}
window.__visitedWorkflows.push(wfName);
document.querySelectorAll('.screen').forEach(s=>s.classList.remove('active'));
document.querySelectorAll('.menu-item').forEach(m=>m.classList.remove('active'));
screen.classList.add('active');
if(menu) menu.classList.add('active');
screen.scrollIntoView({behavior:'smooth',block:'center'});
window.__currentIndex++;
const subs = window.__getSubScreens(screen);
return {screenName:wfName, subScreens:subs};
};
window.__clickSubScreen = async function(name){
const tabs=[...document.querySelectorAll('.tab, .nav-link, [role="tab"], [data-tab], .sub-tab, .tab-item')];
const t=tabs.find(x=>x.textContent.trim()===name);
if(t){t.click(); return true;}
return false;
};
})();
"""
await page.evaluate(js_logic)
await asyncio.sleep(1)
screenshots = []
index = 0
# --- Capture all screens ---
while True:
result = await page.evaluate("window.__captureNext()")
if not result:
break
screen_name = result.get("screenName", f"screen_{index}")
sub_screens = result.get("subScreens", [])
screenshot_path = os.path.join(OUTPUT_DIR, f"{screen_name}.png")
print(f"📸 Capturing main screen: {screen_name}")
await wait_for_layout_stable()
await fix_layout_dynamically()
await asyncio.sleep(1.0)
await page.screenshot(path=screenshot_path, full_page=True)
screenshots.append(screenshot_path)
# --- Capture sub-tabs ---
first_active_skipped = False
for sub in sub_screens:
is_active = await page.evaluate(
"""(subText) => {
const tabs=[...document.querySelectorAll('.tab, .nav-link, [role="tab"], [data-tab], .sub-tab, .tab-item')];
const t=tabs.find(x=>x.textContent.trim()===subText);
if(!t) return false;
const cls=t.getAttribute('class')||'';
return cls.includes('active');
}""",
sub
)
if is_active and not first_active_skipped:
first_active_skipped = True
continue
print(f" ↳ Capturing sub-screen: {sub}")
await page.evaluate(f"window.__clickSubScreen('{sub}')")
await wait_for_layout_stable()
await fix_layout_dynamically()
await asyncio.sleep(1.0)
sub_name_clean = sub.replace(" ", "_").lower()
sub_path = os.path.join(OUTPUT_DIR, f"{screen_name}_{sub_name_clean}.png")
await page.screenshot(path=sub_path, full_page=True)
screenshots.append(sub_path)
index += 1
await browser.close()
# --- Generate PDF ---
if not screenshots:
raise RuntimeError("No screenshots captured — check if .screen elements exist!")
print(f"🧾 Combining {len(screenshots)} screenshots into PDF: {pdf_path}")
pdf = FPDF()
for img_path in screenshots:
image = Image.open(img_path)
w, h = image.size
pdf_w, pdf_h = 210, 297
aspect = h / w
pdf.add_page()
pdf.image(img_path, 0, 0, pdf_w, pdf_w * aspect)
pdf.output(pdf_path, "F")
print(f"✅ PDF generated successfully: {pdf_path}")
return pdf_path
generate_ui_report = capture_workflows