/** * Email capture — the tabbed Email Management page, as the demo customer. * * Captures: * - whp-email.png the Email page on its default "Email Accounts" * tab: the top button strip (Webmail / Admin * Panel / Setup Instructions) + the tab bar * (Email Accounts · Forwarders · Email Domains * (DNS)) + the Email Accounts card. * - whp-email-autodiscovery.png the "Autodiscovery Records (DNS)" card on the * "Email Domains (DNS)" tab: the per-domain * autodiscovery DNS records table + copyable zone. * * Viewport-only (1440x900, deviceScaleFactor 2), redacted for our multi-server * fleet: server/mail hostnames + IPs become placeholders, while the brand demo * domain (whp-demo.anhh.co) is kept visible on purpose. * * Read-only: switches tabs / selects a domain for the shot, never saves. */ import { chromium, type Page } from 'playwright'; import { mkdir } from 'node:fs/promises'; import { resolve, dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; const __dirname = dirname(fileURLToPath(import.meta.url)); const OUT_DIR = resolve(__dirname, '../../src/assets/screenshots/whp'); function need(name: string): string { const v = process.env[name]; if (!v) throw new Error(`missing env: ${name}`); return v; } const BASE = need('WHP_BASE'); const USER = need('WHP_USER'); const PASS = need('WHP_PASS'); const HIDE_CSS = `.navbar-text, .brand-full { visibility: hidden !important; }`; async function login(page: Page) { await page.goto(`${BASE}/login.php`, { waitUntil: 'domcontentloaded' }); await page.fill('input[name="user"]', USER); await page.fill('input[name="password"]', PASS); await page.click('button[type="submit"]'); await page.waitForLoadState('networkidle'); } /** * Neutralise fleet-identifying text before the screenshot. The brand demo * domain (anhh.co) is intentionally preserved; mail/server hosts and IPs are * swapped for placeholders. Inline only — no named helpers inside evaluate * (esbuild's __name instrumentation isn't defined in the browser context). */ async function redact(page: Page) { await page.addStyleTag({ content: HIDE_CSS }); await page.evaluate(() => { const swaps: [RegExp, string][] = [ [/mail\d+\.cloud-hosting\.io/gi, '.cloud-hosting.io'], [/whp\d+(-[a-z0-9]+)?\.cloud-hosting\.io/gi, '.cloud-hosting.io'], [/WHP\d+(-[A-Z0-9]+)?\b/g, ''], [/whp\d+(-[a-z0-9]+)?\b/gi, ''], [/\b\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}\b/g, ''], ]; const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT); const nodes: Text[] = []; let n: Node | null = walker.nextNode(); while (n) { nodes.push(n as Text); n = walker.nextNode(); } for (const node of nodes) { let v = node.nodeValue ?? ''; for (const [re, rep] of swaps) v = v.replace(re, rep); if (v !== node.nodeValue) node.nodeValue = v; } }); } async function main() { await mkdir(OUT_DIR, { recursive: true }); const browser = await chromium.launch({ headless: true }); const ctx = await browser.newContext({ ignoreHTTPSErrors: true, viewport: { width: 1440, height: 900 }, deviceScaleFactor: 2, }); const page = await ctx.newPage(); try { await login(page); await page.goto(`${BASE}/index.php?page=email-management`, { waitUntil: 'networkidle' }); await page.waitForSelector('#email-mgmt-nav', { state: 'visible' }); // --- Shot 1: the default Email Accounts tab (orientation) --- await page.waitForTimeout(300); await redact(page); await page.evaluate(() => window.scrollTo(0, 0)); const mainPath = resolve(OUT_DIR, 'whp-email.png'); await page.screenshot({ path: mainPath }); // viewport-only, no chrome console.log(`captured whp-email -> ${mainPath}`); // --- Shot 2: the Autodiscovery Records (DNS) card on the DNS tab --- await page.locator('#email-mgmt-nav button[data-bs-target="#email-mgmt-dns"]').click(); await page.waitForSelector('#custMailDnsDomain', { state: 'visible' }); // Ensure a domain is selected, then (re)render the zone block. await page.evaluate(() => { const sel = document.getElementById('custMailDnsDomain') as HTMLSelectElement | null; if (sel && sel.selectedIndex < 0 && sel.options.length) sel.selectedIndex = 0; const fn = (window as unknown as { renderCustMailDns?: () => void }).renderCustMailDns; if (typeof fn === 'function') fn(); }); await page.waitForTimeout(500); await redact(page); // re-run: tab content rendered after the first pass // The autodiscovery section is the .card wrapping the domain