Files
kb-anhonesthost/tools/screenshots/capture-quarantine.ts
T
Claude 2044d9e0bc Add screenshots to the quarantine self-service article
Four screenshots of the actual PMG quarantine portal (login with
Request Quarantine Link, the request dialog, the held-mail list, and
the message-selected action toolbar), captured via a new
capture-quarantine.ts following the existing capture-*.ts pattern
(1440x900, deviceScaleFactor 2, viewport-only).

Redacted: real server hostname, the test mailbox address, and the
synthetic test-message content are all swapped for believable generic
customer-facing examples before each shot.

Restructured the article's two near-duplicate step lists ("Check your
quarantine" / "Get in without waiting") into one flow now that the
screenshots show it's genuinely a single screen, not two paths.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VXgBxaVABantBF8twz4LpU
2026-09-09 11:41:33 -07:00

119 lines
5.6 KiB
TypeScript

/**
* PMG quarantine capture — the customer self-service spam quarantine portal.
*
* Captures:
* - email-quarantine-login.png Login screen showing the "Request Quarantine
* Link" button next to Login.
* - email-quarantine-request.png The "Request Quarantine Link" dialog with an
* email address entered.
* - email-quarantine-list.png The authenticated quarantine grid showing held
* mail, redacted to generic example content.
* - email-quarantine-actions.png A selected message with the Whitelist /
* Blacklist / Deliver / Delete toolbar visible.
*
* Viewport-only (1440x900, deviceScaleFactor 2), redacted: the real server
* hostname, the real test mailbox address, and the real (internal-test-looking)
* sender/subject/body text are all swapped for generic customer-facing examples.
*
* Read-only: never actually delivers/whitelists/blacklists/deletes anything.
*/
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/email');
const BASE = process.env.PMG_BASE || 'https://pmg01.cloud-hosting.io:8006';
const TICKET_URL = process.env.PMG_TICKET_URL; // full quarantine?ticket=... URL
/** Swap real fleet/test identifiers for believable generic customer-facing text. */
async function redact(page: Page) {
await page.evaluate(() => {
const swaps: [RegExp, string][] = [
[/pmg01\.cloud-hosting\.io/gi, 'mail.yourdomain.com'],
[/claude@darksideofperfection\.com/gi, 'you@yourdomain.com'],
[/pmg-pilot-tester@anthropic\.com/gi, 'prizes@totally-legit-sweepstakes.example'],
[/SPAM: PMG pilot - spam detection test \(GTUBE\) \(retry\)/g, "SPAM: You've WON a prize!!!"],
[/SPAM: PMG pilot - clean delivery test \(retry\)/g, 'SPAM: Limited time offer - act now'],
[/This is a clean end-to-end delivery test, retried after greylist window\./g,
'Congratulations! You have been selected to receive an exclusive reward. Click below to claim yours before this offer expires.'],
[/GTUBE retry test\./g, "You've been selected for an exclusive reward!"],
[/XJS\*C4JDBQADN1\.NSBN3\*2IDNEN\*GTUBE-STANDARD-ANTI-UBE-TEST-EMAIL\*C\.34X/g,
'Click here to claim your prize before this limited-time offer expires: http://totally-legit-sweepstakes.example/claim'],
[/\b1010\b/g, '8.4'],
];
const docs: Document[] = [document];
for (const f of Array.from(document.querySelectorAll('iframe'))) {
try {
const doc = (f as HTMLIFrameElement).contentDocument;
if (doc) docs.push(doc);
} catch { /* cross-origin, skip */ }
}
for (const root of docs) {
const walker = root.createTreeWalker(root.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() {
if (!TICKET_URL) throw new Error('missing env: PMG_TICKET_URL');
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 {
// --- Shot 1: login screen, showing the Request Quarantine Link button ---
await page.goto(`${BASE}/quarantine`, { waitUntil: 'networkidle' });
await page.waitForSelector('text=Request Quarantine Link', { state: 'visible' });
await page.waitForTimeout(300);
await redact(page);
let p = resolve(OUT_DIR, 'email-quarantine-login.png');
await page.locator('.x-window').first().screenshot({ path: p });
console.log(`captured email-quarantine-login -> ${p}`);
// --- Shot 2: the Request Quarantine Link dialog, filled in ---
await page.getByRole('button', { name: 'Request Quarantine Link' }).click();
await page.getByRole('textbox', { name: 'Your E-Mail:' }).fill('you@yourdomain.com');
await page.waitForTimeout(200);
p = resolve(OUT_DIR, 'email-quarantine-request.png');
await page.locator('.x-window').last().screenshot({ path: p });
console.log(`captured email-quarantine-request -> ${p}`);
// --- Shot 3 & 4: authenticated quarantine view, via the real ticket link ---
await page.goto(TICKET_URL, { waitUntil: 'networkidle' });
await page.waitForSelector('grid[aria-label="Spam Quarantine"], .x-grid', { state: 'visible' }).catch(() => {});
await page.waitForTimeout(500);
await redact(page);
p = resolve(OUT_DIR, 'email-quarantine-list.png');
await page.screenshot({ path: p });
console.log(`captured email-quarantine-list -> ${p}`);
// Select the first row to reveal the action toolbar + message preview.
const firstRow = page.locator('.x-grid-row').first();
await firstRow.click();
await page.waitForTimeout(500);
await redact(page);
p = resolve(OUT_DIR, 'email-quarantine-actions.png');
await page.screenshot({ path: p });
console.log(`captured email-quarantine-actions -> ${p}`);
} finally {
await browser.close();
}
}
main().catch((err) => { console.error(err); process.exit(1); });