69 lines
7.0 KiB
JavaScript
69 lines
7.0 KiB
JavaScript
// Installed Firefox via WebDriver BiDi, matching the existing SW cross-browser workflow.
|
|
import { readFile, writeFile, mkdir } from 'node:fs/promises'
|
|
import { spawn } from 'node:child_process'
|
|
import path from 'node:path'
|
|
import http from 'node:http'
|
|
|
|
const out = path.resolve(process.argv[2] ?? 'docs/sw/storybook-overview-qa')
|
|
await mkdir(out,{recursive:true})
|
|
const report = { checks:[], errors:[], infrastructureWarnings:[] }
|
|
const check = (name,ok,data) => {report.checks.push({name,status:ok?'PASS':'FAIL',data});console.log(ok?'PASS':'FAIL',name)}
|
|
const sleep = ms => new Promise(resolve=>setTimeout(resolve,ms))
|
|
const root=path.resolve('storybook-static'),mime={'.html':'text/html','.js':'text/javascript','.css':'text/css','.json':'application/json','.svg':'image/svg+xml','.ttf':'font/ttf','.png':'image/png'}
|
|
const server=http.createServer(async(req,res)=>{try{const p=new URL(req.url,'http://localhost').pathname,file=path.resolve(root,'.'+decodeURIComponent(p==='/'?'/index.html':p));if(path.relative(root,file).startsWith('..'))return res.writeHead(403).end();res.setHeader('Content-Type',mime[path.extname(file)]??'application/octet-stream');res.end(await readFile(file))}catch{res.writeHead(404).end()}})
|
|
await new Promise(resolve=>server.listen(0,'127.0.0.1',resolve))
|
|
const url=`http://127.0.0.1:${server.address().port}/iframe.html?id=sw-header--overview&viewMode=story`
|
|
let processFirefox,ws
|
|
try {
|
|
for(const [width,height]of[[1400,1000],[500,800]]){
|
|
const profile=path.resolve('_local',`overview-firefox-${process.pid}-${width}`)
|
|
await mkdir(profile,{recursive:true})
|
|
const port=9500+process.pid%400
|
|
processFirefox=spawn(process.env.SW_FIREFOX_PATH??'C:/Program Files/Mozilla Firefox/firefox.exe',['--headless','--no-remote','--profile',profile,`--remote-debugging-port=${port}`],{stdio:'ignore',env:{...process.env,MOZ_HEADLESS_WIDTH:String(width),MOZ_HEADLESS_HEIGHT:String(height)}})
|
|
ws=undefined
|
|
for(let i=0;i<40&&!ws;i++){await sleep(250);try{ws=await new Promise((resolve,reject)=>{const s=new WebSocket(`ws://127.0.0.1:${port}/session`);s.onopen=()=>resolve(s);s.onerror=reject})}catch{ws=undefined}}
|
|
if(!ws)throw Error('Firefox BiDi unavailable')
|
|
let id=0;const pending=new Map()
|
|
ws.onmessage=event=>{const m=JSON.parse(event.data);if(m.id&&pending.has(m.id)){const {resolve,reject}=pending.get(m.id);pending.delete(m.id);m.type==='error'?reject(Error(m.message)):resolve(m.result)}else if(m.method==='log.entryAdded'&&m.params.level==='error'){
|
|
const text=m.params.text
|
|
if(text.startsWith('downloadable font: download failed')&&text.includes('/sb-common-assets/nunito-sans-regular.woff2'))report.infrastructureWarnings.push(text)
|
|
else report.errors.push(text)
|
|
}}
|
|
const send=(method,params={})=>new Promise((resolve,reject)=>{const next=++id;pending.set(next,{resolve,reject});ws.send(JSON.stringify({id:next,method,params}))})
|
|
const session=await send('session.new',{capabilities:{}});report.version=session.capabilities.browserVersion
|
|
await send('session.subscribe',{events:['log.entryAdded']})
|
|
const context=(await send('browsingContext.getTree')).contexts[0].context
|
|
const evaluate=async expression=>{const r=await send('script.evaluate',{expression,target:{context},awaitPromise:true});if(r.type==='exception')throw Error(r.exceptionDetails.text);return r.result.value}
|
|
const data=async expression=>JSON.parse(await evaluate(`JSON.stringify(${expression})`))
|
|
const settle=()=>evaluate('new Promise(resolve=>requestAnimationFrame(()=>requestAnimationFrame(()=>resolve(true))))')
|
|
const key=async value=>send('input.performActions',{context,actions:[{type:'key',id:'keyboard',actions:[{type:'keyDown',value},{type:'keyUp',value}]}]})
|
|
await send('browsingContext.navigate',{context,url,wait:'complete'})
|
|
let ready=false
|
|
for(let i=0;i<40&&!ready;i++){ready=await evaluate('!!document.querySelector("[data-preview-stage]")');if(!ready)await sleep(100)}
|
|
if(!ready)throw Error('Overview did not render')
|
|
await settle()
|
|
const fit=await data('(()=>{const s=document.querySelector("[data-preview-stage]").getBoundingClientRect(),b=document.querySelector("[data-preview-slot]").getBoundingClientRect();return {width:s.width,height:s.height,slotWidth:b.width,slotHeight:b.height,headerHeight:document.querySelector("header.hm-sw-header").getBoundingClientRect().height,viewportWidth:innerWidth,viewportHeight:innerHeight}})()')
|
|
check(`Firefox ${width}: 16:9 in Inner and Header 49`,Math.abs(fit.width/fit.height-16/9)<.005&&fit.width<=fit.slotWidth+.1&&fit.height<=fit.slotHeight+.1&&fit.headerHeight===49,fit)
|
|
await evaluate('(()=>{const s=document.querySelector("select");s.value="expanded";s.dispatchEvent(new Event("change",{bubbles:true}));return true})()');await settle()
|
|
await evaluate('(()=>{const b=document.querySelector("[data-property-panel-trigger]");if(b.getClientRects().length)b.click();else{document.querySelector(".hm-sw-header__more-trigger").click()}return true})()');await settle()
|
|
await evaluate('(()=>{const m=[...document.querySelectorAll("[role=menuitemcheckbox]")].find(e=>e.textContent.trim()==="속성창");if(m)m.click();return true})()');await settle()
|
|
const popup=await data('(()=>{const e=document.querySelector(".hm-sw-property"),r=e.getBoundingClientRect();return {width:r.width,height:r.height,focus:document.activeElement===e}})()')
|
|
check(`Firefox ${width}: full original popup 314x754`,popup.width===314&&popup.height===754,popup)
|
|
const bottom=await data('(()=>{const z=document.querySelector(".hm-sw-property [data-property-id=z] input"),s=document.querySelector("[data-preview-stage]");z.scrollIntoView({block:"nearest",inline:"nearest"});z.focus();const r=z.getBoundingClientRect();return {top:s.scrollTop,visible:document.elementFromPoint(r.x+r.width/2,r.y+r.height/2)===z}})()')
|
|
check(`Firefox ${width}: popup bottom reachable and focusable`,bottom.top>0&&bottom.visible,bottom)
|
|
await key('\uE00C');await settle()
|
|
check(`Firefox ${width}: Escape closes popup and restores focus`,await evaluate('!document.querySelector(".hm-sw-property")&&document.activeElement.matches("[data-property-panel-trigger],.hm-sw-header__more-trigger")'))
|
|
await writeFile(path.join(out,`overview-firefox-${width}.png`),Buffer.from((await send('browsingContext.captureScreenshot',{context})).data,'base64'))
|
|
await send('session.end');ws.close();ws=undefined;processFirefox.kill();processFirefox=undefined
|
|
await sleep(250)
|
|
}
|
|
check('Firefox SW runtime errors absent; known Storybook font warnings recorded separately',report.errors.length===0,{errors:report.errors,warnings:report.infrastructureWarnings})
|
|
}catch(error){check('Firefox suite completes',false,error.stack)}
|
|
finally{
|
|
ws?.close();processFirefox?.kill();await new Promise(resolve=>server.close(resolve))
|
|
report.status=report.checks.every(c=>c.status==='PASS')?'PASS':'FAIL'
|
|
await writeFile(path.join(out,'firefox-result.json'),JSON.stringify(report,null,2)+'\n')
|
|
console.log(JSON.stringify({status:report.status,checks:report.checks.length,version:report.version}))
|
|
process.exitCode=report.status==='PASS'?0:1
|
|
}
|