import fs from 'node:fs/promises'; import path from 'node:path'; import { chromium, devices } from 'playwright'; const baseUrl = 'https://127.0.0.1:4400'; const outputDir = path.resolve(process.cwd(), '..', '..', '..', 'output', 'playwright', 'qa-visual-review'); const mockConfig = { authority: { issuer: 'https://authority.local', clientId: 'stella-ops-ui', authorizeEndpoint: 'https://authority.local/connect/authorize', tokenEndpoint: 'https://authority.local/connect/token', logoutEndpoint: 'https://authority.local/connect/logout', redirectUri: 'https://127.0.0.1:4400/auth/callback', postLogoutRedirectUri: 'https://127.0.0.1:4400/', scope: 'openid profile email ui.read', audience: 'https://scanner.local', dpopAlgorithms: ['ES256'], refreshLeewaySeconds: 60, }, apiBaseUrls: { authority: 'https://authority.local', scanner: 'https://scanner.local', policy: 'https://policy.local', concelier: 'https://concelier.local', attestor: 'https://attestor.local', gateway: 'https://gateway.local', }, quickstartMode: true, setup: 'complete', }; const oidcConfig = { issuer: mockConfig.authority.issuer, authorization_endpoint: mockConfig.authority.authorizeEndpoint, token_endpoint: mockConfig.authority.tokenEndpoint, jwks_uri: 'https://authority.local/.well-known/jwks.json', response_types_supported: ['code'], subject_types_supported: ['public'], id_token_signing_alg_values_supported: ['RS256'], }; const shellSession = { subjectId: 'qa-visual-user', tenant: 'tenant-default', scopes: [ 'ui.read', 'admin', 'ui.admin', 'orch:read', 'orch:operate', 'orch:quota', 'findings:read', 'vuln:view', 'vuln:investigate', 'vuln:operate', 'vuln:audit', 'authority:tenants.read', 'advisory:read', 'vex:read', 'exceptions:read', 'exceptions:approve', 'aoc:verify', 'policy:read', 'policy:author', 'policy:review', 'policy:approve', 'policy:simulate', 'policy:audit', 'health:read', 'notify:viewer', 'release:read', 'release:write', 'release:publish', 'sbom:read', 'signer:read', 'analytics.read', 'scheduler:read', 'scheduler:operate', ], }; const routesToCapture = [ '/setup', '/setup/wizard', '/mission-control/board', '/security', '/security/findings', '/releases', '/ops', ]; function sanitizeRoute(route) { return route.replace(/^\//, '').replace(/[/?#=&]+/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '') || 'home'; } async function applyMocks(page) { await page.route('**/*', (route) => { const url = route.request().url(); if (url.includes('/config.json')) { return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(mockConfig), }); } if (url.includes('/platform/envsettings.json')) { return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(mockConfig), }); } if (url.includes('/.well-known/openid-configuration')) { return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify(oidcConfig), }); } if (url.includes('/.well-known/jwks.json')) { return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ keys: [] }), }); } const apiLike = url.includes('authority.local') || url.includes('scanner.local') || url.includes('policy.local') || url.includes('concelier.local') || url.includes('attestor.local') || url.includes('gateway.local') || url.includes('/platform/') || url.includes('/authority/') || url.includes('/scanner/') || url.includes('/policy/') || url.includes('/concelier/') || url.includes('/attestor/') || url.includes('/api/'); if (apiLike) { return route.fulfill({ status: 200, contentType: 'application/json', body: JSON.stringify({ items: [], data: [], total: 0 }), }); } return route.continue(); }); await page.addInitScript((session) => { window.__stellaopsTestSession = session; }, shellSession); } async function captureContext(browser, name, contextOptions) { const context = await browser.newContext({ ignoreHTTPSErrors: true, ...contextOptions }); const page = await context.newPage(); await applyMocks(page); for (const route of routesToCapture) { const fullUrl = `${baseUrl}${route}`; await page.goto(fullUrl, { waitUntil: 'domcontentloaded' }); await page.waitForTimeout(2500); const file = path.join(outputDir, `${name}-${sanitizeRoute(route)}.png`); await page.screenshot({ path: file, fullPage: true }); console.log(`[${name}] captured ${route} -> ${file}`); } await context.close(); } (async () => { await fs.mkdir(outputDir, { recursive: true }); const browser = await chromium.launch({ headless: true }); try { await captureContext(browser, 'desktop', { viewport: { width: 1440, height: 1024 } }); await captureContext(browser, 'mobile', devices['iPhone 13']); } finally { await browser.close(); } console.log(`Screenshots saved in ${outputDir}`); })();