dina1 commited on
Commit
7085457
·
verified ·
1 Parent(s): d038c37

Update playwright_model.py

Browse files
Files changed (1) hide show
  1. playwright_model.py +139 -92
playwright_model.py CHANGED
@@ -1,28 +1,26 @@
1
- import asyncio
2
  import os
3
- from playwright.async_api import async_playwright
4
  from PIL import Image
5
  from fpdf import FPDF
6
 
7
  OUTPUT_DIR = "static/outputs"
8
 
9
- async def capture_workflows(public_url: str, pdf_filename: str = "workflow_screens.pdf"):
10
  os.makedirs(OUTPUT_DIR, exist_ok=True)
11
  pdf_path = os.path.join(OUTPUT_DIR, pdf_filename)
12
 
13
- async with async_playwright() as p:
14
- browser = await p.chromium.launch(headless=True)
15
- page = await browser.new_page()
16
  print(f"Opening page: {public_url}")
17
- await page.goto(public_url, wait_until="load")
18
 
19
- # --- Utility: wait for stable layout ---
20
- async def wait_for_layout_stable():
21
- await page.evaluate("""
22
- (async () => {
23
  let stableCount = 0;
24
  let lastHeight = document.body.scrollHeight;
25
- await new Promise((resolve) => {
26
  const check = setInterval(() => {
27
  const current = document.body.scrollHeight;
28
  if (Math.abs(current - lastHeight) < 1) {
@@ -40,69 +38,128 @@ async def capture_workflows(public_url: str, pdf_filename: str = "workflow_scree
40
  })();
41
  """)
42
 
43
- # --- Fix only sidebar; keep top bar dynamic per screen ---
44
- async def fix_layout_dynamically():
45
  try:
46
- await page.evaluate("""
47
- const sidebar = document.querySelector('aside, .sidebar, nav');
48
- const topbar = document.querySelector('.top-bar, header, .app-header, .navbar, .page-header');
49
-
50
- // Reset old fixed styles
51
- [sidebar, topbar].forEach(el => {
52
- if (!el) return;
53
- el.style.position = '';
54
- el.style.top = '';
55
- el.style.left = '';
56
- el.style.width = '';
57
- el.style.height = '';
58
- el.style.zIndex = '';
59
- el.style.overflow = '';
60
- el.style.transition = '';
61
- });
62
-
63
- // Fix sidebar only (topbar remains dynamic)
64
- if (sidebar) {
65
- const rect = sidebar.getBoundingClientRect();
66
- const sidebarWidth = rect.width || sidebar.offsetWidth || 220;
67
- const topOffset = topbar ? topbar.offsetHeight || 60 : 60;
68
-
69
- sidebar.style.position = 'fixed';
70
- sidebar.style.top = topOffset + 'px';
71
- sidebar.style.left = '0';
72
- sidebar.style.height = `calc(100vh - ${topOffset}px)`;
73
- sidebar.style.width = sidebarWidth + 'px';
74
- sidebar.style.zIndex = '1000';
75
- sidebar.style.overflow = 'auto';
76
- sidebar.style.transition = 'none';
77
- sidebar.dataset.locked = 'true';
78
- }
79
-
80
- const main = document.querySelector('main, .main-content, .app-body, .content');
81
- if (main && sidebar) {
82
- main.style.marginLeft = sidebar.offsetWidth + 'px';
83
- main.style.marginTop = topbar ? (topbar.offsetHeight || 60) + 'px' : '0';
84
- main.style.position = 'relative';
85
- }
86
-
87
- window.scrollTo(0, 0);
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
  """)
89
  except Exception as e:
90
  print(f"[WARN] Layout fix failed: {e}")
91
 
92
- # --- Wait until sidebar exists ---
93
- await page.wait_for_selector("aside, .sidebar, nav", timeout=5000)
94
- await wait_for_layout_stable()
95
- await fix_layout_dynamically()
96
- await asyncio.sleep(1)
97
 
98
- # --- JS Logic: detect workflows and tabs dynamically ---
99
  js_logic = """
100
  (function(){
101
  const menus=[...document.querySelectorAll('.menu-item')];
102
  const screens=[...document.querySelectorAll('.screen')];
103
  window.__ordered=[];
104
  const seen=new Set();
105
-
106
  for(const m of menus){
107
  let id=m.dataset.screen||m.dataset.target;
108
  if(!id){
@@ -112,15 +169,12 @@ async def capture_workflows(public_url: str, pdf_filename: str = "workflow_scree
112
  const s=screens.find(x=>x.id===id);
113
  if(s && !seen.has(s)){seen.add(s);window.__ordered.push({menu:m,screen:s});}
114
  }
115
-
116
  for(const s of screens)
117
  if(!seen.has(s))
118
  window.__ordered.push({menu:null,screen:s});
119
-
120
  window.__visitedWorkflows=[];
121
  window.__currentIndex=0;
122
  window.__done=false;
123
-
124
  window.__getSubScreens = function(screen){
125
  const tabs=[...screen.querySelectorAll('.tab, .nav-link, [role="tab"], [data-tab], .sub-tab, .tab-item')];
126
  const list=[];
@@ -130,21 +184,17 @@ async def capture_workflows(public_url: str, pdf_filename: str = "workflow_scree
130
  }
131
  return list;
132
  };
133
-
134
- window.__captureNext=async function(){
135
  if(window.__done) return false;
136
  if(window.__currentIndex>=window.__ordered.length){window.__done=true;return false;}
137
-
138
  const pair=window.__ordered[window.__currentIndex];
139
  const {menu,screen}=pair;
140
  if(!screen){window.__done=true;return false;}
141
-
142
  const wfName=screen.id || screen.getAttribute('data-name') || ('screen_'+window.__currentIndex);
143
  if(window.__visitedWorkflows.includes(wfName)){
144
  window.__currentIndex++;
145
  return window.__captureNext();
146
  }
147
-
148
  window.__visitedWorkflows.push(wfName);
149
  document.querySelectorAll('.screen').forEach(s=>s.classList.remove('active'));
150
  document.querySelectorAll('.menu-item').forEach(m=>m.classList.remove('active'));
@@ -152,12 +202,10 @@ async def capture_workflows(public_url: str, pdf_filename: str = "workflow_scree
152
  if(menu) menu.classList.add('active');
153
  screen.scrollIntoView({behavior:'smooth',block:'center'});
154
  window.__currentIndex++;
155
-
156
  const subs = window.__getSubScreens(screen);
157
  return {screenName:wfName, subScreens:subs};
158
  };
159
-
160
- window.__clickSubScreen = async function(name){
161
  const tabs=[...document.querySelectorAll('.tab, .nav-link, [role="tab"], [data-tab], .sub-tab, .tab-item')];
162
  const t=tabs.find(x=>x.textContent.trim()===name);
163
  if(t){t.click(); return true;}
@@ -165,15 +213,15 @@ async def capture_workflows(public_url: str, pdf_filename: str = "workflow_scree
165
  };
166
  })();
167
  """
168
- await page.evaluate(js_logic)
169
- await asyncio.sleep(1)
170
 
171
  screenshots = []
172
  index = 0
173
 
174
- # --- Capture all screens ---
175
  while True:
176
- result = await page.evaluate("window.__captureNext()")
177
  if not result:
178
  break
179
 
@@ -182,16 +230,16 @@ async def capture_workflows(public_url: str, pdf_filename: str = "workflow_scree
182
  screenshot_path = os.path.join(OUTPUT_DIR, f"{screen_name}.png")
183
  print(f"📸 Capturing main screen: {screen_name}")
184
 
185
- await wait_for_layout_stable()
186
- await fix_layout_dynamically()
187
- await asyncio.sleep(1.0)
188
- await page.screenshot(path=screenshot_path, full_page=True)
189
  screenshots.append(screenshot_path)
190
 
191
- # --- Capture sub-tabs ---
192
  first_active_skipped = False
193
  for sub in sub_screens:
194
- is_active = await page.evaluate(
195
  """(subText) => {
196
  const tabs=[...document.querySelectorAll('.tab, .nav-link, [role="tab"], [data-tab], .sub-tab, .tab-item')];
197
  const t=tabs.find(x=>x.textContent.trim()===subText);
@@ -206,21 +254,21 @@ async def capture_workflows(public_url: str, pdf_filename: str = "workflow_scree
206
  continue
207
 
208
  print(f" ↳ Capturing sub-screen: {sub}")
209
- await page.evaluate(f"window.__clickSubScreen('{sub}')")
210
- await wait_for_layout_stable()
211
- await fix_layout_dynamically()
212
- await asyncio.sleep(1.0)
213
 
214
  sub_name_clean = sub.replace(" ", "_").lower()
215
  sub_path = os.path.join(OUTPUT_DIR, f"{screen_name}_{sub_name_clean}.png")
216
- await page.screenshot(path=sub_path, full_page=True)
217
  screenshots.append(sub_path)
218
 
219
  index += 1
220
 
221
- await browser.close()
222
 
223
- # --- Generate PDF ---
224
  if not screenshots:
225
  raise RuntimeError("No screenshots captured — check if .screen elements exist!")
226
 
@@ -239,5 +287,4 @@ async def capture_workflows(public_url: str, pdf_filename: str = "workflow_scree
239
  print(f"✅ PDF generated successfully: {pdf_path}")
240
  return pdf_path
241
 
242
-
243
  generate_ui_report = capture_workflows
 
 
1
  import os
2
+ from playwright.sync_api import sync_playwright
3
  from PIL import Image
4
  from fpdf import FPDF
5
 
6
  OUTPUT_DIR = "static/outputs"
7
 
8
+ def capture_workflows(public_url: str, pdf_filename: str = "workflow_screens.pdf"):
9
  os.makedirs(OUTPUT_DIR, exist_ok=True)
10
  pdf_path = os.path.join(OUTPUT_DIR, pdf_filename)
11
 
12
+ with sync_playwright() as p:
13
+ browser = p.chromium.launch(headless=True)
14
+ page = browser.new_page()
15
  print(f"Opening page: {public_url}")
16
+ page.goto(public_url, wait_until="load")
17
 
18
+ def wait_for_layout_stable():
19
+ page.evaluate("""
20
+ (() => {
 
21
  let stableCount = 0;
22
  let lastHeight = document.body.scrollHeight;
23
+ return new Promise((resolve) => {
24
  const check = setInterval(() => {
25
  const current = document.body.scrollHeight;
26
  if (Math.abs(current - lastHeight) < 1) {
 
38
  })();
39
  """)
40
 
41
+ def fix_layout_dynamically():
 
42
  try:
43
+ page.evaluate("""
44
+ (() => {
45
+ console.log('🔧 Fixing layout...');
46
+
47
+ // Reset all layout elements
48
+ const resetSelectors = [
49
+ '.top-bar, header, .app-header, .navbar, .page-header',
50
+ 'aside, .sidebar, nav',
51
+ 'main, .main-content, .app-body, .content, .page-content'
52
+ ];
53
+
54
+ resetSelectors.forEach(selector => {
55
+ document.querySelectorAll(selector).forEach(el => {
56
+ if (el) {
57
+ el.style.position = '';
58
+ el.style.top = '';
59
+ el.style.left = '';
60
+ el.style.width = '';
61
+ el.style.height = '';
62
+ el.style.zIndex = '';
63
+ el.style.overflow = '';
64
+ el.style.transition = '';
65
+ el.style.marginLeft = '';
66
+ el.style.marginTop = '';
67
+ el.style.paddingTop = '';
68
+ }
69
+ });
70
+ });
71
+
72
+ document.body.style.marginTop = '';
73
+
74
+ // Get accurate height including margins
75
+ const getAccurateHeight = (element) => {
76
+ if (!element) return 60;
77
+ element.offsetHeight; // Force reflow
78
+ const rect = element.getBoundingClientRect();
79
+ const style = window.getComputedStyle(element);
80
+ const marginTop = parseInt(style.marginTop) || 0;
81
+ const marginBottom = parseInt(style.marginBottom) || 0;
82
+ return Math.max(rect.height, element.offsetHeight) + marginTop + marginBottom;
83
+ };
84
+
85
+ // Fix topbar with highest priority
86
+ const topbarSelectors = '.top-bar, header, .app-header, .navbar, .page-header';
87
+ const topbar = document.querySelector(topbarSelectors);
88
+ let topOffset = 60;
89
+
90
+ if (topbar) {
91
+ topOffset = getAccurateHeight(topbar);
92
+
93
+ topbar.style.position = 'fixed';
94
+ topbar.style.top = '0';
95
+ topbar.style.left = '0';
96
+ topbar.style.width = '100%';
97
+ topbar.style.zIndex = '10000';
98
+ topbar.style.transition = 'none';
99
+ topbar.style.boxSizing = 'border-box';
100
+
101
+ // Push page down to reveal content
102
+ document.body.style.marginTop = topOffset + 'px';
103
+ console.log('✅ Topbar height:', topOffset, 'px');
104
+ }
105
+
106
+ // Fix sidebar below topbar
107
+ const sidebarSelectors = 'aside, .sidebar, nav';
108
+ const sidebar = document.querySelector(sidebarSelectors);
109
+
110
+ if (sidebar) {
111
+ const sidebarWidth = sidebar.offsetWidth || 220;
112
+
113
+ sidebar.style.position = 'fixed';
114
+ sidebar.style.top = topOffset + 'px';
115
+ sidebar.style.left = '0';
116
+ sidebar.style.height = `calc(100vh - ${topOffset}px)`;
117
+ sidebar.style.width = sidebarWidth + 'px';
118
+ sidebar.style.zIndex = '9999';
119
+ sidebar.style.overflow = 'auto';
120
+ sidebar.style.transition = 'none';
121
+ sidebar.style.boxSizing = 'border-box';
122
+
123
+ console.log('✅ Sidebar top:', topOffset, 'px');
124
+
125
+ // Fix content spacing
126
+ const contentSelectors = 'main, .main-content, .app-body, .content, .page-content';
127
+ document.querySelectorAll(contentSelectors).forEach(el => {
128
+ if (el) {
129
+ el.style.marginLeft = sidebarWidth + 'px';
130
+ el.style.position = 'relative';
131
+ el.style.paddingTop = '20px'; // Add breathing room for titles
132
+ }
133
+ });
134
+ }
135
+
136
+ // Hide any duplicate title bars
137
+ document.querySelectorAll('.title-bar:not(.top-bar):not(header)').forEach(el => {
138
+ if (window.getComputedStyle(el).position === 'fixed') {
139
+ el.style.display = 'none';
140
+ }
141
+ });
142
+
143
+ window.scrollTo(0, 0);
144
+ console.log('✅ Layout fix completed');
145
+ })();
146
  """)
147
  except Exception as e:
148
  print(f"[WARN] Layout fix failed: {e}")
149
 
150
+ # Wait for sidebar and stabilize
151
+ page.wait_for_selector("aside, .sidebar, nav", timeout=5000)
152
+ wait_for_layout_stable()
153
+ fix_layout_dynamically()
154
+ page.wait_for_timeout(2000) # Increased wait for rendering
155
 
156
+ # JS logic for capturing workflows
157
  js_logic = """
158
  (function(){
159
  const menus=[...document.querySelectorAll('.menu-item')];
160
  const screens=[...document.querySelectorAll('.screen')];
161
  window.__ordered=[];
162
  const seen=new Set();
 
163
  for(const m of menus){
164
  let id=m.dataset.screen||m.dataset.target;
165
  if(!id){
 
169
  const s=screens.find(x=>x.id===id);
170
  if(s && !seen.has(s)){seen.add(s);window.__ordered.push({menu:m,screen:s});}
171
  }
 
172
  for(const s of screens)
173
  if(!seen.has(s))
174
  window.__ordered.push({menu:null,screen:s});
 
175
  window.__visitedWorkflows=[];
176
  window.__currentIndex=0;
177
  window.__done=false;
 
178
  window.__getSubScreens = function(screen){
179
  const tabs=[...screen.querySelectorAll('.tab, .nav-link, [role="tab"], [data-tab], .sub-tab, .tab-item')];
180
  const list=[];
 
184
  }
185
  return list;
186
  };
187
+ window.__captureNext=function(){
 
188
  if(window.__done) return false;
189
  if(window.__currentIndex>=window.__ordered.length){window.__done=true;return false;}
 
190
  const pair=window.__ordered[window.__currentIndex];
191
  const {menu,screen}=pair;
192
  if(!screen){window.__done=true;return false;}
 
193
  const wfName=screen.id || screen.getAttribute('data-name') || ('screen_'+window.__currentIndex);
194
  if(window.__visitedWorkflows.includes(wfName)){
195
  window.__currentIndex++;
196
  return window.__captureNext();
197
  }
 
198
  window.__visitedWorkflows.push(wfName);
199
  document.querySelectorAll('.screen').forEach(s=>s.classList.remove('active'));
200
  document.querySelectorAll('.menu-item').forEach(m=>m.classList.remove('active'));
 
202
  if(menu) menu.classList.add('active');
203
  screen.scrollIntoView({behavior:'smooth',block:'center'});
204
  window.__currentIndex++;
 
205
  const subs = window.__getSubScreens(screen);
206
  return {screenName:wfName, subScreens:subs};
207
  };
208
+ window.__clickSubScreen = function(name){
 
209
  const tabs=[...document.querySelectorAll('.tab, .nav-link, [role="tab"], [data-tab], .sub-tab, .tab-item')];
210
  const t=tabs.find(x=>x.textContent.trim()===name);
211
  if(t){t.click(); return true;}
 
213
  };
214
  })();
215
  """
216
+ page.evaluate(js_logic)
217
+ page.wait_for_timeout(1000)
218
 
219
  screenshots = []
220
  index = 0
221
 
222
+ # Capture all screens
223
  while True:
224
+ result = page.evaluate("window.__captureNext()")
225
  if not result:
226
  break
227
 
 
230
  screenshot_path = os.path.join(OUTPUT_DIR, f"{screen_name}.png")
231
  print(f"📸 Capturing main screen: {screen_name}")
232
 
233
+ wait_for_layout_stable()
234
+ fix_layout_dynamically()
235
+ page.wait_for_timeout(1000)
236
+ page.screenshot(path=screenshot_path, full_page=True)
237
  screenshots.append(screenshot_path)
238
 
239
+ # Capture sub-tabs
240
  first_active_skipped = False
241
  for sub in sub_screens:
242
+ is_active = page.evaluate(
243
  """(subText) => {
244
  const tabs=[...document.querySelectorAll('.tab, .nav-link, [role="tab"], [data-tab], .sub-tab, .tab-item')];
245
  const t=tabs.find(x=>x.textContent.trim()===subText);
 
254
  continue
255
 
256
  print(f" ↳ Capturing sub-screen: {sub}")
257
+ page.evaluate(f"window.__clickSubScreen('{sub}')")
258
+ wait_for_layout_stable()
259
+ fix_layout_dynamically()
260
+ page.wait_for_timeout(1000)
261
 
262
  sub_name_clean = sub.replace(" ", "_").lower()
263
  sub_path = os.path.join(OUTPUT_DIR, f"{screen_name}_{sub_name_clean}.png")
264
+ page.screenshot(path=sub_path, full_page=True)
265
  screenshots.append(sub_path)
266
 
267
  index += 1
268
 
269
+ browser.close()
270
 
271
+ # Generate PDF
272
  if not screenshots:
273
  raise RuntimeError("No screenshots captured — check if .screen elements exist!")
274
 
 
287
  print(f"✅ PDF generated successfully: {pdf_path}")
288
  return pdf_path
289
 
 
290
  generate_ui_report = capture_workflows