Files
HMDesign/packages/sw/tests/property-close.browser.mjs

65 lines
5.4 KiB
JavaScript

// Historical since 2026-10-19: opens SW Story IDs retired by the unused demo removal (property-panel). Kept as evidence
// for its recorded run; current regression: storybook-unused-removal.browser.mjs.
// Property panel close button (Figma 26:12366): canvas bleed, Default / Hover, close by click and Escape.
// Usage: node packages/sw/tests/property-close.browser.mjs <evidence-dir> <label> [storybook-url]
// Storybook serves packages/sw/dist: build @hm-design/sw first.
import { mkdir, writeFile } from 'node:fs/promises'
import path from 'node:path'
import { loadPlaywright } from './playwright-runtime.mjs'
const out = path.resolve(process.argv[2] ?? 'docs/sw/property-close-qa')
const label = process.argv[3] ?? 'run'
const base = process.argv[4] ?? 'http://127.0.0.1:6024'
const { chromium } = loadPlaywright()
await mkdir(out, { recursive: true })
const browser = await chromium.launch({ executablePath: process.env.SW_CHROME_PATH ?? 'C:/Program Files/Google/Chrome/Application/chrome.exe' })
const page = await browser.newPage({ viewport: { width: 1400, height: 1000 }, deviceScaleFactor: 4 })
const report = { label, checks: [], errors: [] }
page.on('pageerror', e => report.errors.push(e.message)); page.on('console', m => { if (m.type() === 'error') report.errors.push(m.text()) })
const check = (name, ok, data) => { report.checks.push({ name, status: ok ? 'PASS' : 'FAIL', data }); console.log(ok ? 'PASS' : 'FAIL', name, JSON.stringify(data)) }
await page.goto(`${base}/iframe.html?id=sw-header--property-panel&viewMode=story`)
await page.locator('.hm-sw-header').waitFor(); await page.evaluate(() => document.fonts.ready)
await page.getByRole('button', { name: '원 1개', exact: true }).click()
const trigger = page.locator('.hm-sw-header [data-property-panel-trigger]')
await trigger.click(); await page.mouse.move(1, 1)
const close = page.locator('.hm-sw-property__close').first()
const box = await close.boundingBox()
const geo = await close.evaluate(e => { const p = e.closest('.hm-sw-property').getBoundingClientRect(), r = e.getBoundingClientRect(); return { w: r.width, h: r.height, right: p.right - r.right, top: r.top - p.top, imgs: [...e.querySelectorAll('img')].map(i => [i.className, i.getBoundingClientRect().width, i.getBoundingClientRect().height]) } })
check('size 44 x 30, top-right of the panel', geo.w === 44 && geo.h === 30, geo)
// Light pixels (all channels >= 235) inside the 44 x 30 button: the Figma canvas is #F5F5F5 / white.
const bright = async name => {
const clip = { x: box.x - 2, y: box.y - 2, width: box.width + 4, height: box.height + 4 }
const buf = await page.screenshot({ clip })
await writeFile(path.join(out, `${label}-${name}.png`), buf)
return page.evaluate(async ([b64, w, h]) => {
const i = new Image(); i.src = 'data:image/png;base64,' + b64; await i.decode()
const c = document.createElement('canvas'); c.width = i.width; c.height = i.height; const x = c.getContext('2d'); x.drawImage(i, 0, 0)
const s = i.width / (w + 4), d = x.getImageData(0, 0, i.width, i.height).data; let n = 0; const where = []
for (let yy = Math.round(2 * s); yy < Math.round((h + 2) * s); yy++) for (let xx = Math.round(2 * s); xx < Math.round((w + 2) * s); xx++) {
const bx = xx / s - 2, by = yy / s - 2, edge = bx < 1.5 || bx > w - 1.5 || by < 1.5 || by > h - 1.5 || (bx > w - 4 && by < 4)
if (!edge) continue // the white X of Hover is intended; the canvas can only show at the edge / rounded corner
const k = (yy * i.width + xx) * 4; if (d[k] >= 235 && d[k + 1] >= 235 && d[k + 2] >= 235) { n++; if (where.length < 6) where.push([+(xx / s - 2).toFixed(2), +(yy / s - 2).toFixed(2)]) } }
return { bright: n, where }
}, [buf.toString('base64'), box.width, box.height])
}
const def = await bright('default')
await close.hover()
const hov = await bright('hover')
const vis = await close.evaluate(e => [...e.querySelectorAll('img')].map(i => getComputedStyle(i).visibility))
report.pixels = { def, hov }
check('Default: no canvas-light pixels on the button edge / corner', def.bright === 0, def)
check('Hover: hover asset shown, no canvas-light pixels on the edge / corner', vis.join() === 'hidden,visible' && hov.bright === 0, { vis, ...hov })
// Click closes; focus returns to the trigger.
await close.click()
let state = await page.evaluate(() => ({ open: !!document.querySelector('.hm-sw-property'), focusTrigger: document.activeElement?.hasAttribute('data-property-panel-trigger'), log: document.querySelector('[data-property-log]').textContent.slice(-60) }))
check('click closes the panel, focus back on 속성창 trigger', !state.open && state.focusTrigger, state)
// Escape closes from inside the panel; focus returns.
await trigger.click(); await page.locator('.hm-sw-property').waitFor()
await page.locator('.hm-sw-property__close').first().focus()
await page.keyboard.press('Escape')
state = await page.evaluate(() => ({ open: !!document.querySelector('.hm-sw-property'), focusTrigger: document.activeElement?.hasAttribute('data-property-panel-trigger'), log: document.querySelector('[data-property-log]').textContent.slice(-60) }))
check('Escape closes the panel, focus back on 속성창 trigger', !state.open && state.focusTrigger, state)
check('no console / page errors', report.errors.length === 0, report.errors)
await writeFile(path.join(out, `${label}-result.json`), JSON.stringify(report, null, 2))
await browser.close()
console.log('TOTAL', report.checks.length, 'FAIL', report.checks.filter(c => c.status === 'FAIL').length)