Files
git.stella-ops.org/src/Web/StellaOps.Web/scripts/live-notifications-watchlist-recheck.mjs
2026-03-10 00:25:34 +02:00

178 lines
5.1 KiB
JavaScript

#!/usr/bin/env node
import { mkdir, writeFile } from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { chromium } from 'playwright';
import { authenticateFrontdoor, createAuthenticatedContext } from './live-frontdoor-auth.mjs';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
const webRoot = path.resolve(__dirname, '..');
const outputDir = path.join(webRoot, 'output', 'playwright');
const outputPath = path.join(outputDir, 'live-notifications-watchlist-recheck.json');
const authStatePath = path.join(outputDir, 'live-notifications-watchlist-recheck.state.json');
const authReportPath = path.join(outputDir, 'live-notifications-watchlist-recheck.auth.json');
const scopeQuery = 'tenant=demo-prod&regions=us-east&environments=stage&timeWindow=7d';
async function settle(page) {
await page.waitForLoadState('domcontentloaded', { timeout: 15_000 }).catch(() => {});
await page.waitForTimeout(1_500);
}
async function headingText(page) {
const headings = page.locator('h1, h2, [data-testid="page-title"], .page-title');
const count = await headings.count();
for (let index = 0; index < Math.min(count, 4); index += 1) {
const text = (await headings.nth(index).innerText().catch(() => '')).trim();
if (text) {
return text;
}
}
return '';
}
async function captureSnapshot(page, label) {
const alerts = await page
.locator('[role="alert"], .mat-mdc-snack-bar-container, .toast, .notification, .error-banner')
.evaluateAll((nodes) =>
nodes
.map((node) => (node.textContent || '').trim().replace(/\s+/g, ' '))
.filter(Boolean)
.slice(0, 5),
)
.catch(() => []);
return {
label,
url: page.url(),
title: await page.title(),
heading: await headingText(page),
alerts,
};
}
async function navigate(page, route) {
const separator = route.includes('?') ? '&' : '?';
const url = `https://stella-ops.local${route}${separator}${scopeQuery}`;
await page.goto(url, { waitUntil: 'domcontentloaded', timeout: 30_000 });
await settle(page);
return url;
}
async function clickLinkAndVerify(page, route, linkName, expectedPath) {
await navigate(page, route);
const locator = page.getByRole('link', { name: linkName }).first();
if ((await locator.count()) === 0) {
return {
action: `link:${linkName}`,
ok: false,
reason: 'missing-link',
snapshot: await captureSnapshot(page, `missing-link:${linkName}`),
};
}
await locator.click({ timeout: 10_000 });
await page.waitForURL((url) => url.pathname.includes(expectedPath), { timeout: 15_000 });
await settle(page);
return {
action: `link:${linkName}`,
ok: page.url().includes(expectedPath),
snapshot: await captureSnapshot(page, `after-link:${linkName}`),
};
}
async function main() {
await mkdir(outputDir, { recursive: true });
const authReport = await authenticateFrontdoor({
statePath: authStatePath,
reportPath: authReportPath,
headless: true,
});
const browser = await chromium.launch({
headless: true,
args: ['--disable-dev-shm-usage'],
});
const context = await createAuthenticatedContext(browser, authReport, { statePath: authStatePath });
const page = await context.newPage();
const runtime = {
consoleErrors: [],
pageErrors: [],
responseErrors: [],
requestFailures: [],
};
page.on('console', (message) => {
if (message.type() === 'error') {
runtime.consoleErrors.push({ page: page.url(), text: message.text() });
}
});
page.on('pageerror', (error) => {
runtime.pageErrors.push({ page: page.url(), message: error.message });
});
page.on('requestfailed', (request) => {
const url = request.url();
if (/\.(?:css|js|map|png|jpg|jpeg|svg|woff2?)(?:$|\?)/i.test(url)) {
return;
}
runtime.requestFailures.push({
page: page.url(),
method: request.method(),
url,
error: request.failure()?.errorText ?? 'unknown',
});
});
page.on('response', (response) => {
const url = response.url();
if (/\.(?:css|js|map|png|jpg|jpeg|svg|woff2?)(?:$|\?)/i.test(url)) {
return;
}
if (response.status() >= 400) {
runtime.responseErrors.push({
page: page.url(),
method: response.request().method(),
status: response.status(),
url,
});
}
});
const results = [];
results.push(await clickLinkAndVerify(
page,
'/ops/operations/notifications',
'Open watchlist tuning',
'/setup/trust-signing/watchlist/tuning',
));
results.push(await clickLinkAndVerify(
page,
'/ops/operations/notifications',
'Review watchlist alerts',
'/setup/trust-signing/watchlist/alerts',
));
const summary = {
generatedAtUtc: new Date().toISOString(),
results,
runtime,
};
await writeFile(outputPath, `${JSON.stringify(summary, null, 2)}\n`, 'utf8');
process.stdout.write(`${JSON.stringify(summary, null, 2)}\n`);
await browser.close();
}
main().catch((error) => {
process.stderr.write(`[live-notifications-watchlist-recheck] ${error instanceof Error ? error.message : String(error)}\n`);
process.exitCode = 1;
});