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));}
}
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));}
}