XFE Git
XFE Studio Git
Git 首页 全局搜索
XFE 主站 文档 NuGet

XFEEDToolBox

【WinUI】XFE精英危险工具箱

公开
关注 0 Fork 0 Star 0
UTF-8
import http from 'node:http';
import path from 'node:path';
import {readFile,writeFile} from 'node:fs/promises';

export default async function ({page}) {
  const root=path.resolve('XFEEDToolBox/Assets/Galnet');
  const slides=JSON.parse(await readFile('artifacts/news-carousel/live-slides.json','utf8'));
  const errors=[];page.on('pageerror',e=>errors.push(e.message));
  const server=http.createServer(async(req,res)=>{
    try{
      const target=path.resolve(root,'.'+decodeURIComponent(new URL(req.url,'http://localhost').pathname));
      if(!target.startsWith(root+path.sep)){res.writeHead(403).end();return;}
      const type={'.html':'text/html; charset=utf-8','.js':'text/javascript; charset=utf-8','.css':'text/css; charset=utf-8','.png':'image/png','.svg':'image/svg+xml','.woff2':'font/woff2'}[path.extname(target)]||'application/octet-stream';
      const body=await readFile(target);res.writeHead(200,{'Content-Type':type});res.end(body);
    }catch{if(!res.headersSent)res.writeHead(404);res.end();}
  });
  await new Promise(resolve=>server.listen(0,'127.0.0.1',resolve));
  try{
    await page.addInitScript(()=>{window.chrome??={};window.chrome.webview={postMessage:message=>(window.newsMessages??=[]).push(message)};});
    await page.setViewportSize({width:1440,height:400});
    await page.goto(`http://127.0.0.1:${server.address().port}/index.html`,{waitUntil:'load'});
    await page.evaluate(slides=>window.galnet.setSlides(slides),slides);
    await page.waitForFunction(()=>document.getElementById('cover').complete&&document.getElementById('cover').naturalWidth>0,{},{timeout:30000});
    await page.evaluate(()=>document.fonts.ready);
    await page.screenshot({path:'artifacts/news-carousel/carousel-desktop.png'});
    const initial=await page.evaluate(()=>({title:document.getElementById('slide-title').textContent,background:getComputedStyle(document.body).backgroundImage,font:getComputedStyle(document.getElementById('slide-title')).fontFamily,cover:document.getElementById('cover').naturalWidth}));
    if(!/[\u3400-\u9fff]/.test(initial.title)||!initial.background.includes('space.png')||!initial.font.includes('Century Gothic'))throw new Error('Official styling or automatic Chinese headline is missing');
    await page.locator('#carousel').hover();
    const beforeHover=await page.evaluate(()=>window.galnet.inspect().progress);
    await page.waitForTimeout(450);
    const afterHover=await page.evaluate(()=>window.galnet.inspect().progress);
    if(beforeHover!==afterHover)throw new Error('Hover did not pause the carousel');
    await page.getByRole('button',{name:'下一条新闻',exact:true}).click();
    if(await page.evaluate(()=>window.galnet.inspect().index)!==1)throw new Error('Next button failed');
    await page.keyboard.press('ArrowRight');
    if(await page.evaluate(()=>window.galnet.inspect().index)!==2)throw new Error('Keyboard navigation failed');
    await page.getByRole('button',{name:'暂停自动轮播',exact:true}).click();
    if(!await page.evaluate(()=>window.galnet.inspect().paused))throw new Error('Pause toggle failed');
    await page.getByRole('button',{name:'继续自动轮播',exact:true}).click();
    await page.evaluate(()=>{document.activeElement?.blur();});await page.mouse.move(-10,-10);
    await page.waitForFunction(()=>window.galnet.inspect().index!==2,{},{timeout:11000});
    await page.evaluate(slides=>{window.galnet.setSlides(slides);},[slides[0],...slides.slice(1)]);
    await page.getByRole('button',{name:`第 1 条:${slides[0].title}`,exact:true}).click();
    await page.setViewportSize({width:480,height:400});await page.screenshot({path:'artifacts/news-carousel/carousel-narrow.png'});
    const narrow=await page.evaluate(()=>{const title=document.getElementById('slide-title').getBoundingClientRect(),footer=document.querySelector('.carousel-controls').getBoundingClientRect();return {scrollWidth:document.documentElement.scrollWidth,width:innerWidth,titleBottom:title.bottom,controlsTop:footer.top};});
    if(narrow.scrollWidth>narrow.width||narrow.titleBottom>=narrow.controlsTop)throw new Error('Narrow layout overlaps or overflows');
    await page.setViewportSize({width:1280,height:900});
    await page.evaluate(slide=>window.galnet.setArticle(slide),slides[0]);
    await page.waitForFunction(()=>document.getElementById('article-cover').complete&&document.getElementById('article-cover').naturalWidth>0,{},{timeout:30000});
    if(!(await page.locator('#article-body').innerText()).includes(slides[0].body.split('\n')[0]))throw new Error('Chinese article body was not rendered');
    await page.screenshot({path:'artifacts/news-carousel/article-chinese.png',fullPage:true});
    await page.getByRole('button',{name:'显示原文',exact:true}).click();
    if(await page.locator('#article-title').textContent()!==slides[0].originalTitle)throw new Error('Original-language toggle failed');
    await page.getByRole('button',{name:'显示中文',exact:true}).click();
    await page.getByRole('button',{name:'官网原文 ↗',exact:true}).click();
    if(!await page.evaluate(id=>window.newsMessages.some(m=>m.type==='official'&&m.id===id),slides[0].id))throw new Error('Official-source action failed');
    await page.evaluate(s=>window.galnet.setArticle({...s,title:'<img src=x onerror="alert(1)">',body:'<script>throw new Error("injected")</script>'}),slides[0]);
    if(await page.locator('#article-title img,#article-body script').count())throw new Error('Article text was interpreted as executable markup');
    if(errors.length)throw new Error(errors.join('\n'));
    const report={passed:true,realOfficialImages:true,automaticChineseHeadline:true,automaticChineseBody:true,officialFontsAndStarfield:true,hoverPauses:true,automaticAdvance:true,manualAdvance:true,keyboardAdvance:true,narrowLayout:narrow,originalToggle:true,sourceLink:true,markupEscaped:true,consoleErrors:errors,gameCallsInvoked:false};
    await writeFile('artifacts/news-carousel/browser-verification.json',JSON.stringify(report,null,2));
    console.log(JSON.stringify(report));
  }finally{await new Promise(resolve=>server.close(resolve));}
}