import { createServer } from 'vite' import { chromium } from 'playwright' const port = Number(process.env.VISUAL_CHECK_PORT ?? 4175) const server = await createServer({ root: process.cwd(), server: { host: '127.0.0.1', port, strictPort: true, }, }) await server.listen() const browser = await chromium.launch({ headless: true }) const page = await browser.newPage({ viewport: { width: 1440, height: 1024 }, deviceScaleFactor: 1 }) const errors = [] page.on('console', (message) => { if (message.type() === 'error') errors.push(message.text()) }) await page.goto(`http://127.0.0.1:${port}/`, { waitUntil: 'networkidle' }) await page.screenshot({ path: 'test-results/login-react-acro.png', fullPage: true }) await page.locator('.login-form-panel .arco-btn-primary').click() await page.waitForSelector('.workbench-shell') await page.waitForTimeout(700) await page.screenshot({ path: 'test-results/workbench-react-acro-light.png', fullPage: true }) await page.locator('.topbar-actions .arco-btn').first().click() await page.screenshot({ path: 'test-results/workbench-react-acro-dark.png', fullPage: true }) const metrics = await page.evaluate(() => { const statusbar = document.querySelector('.statusbar') const search = document.querySelector('.global-search') const rail = document.querySelector('.project-rail') const railChildren = rail?.querySelector('.arco-layout-sider-children') const dashboard = document.querySelector('.dashboard-button') const project = document.querySelector('.project-button') const create = document.querySelector('.create-project') const firstBadge = document.querySelector('.project-badge .arco-badge-number') const railItems = [...document.querySelectorAll('.dashboard-button, .project-button, .create-project')] const rect = (node) => { const box = node?.getBoundingClientRect() return box ? { left: box.left, right: box.right, top: box.top, width: box.width, height: box.height } : null } const railBox = rail?.getBoundingClientRect() const itemRects = railItems.map(rect).filter(Boolean) const horizontalInsets = railBox && project ? { left: project.getBoundingClientRect().left - railBox.left, right: railBox.right - project.getBoundingClientRect().right, } : null const verticalGaps = itemRects.slice(1).map((item, index) => item.top - (itemRects[index].top + itemRects[index].height)) const overflowState = (node) => node ? { clientWidth: node.clientWidth, scrollWidth: node.scrollWidth, clientHeight: node.clientHeight, scrollHeight: node.scrollHeight, overflowX: getComputedStyle(node).overflowX, overflowY: getComputedStyle(node).overflowY, } : null return { statusbarHeight: statusbar?.getBoundingClientRect().height, searchHeight: search?.getBoundingClientRect().height, projectRailWidth: rail?.getBoundingClientRect().width, dashboardButton: rect(dashboard), projectButton: rect(project), createProjectButton: rect(create), firstProjectBadge: rect(firstBadge), horizontalInsets, verticalGaps, projectRailOverflow: overflowState(rail), projectRailChildrenOverflow: overflowState(railChildren), overflowX: document.documentElement.scrollWidth > document.documentElement.clientWidth, } }) await browser.close() await server.close() console.log(JSON.stringify({ metrics, errors }, null, 2)) const failures = [] if (errors.length) failures.push(`console errors: ${errors.join('; ')}`) if (metrics.statusbarHeight !== 32) failures.push(`expected statusbar height 32, got ${metrics.statusbarHeight}`) if (metrics.searchHeight !== 42) failures.push(`expected search height 42, got ${metrics.searchHeight}`) if (metrics.overflowX) failures.push('expected no horizontal overflow') if (!metrics.projectRailWidth || metrics.projectRailWidth < 88) { failures.push(`expected project rail at least 88px wide, got ${metrics.projectRailWidth}`) } if (!metrics.projectButton) failures.push('missing project button') if (!metrics.horizontalInsets || Math.abs(metrics.horizontalInsets.left - metrics.horizontalInsets.right) > 1) { failures.push(`expected equal rail horizontal insets, got ${JSON.stringify(metrics.horizontalInsets)}`) } if (metrics.verticalGaps.some((gap) => gap !== 12)) { failures.push(`expected all project rail vertical gaps to be 12px, got ${JSON.stringify(metrics.verticalGaps)}`) } for (const [name, overflow] of [ ['project rail', metrics.projectRailOverflow], ['project rail children', metrics.projectRailChildrenOverflow], ]) { if ( !overflow || overflow.overflowX !== 'hidden' || overflow.overflowY !== 'hidden' ) { failures.push(`expected ${name} scrollbars to be hidden with no overflow, got ${JSON.stringify(overflow)}`) } } if ( !metrics.firstProjectBadge || metrics.firstProjectBadge.right > metrics.projectRailWidth || metrics.firstProjectBadge.left < 0 ) { failures.push(`expected first project badge to stay inside the project rail, got ${JSON.stringify(metrics.firstProjectBadge)}`) } if ( !metrics.dashboardButton || metrics.dashboardButton.width !== metrics.projectButton?.width || metrics.dashboardButton.height !== metrics.projectButton?.height ) { failures.push( `dashboard button size ${JSON.stringify(metrics.dashboardButton)} does not match project button ${JSON.stringify(metrics.projectButton)}`, ) } if ( !metrics.createProjectButton || metrics.createProjectButton.width !== metrics.projectButton?.width || metrics.createProjectButton.height !== metrics.projectButton?.height ) { failures.push( `create project button size ${JSON.stringify(metrics.createProjectButton)} does not match project button ${JSON.stringify(metrics.projectButton)}`, ) } if (failures.length) { throw new Error(failures.join('\n')) }