|
| 1 | +/** |
| 2 | + * Capture screenshots of PredomicsApp pages for documentation. |
| 3 | + * Usage: node docs/capture_screenshots.mjs |
| 4 | + * |
| 5 | + * Prerequisites: npm install playwright |
| 6 | + * The app must be running at BASE URL (default http://localhost:8001). |
| 7 | + */ |
| 8 | +import { chromium } from 'playwright'; |
| 9 | + |
| 10 | +const BASE = 'http://localhost:8001'; |
| 11 | +const OUT = 'docs/screenshots'; |
| 12 | +const CREDS = { email: 'edi.prifti@gmail.com', password: 'editest' }; |
| 13 | +const PROJECT_ID = 'd0810056bcc1'; |
| 14 | +const JOB_ID = '4180f1e459ac'; |
| 15 | + |
| 16 | +async function dismissOverlays(page) { |
| 17 | + await page.evaluate(() => { |
| 18 | + document.querySelectorAll('.tour-overlay, .tour-backdrop, [class*="tour"]').forEach(el => el.remove()); |
| 19 | + localStorage.setItem('tour_completed', 'true'); |
| 20 | + localStorage.setItem('tourDone', 'true'); |
| 21 | + localStorage.setItem('predomics_tour_done', 'true'); |
| 22 | + }); |
| 23 | + const skipBtn = await page.$('button:has-text("Skip"), button:has-text("Close"), button:has-text("Got it"), .tour-skip'); |
| 24 | + if (skipBtn) await skipBtn.click({ force: true }).catch(() => {}); |
| 25 | + await page.waitForTimeout(300); |
| 26 | +} |
| 27 | + |
| 28 | +async function main() { |
| 29 | + const browser = await chromium.launch(); |
| 30 | + const ctx = await browser.newContext({ |
| 31 | + viewport: { width: 1440, height: 900 }, |
| 32 | + colorScheme: 'dark', |
| 33 | + }); |
| 34 | + const page = await ctx.newPage(); |
| 35 | + |
| 36 | + // ── 1. Landing / Home page (unauthenticated) ── |
| 37 | + await page.goto(BASE); |
| 38 | + await page.waitForTimeout(2000); |
| 39 | + await dismissOverlays(page); |
| 40 | + await page.screenshot({ path: `${OUT}/01_landing.png` }); |
| 41 | + console.log('1/11 Landing page ✓'); |
| 42 | + |
| 43 | + // ── 2. Login page ── |
| 44 | + await page.goto(`${BASE}/login`); |
| 45 | + await page.waitForTimeout(1000); |
| 46 | + await page.screenshot({ path: `${OUT}/02_login.png` }); |
| 47 | + console.log('2/11 Login page ✓'); |
| 48 | + |
| 49 | + // ── Authenticate via API and inject token into localStorage ── |
| 50 | + await page.evaluate(async (creds) => { |
| 51 | + const resp = await fetch('/api/auth/login', { |
| 52 | + method: 'POST', |
| 53 | + headers: { 'Content-Type': 'application/json' }, |
| 54 | + body: JSON.stringify(creds), |
| 55 | + }); |
| 56 | + if (!resp.ok) throw new Error(`Login failed: ${resp.status}`); |
| 57 | + const data = await resp.json(); |
| 58 | + localStorage.setItem('token', data.access_token); |
| 59 | + }, CREDS); |
| 60 | + console.log(' Authenticated (token injected)'); |
| 61 | + |
| 62 | + // ── 3. Projects page ── |
| 63 | + await page.goto(`${BASE}/projects`); |
| 64 | + await page.waitForTimeout(3000); |
| 65 | + await dismissOverlays(page); |
| 66 | + await page.screenshot({ path: `${OUT}/03_projects.png` }); |
| 67 | + console.log('3/11 Projects list ✓'); |
| 68 | + |
| 69 | + // ── 4. Project Data & Run tab ── |
| 70 | + await page.goto(`${BASE}/project/${PROJECT_ID}/data`); |
| 71 | + await page.waitForTimeout(4000); |
| 72 | + await dismissOverlays(page); |
| 73 | + await page.screenshot({ path: `${OUT}/04_project_data.png` }); |
| 74 | + console.log('4/11 Project data tab ✓'); |
| 75 | + |
| 76 | + // ── 5. Parameters tab ── |
| 77 | + await page.goto(`${BASE}/project/${PROJECT_ID}/parameters`); |
| 78 | + await page.waitForTimeout(3000); |
| 79 | + await dismissOverlays(page); |
| 80 | + await page.screenshot({ path: `${OUT}/05_parameters.png` }); |
| 81 | + console.log('5/11 Parameters tab ✓'); |
| 82 | + |
| 83 | + // ── 6. Results — Summary ── |
| 84 | + await page.goto(`${BASE}/project/${PROJECT_ID}/results/${JOB_ID}`); |
| 85 | + await page.waitForTimeout(6000); |
| 86 | + await dismissOverlays(page); |
| 87 | + // Scroll past the jobs table to show the results sub-tab content |
| 88 | + await page.evaluate(() => { |
| 89 | + const subTabs = document.querySelector('button.active')?.closest('.sub-tabs, [class*="sub"]'); |
| 90 | + if (subTabs) subTabs.scrollIntoView({ block: 'start' }); |
| 91 | + else window.scrollBy(0, 600); |
| 92 | + }); |
| 93 | + await page.waitForTimeout(1000); |
| 94 | + await page.screenshot({ path: `${OUT}/06_results_summary.png` }); |
| 95 | + console.log('6/11 Results summary ✓'); |
| 96 | + |
| 97 | + // Helper: click a sub-tab and scroll to content area |
| 98 | + async function clickSubTab(label) { |
| 99 | + await page.evaluate((lbl) => { |
| 100 | + const btns = [...document.querySelectorAll('button')]; |
| 101 | + const btn = btns.find(b => b.textContent.trim() === lbl); |
| 102 | + if (btn) { |
| 103 | + btn.click(); |
| 104 | + btn.scrollIntoView({ block: 'start' }); |
| 105 | + } |
| 106 | + }, label); |
| 107 | + } |
| 108 | + |
| 109 | + // ── 7. Best Model sub-tab ── |
| 110 | + try { |
| 111 | + await clickSubTab('Best Model'); |
| 112 | + await page.waitForTimeout(4000); |
| 113 | + await page.evaluate(() => window.scrollBy(0, -60)); |
| 114 | + await page.screenshot({ path: `${OUT}/07_best_model.png` }); |
| 115 | + console.log('7/11 Best Model ✓'); |
| 116 | + } catch { console.log('7/11 Best Model — SKIPPED (no button)'); } |
| 117 | + |
| 118 | + // ── 8. Population sub-tab ── |
| 119 | + try { |
| 120 | + await clickSubTab('Population'); |
| 121 | + await page.waitForTimeout(4000); |
| 122 | + await page.evaluate(() => window.scrollBy(0, -60)); |
| 123 | + await page.screenshot({ path: `${OUT}/08_population.png` }); |
| 124 | + console.log('8/11 Population ✓'); |
| 125 | + } catch { console.log('8/11 Population — SKIPPED'); } |
| 126 | + |
| 127 | + // ── 9. Co-presence sub-tab ── |
| 128 | + try { |
| 129 | + await clickSubTab('Co-presence'); |
| 130 | + await page.waitForTimeout(6000); |
| 131 | + await page.evaluate(() => window.scrollBy(0, -60)); |
| 132 | + await page.screenshot({ path: `${OUT}/09_copresence.png` }); |
| 133 | + console.log('9/11 Co-presence ✓'); |
| 134 | + |
| 135 | + // Scroll to see heatmap + network |
| 136 | + await page.evaluate(() => window.scrollBy(0, 900)); |
| 137 | + await page.waitForTimeout(3000); |
| 138 | + await page.screenshot({ path: `${OUT}/10_copresence_network.png` }); |
| 139 | + console.log('10/11 Co-presence network ✓'); |
| 140 | + } catch (e) { console.log('9-10/11 Co-presence — SKIPPED:', e.message); } |
| 141 | + |
| 142 | + // ── 11. Comparative sub-tab ── |
| 143 | + try { |
| 144 | + await clickSubTab('Comparative'); |
| 145 | + await page.waitForTimeout(4000); |
| 146 | + await page.evaluate(() => window.scrollBy(0, -60)); |
| 147 | + await page.screenshot({ path: `${OUT}/11_comparative.png` }); |
| 148 | + console.log('11/11 Comparative ✓'); |
| 149 | + } catch { console.log('11/11 Comparative — SKIPPED'); } |
| 150 | + |
| 151 | + await browser.close(); |
| 152 | + console.log(`\nDone! Screenshots saved to ${OUT}/`); |
| 153 | +} |
| 154 | + |
| 155 | +main().catch(e => { console.error(e); process.exit(1); }); |
0 commit comments