91 lines
5.6 KiB
JavaScript
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)); }
|