import { loadPlaywright, launchChromium } from '../../scripts/orgchart-browser-runtime.mjs'; import { readFile, writeFile } from 'node:fs/promises'; await loadPlaywright(); const output = 'test-results/org-chart-final-check'; const baselineMode = process.argv.includes('--baseline'); const repairMode = process.argv.includes('--geometry-repair'); const browser = await launchChromium({ headless: true }); const measurements = [], results = [], errors = []; const check = (ok, name, actual) => results.push({ name, status: ok ? 'PASS' : 'FAIL', actual }); const previous = baselineMode ? [] : JSON.parse(await readFile(`${output}/logo-before.json`, 'utf8')); try { for (const story of ['department-colors', 'full-composition']) { const page = await browser.newPage({ viewport: { width: 1440, height: 1000 } }); let expectedImageFailure = false; page.on('pageerror', error => errors.push(error.message)); page.on('console', item => { if (item.type() === 'error' && !(expectedImageFailure && item.text().includes('ERR_INVALID_URL'))) errors.push(item.text()); }); await page.goto(`${process.env.ORGCHART_STORY_ORIGIN}/iframe.html?id=orgchart--${story}&viewMode=story`); await page.waitForFunction(() => { const root = document.querySelector('.hm-orgchart-host, .hm-orgchart-component-host')?.shadowRoot; return root?.querySelector('[data-card-asset]') && [...root.querySelectorAll('link')].every(link => link.sheet); }); await page.evaluate(async () => { await document.fonts.ready; const root = document.querySelector('.hm-orgchart-host, .hm-orgchart-component-host').shadowRoot; await Promise.all([...root.querySelectorAll('img')].map(image => image.decode().catch(() => {}))); }); const logos = await page.evaluate(() => { const root = document.querySelector('.hm-orgchart-host, .hm-orgchart-component-host').shadowRoot; const rect = element => { const b = element.getBoundingClientRect(); return { x: b.x, y: b.y, width: b.width, height: b.height }; }; return [...root.querySelectorAll('[data-card-kind] .hm-orgchart-affiliation-mark')].map(mark => { const img = mark.querySelector('img'), style = getComputedStyle(mark), fallback = mark.querySelector('.hm-orgchart-affiliation-fallback'); return { id: mark.closest('[data-card-kind]').dataset.employeeId, kind: mark.closest('[data-card-kind]').dataset.cardKind, bounds: rect(mark), image: img ? rect(img) : null, imageFit: img && getComputedStyle(img).objectFit, background: style.backgroundColor, borders: [style.borderTopWidth, style.borderRightWidth, style.borderBottomWidth, style.borderLeftWidth], borderStyle: style.borderTopStyle, shadow: style.boxShadow, imageBackground: img && getComputedStyle(img).backgroundColor, before: getComputedStyle(mark, '::before').content, after: getComputedStyle(mark, '::after').content, loaded: !!img?.naturalWidth, fallbackVisibility: getComputedStyle(fallback).visibility }; }); }); measurements.push({ story, logos }); if (!baselineMode) { for (const [index, logo] of logos.entries()) { const before = previous.find(item => item.story === story).logos[index]; if (!repairMode) check(logo.background === 'rgba(0, 0, 0, 0)' && logo.borders.every(value => value === '0px') && logo.borderStyle === 'none' && logo.shadow === 'none', `${story}/${logo.id}: transparent borderless container`, logo); check(JSON.stringify(logo.bounds) === JSON.stringify(before.bounds) && JSON.stringify(logo.image) === JSON.stringify(before.image) && logo.imageFit === 'contain', `${story}/${logo.id}: unchanged container/image geometry`, { before, after: logo }); if (!repairMode) check(logo.loaded && logo.fallbackVisibility === 'hidden' && logo.imageBackground === 'rgba(0, 0, 0, 0)' && ['none', 'normal'].includes(logo.before) && ['none', 'normal'].includes(logo.after), `${story}/${logo.id}: loaded transparent image and no fallback/decorations`, logo); } await page.locator('[data-employee-id="mock-leader-04"]').screenshot({ path: `${output}/${story}-logo-card.png` }); expectedImageFailure = true; const failure = await page.evaluate(async () => { const root = document.querySelector('.hm-orgchart-host, .hm-orgchart-component-host').shadowRoot; const mark = root.querySelector('[data-card-asset] .hm-orgchart-affiliation-mark'); mark.querySelector('img').src = 'data:image/png;base64,invalid-image'; await new Promise(resolve => setTimeout(resolve, 100)); const fallback = mark.querySelector('.hm-orgchart-affiliation-fallback'); return { noImage: !mark.querySelector('img'), available: mark.dataset.logoAvailable, visibility: getComputedStyle(fallback).visibility, text: fallback.textContent }; }); if (!repairMode) check(failure.noImage && failure.available === 'false' && failure.visibility === 'visible' && !!failure.text, `${story}: failed image retains fallback`, failure); } await page.close(); } } finally { await browser.close(); } if (baselineMode) await writeFile(`${output}/logo-before.json`, JSON.stringify(measurements, null, 2)); else { check(errors.length === 0, 'browser console/runtime errors', errors); await writeFile(`${output}/${repairMode ? 'logo-geometry-repair' : 'logo-browser'}.json`, JSON.stringify({ results, measurements, errors }, null, 2)); console.log(JSON.stringify({ checks: results.length, pass: results.filter(item => item.status === 'PASS').length, failures: results.filter(item => item.status === 'FAIL'), errors }, null, 2)); if (results.some(item => item.status === 'FAIL')) process.exitCode = 1; }