Assessor_2.0/tests/client-info-design-browser-...

91 lines
5.6 KiB
JavaScript

// Opt-in, synthetic-only browser acceptance check. No app/backend/database start.
// PLAYWRIGHT_MODULE points to an already installed Playwright package.
import { build } from 'esbuild';
import { createServer } from 'node:http';
import { readFile } from 'node:fs/promises';
import { resolve, extname } from 'node:path';
import { pathToFileURL } from 'node:url';
import assert from 'node:assert/strict';
const { chromium } = await import(pathToFileURL(process.env.PLAYWRIGHT_MODULE).href);
const output = resolve('outputs/client-info-design-check');
const fixture = resolve('tests/fixtures/client-info-design/preview.tsx');
await build({ entryPoints: [fixture], bundle: true, format: 'iife', jsx: 'automatic', outdir: output,
entryNames: 'preview', loader: { '.woff2': 'file', '.woff': 'file', '.png': 'file' },
plugins: [{ name: 'synthetic-app-context', setup(builder) {
builder.onResolve({ filter: /(?:^|\/)app\/AppProvider$/ }, () => ({ path: fixture }));
}}] });
const server = createServer(async (request, response) => {
const url = new URL(request.url, 'http://127.0.0.1');
if (url.pathname === '/') {
response.setHeader('Content-Type', 'text/html');
response.end('<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Synthetic Client Info verification</title><link rel="stylesheet" href="/preview.css"></head><body><div id="root"></div><script src="/preview.js"></script></body></html>');
return;
}
if (!/^\/[\w.-]+$/.test(url.pathname)) { response.writeHead(404).end(); return; }
try {
const extension = extname(url.pathname);
response.setHeader('Content-Type', extension === '.js' ? 'text/javascript' : extension === '.css' ? 'text/css' : 'font/woff2');
response.end(await readFile(resolve(output, url.pathname.slice(1))));
} catch { response.writeHead(404).end(); }
});
await new Promise(resolve => server.listen(0, '127.0.0.1', resolve));
const browser = await chromium.launch({ headless: true, channel: 'msedge' });
try {
const page = await browser.newPage();
const errors = [];
page.on('pageerror', error => errors.push(error.message));
for (const theme of ['light', 'dark']) {
await page.setViewportSize({ width: 1600, height: 1100 });
await page.goto(`http://127.0.0.1:${server.address().port}/?theme=${theme}`);
await page.locator('.client-domains li').first().waitFor();
await page.evaluate(() => document.fonts.ready);
for (const width of [1600, 1100, 860, 390]) {
await page.setViewportSize({ width, height: 1100 });
await page.waitForTimeout(150);
const geometry = await page.evaluate(() => {
const root = document.querySelector('.dashboard-page');
const svg = document.querySelector('.client-posture-connectors');
const ring = document.querySelector('.client-overall-ring').getBoundingClientRect();
const number = document.querySelector('.client-overall-ring-number strong').getBoundingClientRect();
const nodes = [...document.querySelectorAll('.client-domains .client-domain-score')];
const frame = svg.getBoundingClientRect();
return { overflow: document.documentElement.scrollWidth > innerWidth,
background: getComputedStyle(root).backgroundColor,
radius: getComputedStyle(document.querySelector('.client-overview-fact')).borderRadius,
numberOffset: Math.abs(number.x + number.width / 2 - ring.x - ring.width / 2),
buttonHeight: document.querySelector('.client-source-overview header button').getBoundingClientRect().height,
connectors: getComputedStyle(svg).display === 'none' ? [] : [...svg.querySelectorAll('path')].map((path, index) => {
const endpoint = path.getPointAtLength(path.getTotalLength());
const node = nodes[index].getBoundingClientRect();
return Math.hypot(endpoint.x + frame.x - node.x, endpoint.y + frame.y - node.y - node.height / 2);
}) };
});
assert.equal(geometry.overflow, false, `${theme} ${width}: horizontal overflow`);
assert.equal(geometry.radius, '0px');
assert(geometry.numberOffset < 1, 'Overall number must remain centered');
assert(geometry.buttonHeight < 50, 'Methodology action must not become a source tile');
if (width > 800) assert.equal(geometry.connectors.length, 8);
assert(geometry.connectors.every(error => error < 1), 'Connector endpoints must follow domain nodes');
console.log(JSON.stringify({ theme, width, ...geometry }));
if (width === 1600 || width === 390) await page.screenshot({ path: resolve(output, `${theme}-${width}.png`), fullPage: true });
}
await page.setViewportSize({ width: 1600, height: 1100 });
const environment = page.locator('.client-environment-detail');
assert.equal(await environment.getAttribute('open'), null);
await environment.locator('summary').focus();
await page.keyboard.press('Enter');
assert.notEqual(await environment.getAttribute('open'), null);
await environment.locator('summary').press('Space');
assert.equal(await environment.getAttribute('open'), null);
const group = page.locator('.risk-register-detail-group').first();
await group.locator('summary').focus();
await page.keyboard.press('Enter');
assert.notEqual(await group.getAttribute('open'), null);
assert(await group.locator('dl').isVisible());
await page.locator('.client-risk-register-list button').last().click();
assert.equal(await group.getAttribute('open'), null);
}
assert.deepEqual(errors, []);
} finally { await browser.close(); await new Promise(resolve => server.close(resolve)); }