// 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); 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 from 'react'; import {createRoot} from 'react-dom/client'; import {ResultsBrowser} from './src/features/results-browser/ResultsBrowser'; createRoot(document.getElementById('root')).render(); `,resolveDir:process.cwd(),loader:'tsx'}, plugins:[{name:'synthetic',setup(build){ build.onResolve({filter:/assessor-api-client$/},args=>({path:args.path,namespace:'fixture'})); build.onLoad({filter:/.*/,namespace:'fixture'},()=>({contents:` const row={resultProjectionId:'synthetic',sourceResultId:'synthetic',sourceKind:'scanner-finding',sourceTool:'pingcastle',sourceRuleId:'A-NotEnoughDC',title:'Domain controller redundancy',severityNormalized:'high',outcomeNormalized:'fail',resultType:'exposure_finding',isDeviation:true,isIoe:true,frameworkReferences:[],categories:[]}; export class AssessorApiClient {async queryUnifiedResultFacets(){return {facets:[]}} async queryUnifiedResults(){return {items:[row],total:1}} async getResultDbStatus(){return null}} `,loader:'js'})); }}],define:{'import.meta.env.DEV':'false'},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 ?? '
'); }); 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:1440,height:900},acceptDownloads:true}); await page.goto(`http://127.0.0.1:${server.address().port}/`); for(const theme of ['dark','light']) { await page.evaluate(t=>document.documentElement.dataset.theme=t,theme); for (const [label, extension] of [['Export Excel', '.xlsx'], ['Export CSV', '.csv']]) { const button=page.getByRole('button',{name:label,exact:true}); await button.waitFor(); const [download]=await Promise.all([page.waitForEvent('download'),button.click()]); assert.ok(download.suggestedFilename().endsWith(extension)); assert.equal(await download.failure(),null); } await page.screenshot({path:`.excel-${theme}.png`}); } console.log('Excel and CSV downloads verified in both themes'); } finally {await browser?.close();await new Promise(resolve=>server.close(resolve));}