113 lines
7.4 KiB
JavaScript
113 lines
7.4 KiB
JavaScript
// Synthetic browser acceptance. Run with PLAYWRIGHT_MODULE pointing to Playwright's index.mjs.
|
|
import assert from 'node:assert/strict';
|
|
import { readFile } from 'node:fs/promises';
|
|
import { createServer } from 'node:http';
|
|
import { pathToFileURL } from 'node:url';
|
|
import { build } from 'esbuild';
|
|
|
|
const { chromium } = await import(pathToFileURL(process.env.PLAYWRIGHT_MODULE).href);
|
|
assert.ok(!(await readFile('src/app/App.tsx', 'utf8')).includes('context-layer-btn'));
|
|
const main = await readFile('src/main.tsx', 'utf8');
|
|
const styles = [...main.matchAll(/^import '([^']+\.css)';/gm)].map(([, path]) =>
|
|
`import ${JSON.stringify(path.startsWith('.') ? './src/' + path.slice(2) : path)};`).join('\n');
|
|
const bundle = await build({
|
|
stdin: { contents: `${styles}
|
|
import React, { useState } from 'react';
|
|
import { createRoot } from 'react-dom/client';
|
|
import { EnginesControl } from './src/components/EnginesControl';
|
|
import { FindingContext, FindingContextBadges } from './src/features/finding-reader/FindingContext';
|
|
import { DynamicFilterControls } from './src/features/search-filter/DynamicFilterControls';
|
|
import { CONTEXT_CLUSTER_FACET_ID } from './src/features/context-layer/context-cluster-facet';
|
|
const record = { id:'synthetic', findingOccurrenceId:'synthetic-occurrence', fields:[] };
|
|
function Fixture() {
|
|
const [filters, setFilters] = useState({});
|
|
window.fixtureApp = { setFacetFilter: (id, filter) => setFilters(filter ? {[id]:filter} : {}) };
|
|
return <><div className="reader-toolbar"><div className="toolbar"><EnginesControl/></div></div><DynamicFilterControls facets={[{id:CONTEXT_CLUSTER_FACET_ID, label:'Context Cluster', kind:'category',
|
|
derivedKind:'context-cluster',allCount:1,values:[{value:'synthetic-cluster',label:'Synthetic cluster',count:1}]}]} filters={filters}
|
|
statusFilter="all" onStatusFilter={()=>{}} onFilter={window.fixtureApp.setFacetFilter}/>
|
|
<div className="reader-heading-actions"><FindingContextBadges record={record} isPolicySetting={false}/></div>
|
|
<div className="reader-summary"><FindingContext record={record} isPolicySetting={false}/></div></>;
|
|
}
|
|
createRoot(document.getElementById('root')).render(<Fixture/>);`, resolveDir: process.cwd(), loader: 'tsx' },
|
|
plugins: [{ name: 'synthetic-context', setup(build) {
|
|
build.onResolve({ filter: /(?:AppProvider|use-assessment-context)$/ }, args => ({ path: args.path, namespace: 'fixture' }));
|
|
build.onLoad({ filter: /.*/, namespace: 'fixture' }, args => ({ contents: args.path.endsWith('AppProvider')
|
|
? 'export const useOptionalApp = () => window.fixtureApp;'
|
|
: `export const useAssessmentContext = () => ({status:'ready',context:{catalog:{version:'synthetic'},engine:{version:'synthetic'},
|
|
settings:[],findings:[{occurrenceId:'synthetic-occurrence',clusterIds:['synthetic-cluster'],conditionIds:[],declaredReferenceIds:[]}],
|
|
clusters:[{clusterId:'synthetic-cluster',label:{en:'Synthetic cluster'},description:{en:'Synthetic example context.'},
|
|
state:'present',satisfied:0,unknown:0,notSatisfied:0,minimumSatisfied:1,conditions:[],sources:[]}],
|
|
coverage:{uncataloguedWithoutRecognition:0,scannerFindings:1},recognitions:[],techniqueAssignments:[],
|
|
semantics:[],scannerRules:[],declaredReferences:[],cisControlAreas:[]}});`, loader: 'js' }));
|
|
}}], jsx: 'automatic', bundle: true, write: false, outdir: 'synthetic', format: 'iife',
|
|
loader: { '.woff2': 'dataurl', '.woff': 'dataurl' }
|
|
});
|
|
const server = createServer((request, response) => {
|
|
const file = bundle.outputFiles.find(file => request.url === '/' + file.path.split(/[\\/]/).at(-1));
|
|
response.setHeader('Content-Type', file ? (request.url.endsWith('.css') ? 'text/css' : 'text/javascript') : 'text/html');
|
|
response.end(file?.contents ?? '<!doctype html><html><head><link rel="stylesheet" href="/stdin.css"></head><body><div id="root"></div><script src="/stdin.js"></script></body></html>');
|
|
});
|
|
await new Promise(resolve => server.listen(0, '127.0.0.1', resolve));
|
|
let browser;
|
|
try {
|
|
browser = await chromium.launch({ headless: true, channel: 'msedge' });
|
|
const page = await browser.newPage({ viewport: { width: 1000, height: 600 } });
|
|
page.on('pageerror', error => console.error(error.message));
|
|
await page.goto(`http://127.0.0.1:${server.address().port}/`);
|
|
const summary = page.locator('details.finding-context > summary');
|
|
const content = page.locator('.finding-context__content');
|
|
for (const theme of ['dark', 'light']) {
|
|
await page.evaluate(theme => { document.documentElement.dataset.theme = theme; }, theme);
|
|
await page.getByLabel('Context Cluster', {exact:true}).selectOption('');
|
|
assert.equal(await page.getByLabel('Context Cluster', {exact:true}).locator('option:checked').textContent(), 'Context Cluster: All (1)');
|
|
await page.screenshot({path:`.context-count-${theme}.png`});
|
|
await page.locator('.context-cluster-badge').click();
|
|
assert.equal(await page.getByLabel('Context Cluster', {exact:true}).inputValue(), 'synthetic-cluster');
|
|
assert.equal(await content.isVisible(), false);
|
|
await summary.click();
|
|
assert.equal(await content.isVisible(), true);
|
|
await summary.click();
|
|
assert.equal(await content.isVisible(), false);
|
|
await summary.focus();
|
|
await page.keyboard.press('Enter');
|
|
assert.equal(await content.isVisible(), true);
|
|
await page.keyboard.press('Space');
|
|
assert.equal(await content.isVisible(), false);
|
|
await page.getByLabel('Context Cluster', {exact:true}).selectOption('');
|
|
assert.equal(await summary.isVisible(), true);
|
|
const engines = page.getByRole('button', { name: 'Engines', exact: true });
|
|
const appearance = () => engines.evaluate(el => ({background:getComputedStyle(el).backgroundColor, color:getComputedStyle(el).color}));
|
|
await page.mouse.move(900, 550);
|
|
const marked = await appearance();
|
|
const expected = await engines.evaluate(el => getComputedStyle(el).getPropertyValue('--engines-button-bg').trim());
|
|
assert.ok(expected);
|
|
assert.notEqual(marked.background, 'rgba(0, 0, 0, 0)');
|
|
await engines.hover();
|
|
assert.deepEqual(await appearance(), marked);
|
|
await engines.click();
|
|
const dialog = page.getByRole('dialog', { name:'Engines', exact:true });
|
|
const core = dialog.getByRole('checkbox', {name:'Core Engine Always active'});
|
|
assert.equal(await core.isChecked(), true);
|
|
assert.equal(await core.isDisabled(), true);
|
|
const context = dialog.getByRole('checkbox', {name:'Context Engine', exact:true});
|
|
const report = dialog.getByRole('checkbox', {name:'Report Engine', exact:true});
|
|
assert.equal(await context.isChecked(), true);
|
|
assert.equal(await report.isChecked(), true);
|
|
if (process.env.ENGINE_SCREENSHOTS === '1') await page.screenshot({path: `.engines-${theme}.png`});
|
|
await context.uncheck(); await report.uncheck();
|
|
await page.keyboard.press('Escape');
|
|
assert.equal(await dialog.count(), 0);
|
|
assert.equal(await engines.evaluate(el => el === document.activeElement), true);
|
|
assert.deepEqual(await appearance(), marked);
|
|
await engines.click();
|
|
assert.equal(await context.isChecked(), false);
|
|
assert.equal(await report.isChecked(), false);
|
|
await context.check(); await report.check();
|
|
await dialog.getByRole('button', {name:'Close window'}).click();
|
|
console.log(`${theme}: badge filter, disclosure, blue Engines button, locked Core, checkboxes and dismissal passed`);
|
|
}
|
|
} finally {
|
|
await browser?.close();
|
|
await new Promise(resolve => server.close(resolve));
|
|
}
|