Files
Josh c602b8f8f3 docs: verify against real WHP + capture real screenshots
Discovery against the demo account on whp01 surfaced several inaccuracies:

- Cache is Valkey (Redis wire-compatible), not Redis or Memcached.
  No Memcached is offered as a separate service.
- Site Monitoring is the sidebar label (not 'AI Monitor').
- 'Add a domain' has no Primary/Add-on distinction.
- Sites form: 'Container Type' (not 'Site type'), Number of Containers
  (1-10 for horizontal scaling), CPU per Container (default 0.25),
  Memory per Container (default 256MB), SSL inline on the same form.
- Backups: default retention 5 days / 10 backups; on-demand + scheduled;
  S3 backup targets are visible and configurable.
- Email: per-domain settings live behind 'Setup Instructions' on the
  Email page; mail server hostname is on the Dashboard (per-server,
  e.g. mail01.cloud-hosting.io), not per-domain.

Also reworked the screenshot pipeline:
- New shots.config.ts targets the real index.php?page=... URLs
- Added redactSensitive() step that runs before each screenshot to swap
  server names, IPs, mail hostnames, and demo-user-isms with neutral
  placeholders. This keeps docs portable across the fleet.
- Hides .brand-full and .navbar-text (top-bar server identifier and
  Welcome greeting).
- Captured 9 real WHP screenshots; removed stale placeholders.
2026-05-17 17:00:13 -07:00

110 lines
3.4 KiB
TypeScript

/**
* Discovery script — logs in, captures the dashboard, and probes the sidebar
* nav to learn the actual URLs of the sections referenced in our docs.
*
* Not committed for ongoing use; the canonical capture is run.ts.
*/
import { chromium, type Page } from 'playwright';
import { mkdir, writeFile } 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');
async function login(page: Page): Promise<void> {
console.log(`navigating ${BASE}/login.php`);
await page.goto(`${BASE}/login.php`, { waitUntil: 'domcontentloaded' });
console.log(`landed at ${page.url()}`);
await page.fill('input[name="user"]', USER);
await page.fill('input[name="password"]', PASS);
await page.click('button[type="submit"]');
await page.waitForLoadState('networkidle');
console.log(`logged in, now at ${page.url()}`);
}
async function snapshotPage(page: Page, id: string): Promise<void> {
await mkdir(OUT_DIR, { recursive: true });
const path = resolve(OUT_DIR, `${id}.png`);
await page.screenshot({ path, fullPage: false });
console.log(`captured ${id} -> ${path}`);
}
async function listNavLinks(page: Page): Promise<{ text: string; href: string | null }[]> {
// Use locator API to collect all anchors with hrefs (no string-eval).
const anchors = page.locator('a[href]');
const count = await anchors.count();
const out: { text: string; href: string | null }[] = [];
for (let i = 0; i < count && out.length < 120; i++) {
const a = anchors.nth(i);
const href = await a.getAttribute('href');
if (!href || href.startsWith('#')) continue;
const text = ((await a.textContent()) ?? '').trim().slice(0, 60);
if (!text) continue;
out.push({ text, href });
}
return out;
}
async function probe(page: Page, label: string, hrefHint: string): Promise<string | null> {
const link = page.locator(`a[href*="${hrefHint}"]`).first();
if ((await link.count()) === 0) {
console.log(` NO MATCH for "${label}" (hint=${hrefHint})`);
return null;
}
const href = await link.getAttribute('href');
console.log(` ${label}: ${href}`);
return href;
}
async function main(): Promise<void> {
const browser = await chromium.launch({ headless: true });
const ctx = await browser.newContext({
ignoreHTTPSErrors: true,
viewport: { width: 1440, height: 900 },
});
const page = await ctx.newPage();
try {
await login(page);
await snapshotPage(page, '_discovery-dashboard');
const navLinks = await listNavLinks(page);
await writeFile(
resolve(__dirname, '_discovered-nav.json'),
JSON.stringify(navLinks, null, 2),
);
console.log(`wrote ${navLinks.length} nav links to _discovered-nav.json`);
for (const [label, hint] of [
['Domains', 'domain'],
['Sites', 'site'],
['Email', 'email'],
['Backups', 'backup'],
['Monitor', 'monitor'],
['Resources', 'resource'],
['Dashboard', 'dashboard'],
] as const) {
await probe(page, label, hint);
}
} finally {
await browser.close();
}
}
main().catch((err) => {
console.error(err);
process.exit(1);
});