import http from 'node:http';
import path from 'node:path';
import {readFile,writeFile} from 'node:fs/promises';
export default async function ({page,outputDir}) {
const root=path.resolve('XFEEDToolBox/Assets/Galnet');
const slides=JSON.parse(await readFile('artifacts/news-carousel/live-slides.json','utf8')).map(s=>({...s,image:'/space.png'}));
const errors=[];page.on('pageerror',error=>errors.push(error.message));
const require=(condition,message)=>{if(!condition)throw new Error(message);};
const server=http.createServer(async(req,res)=>{
try {
const target=path.resolve(root,'.'+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';
res.writeHead(200,{'Content-Type':type}).end(await readFile(target));
} catch {if(!res.headersSent)res.writeHead(404);res.end();}
});
await new Promise(resolve=>server.listen(0,'127.0.0.1',resolve));
try {
await page.setViewportSize({width:3000,height:400});
await page.goto(`http://127.0.0.1:${server.address().port}/index.html`,{waitUntil:'load'});
const observed=await page.locator('#progress').evaluate(e=>({width:e.getBoundingClientRect().width,transform:getComputedStyle(e).transform}));
await page.evaluate(slides=>window.galnet.setSlides(slides),slides);
await page.mouse.move(-10,-10);
const frames=await page.evaluate(()=>new Promise(resolve=>{
const samples=[];
function tick(time){samples.push({time,scale:new DOMMatrixReadOnly(getComputedStyle(document.getElementById('progress')).transform).a});if(samples.length<60)requestAnimationFrame(tick);else resolve(samples);}
requestAnimationFrame(tick);
}));
const moving=frames.slice(1).filter((frame,i)=>frame.scale>frames[i].scale).length;
require(moving>=frames.length*.85,`Progress moved on only ${moving}/${frames.length} frames.`);
await page.screenshot({path:path.join(outputDir,'continuous-progress.png')});
const beforeUpdate=await page.evaluate(()=>window.galnet.inspect().progress);
await page.evaluate(slides=>window.galnet.setSlides(slides.map(s=>({...s,teaser:s.teaser+' '}))),slides);
require(await page.evaluate(()=>window.galnet.inspect().progress)>=beforeUpdate,'A content/translation update reset the animation.');
await page.locator('#carousel').hover();
await page.waitForTimeout(50);
const hover=await page.evaluate(()=>window.galnet.inspect().progress);
await page.waitForTimeout(320);
require(await page.evaluate(()=>window.galnet.inspect().progress)===hover,'Hover did not freeze the same animation timeline.');
await page.mouse.move(-10,-10);await page.waitForTimeout(120);
require(await page.evaluate(()=>window.galnet.inspect().progress)>hover,'Leaving hover did not resume from the same position.');
await page.evaluate(()=>window.galnet.setActive(false));await page.waitForTimeout(50);
const inactive=await page.evaluate(()=>window.galnet.inspect().progress);
await page.waitForTimeout(180);
require(await page.evaluate(()=>window.galnet.inspect().progress)===inactive,'Inactive page advanced the timeline.');
await page.evaluate(()=>window.galnet.setActive(true));
await page.getByRole('button',{name:'暂停自动轮播',exact:true}).click();
require(await page.evaluate(()=>window.galnet.inspect().paused),'Pause button failed.');
await page.getByRole('button',{name:'下一条新闻',exact:true}).click();
require(await page.evaluate(()=>window.galnet.inspect().progress)===0,'Manual navigation did not immediately reset the bar.');
await page.getByRole('button',{name:'继续自动轮播',exact:true}).click();
await page.evaluate(()=>document.activeElement?.blur());await page.mouse.move(-10,-10);
const index=await page.evaluate(()=>window.galnet.inspect().index);
await page.waitForFunction(index=>window.galnet.inspect().index!==index,index,{timeout:10000});
require(await page.evaluate(()=>window.galnet.inspect().progress)<800,'Automatic navigation did not restart the timeline.');
await page.evaluate(()=>window.galnet.setActive(false));
await page.screenshot({path:path.join(outputDir,'after-advance.png')});
require(errors.length===0,errors.join('\n'));
const report={passed:true,viewportWidth:3000,observedBeforeData:observed,sampledFrames:frames.length,movingFrames:moving,translationUpdatePreservesProgress:true,hoverPauseAndResume:true,inactivePagePauses:true,pauseButton:true,manualReset:true,automaticAdvance:true,errors,networkScope:'Local fixture and bundled assets only',gameCallsInvoked:false,frames};
await writeFile(path.join(outputDir,'progress-report.json'),JSON.stringify(report,null,2));
console.log(JSON.stringify({...report,frames:undefined}));
} 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,outputDir}) {
const root=path.resolve('XFEEDToolBox/Assets/Galnet');
const slides=JSON.parse(await readFile('artifacts/news-carousel/live-slides.json','utf8')).map(s=>({...s,image:'/space.png'}));
const errors=[];page.on('pageerror',error=>errors.push(error.message));
const require=(condition,message)=>{if(!condition)throw new Error(message);};
const server=http.createServer(async(req,res)=>{
try {
const target=path.resolve(root,'.'+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';
res.writeHead(200,{'Content-Type':type}).end(await readFile(target));
} catch {if(!res.headersSent)res.writeHead(404);res.end();}
});
await new Promise(resolve=>server.listen(0,'127.0.0.1',resolve));
try {
await page.setViewportSize({width:3000,height:400});
await page.goto(`http://127.0.0.1:${server.address().port}/index.html`,{waitUntil:'load'});
const observed=await page.locator('#progress').evaluate(e=>({width:e.getBoundingClientRect().width,transform:getComputedStyle(e).transform}));
await page.evaluate(slides=>window.galnet.setSlides(slides),slides);
await page.mouse.move(-10,-10);
const frames=await page.evaluate(()=>new Promise(resolve=>{
const samples=[];
function tick(time){samples.push({time,scale:new DOMMatrixReadOnly(getComputedStyle(document.getElementById('progress')).transform).a});if(samples.length<60)requestAnimationFrame(tick);else resolve(samples);}
requestAnimationFrame(tick);
}));
const moving=frames.slice(1).filter((frame,i)=>frame.scale>frames[i].scale).length;
require(moving>=frames.length*.85,`Progress moved on only ${moving}/${frames.length} frames.`);
await page.screenshot({path:path.join(outputDir,'continuous-progress.png')});
const beforeUpdate=await page.evaluate(()=>window.galnet.inspect().progress);
await page.evaluate(slides=>window.galnet.setSlides(slides.map(s=>({...s,teaser:s.teaser+' '}))),slides);
require(await page.evaluate(()=>window.galnet.inspect().progress)>=beforeUpdate,'A content/translation update reset the animation.');
await page.locator('#carousel').hover();
await page.waitForTimeout(50);
const hover=await page.evaluate(()=>window.galnet.inspect().progress);
await page.waitForTimeout(320);
require(await page.evaluate(()=>window.galnet.inspect().progress)===hover,'Hover did not freeze the same animation timeline.');
await page.mouse.move(-10,-10);await page.waitForTimeout(120);
require(await page.evaluate(()=>window.galnet.inspect().progress)>hover,'Leaving hover did not resume from the same position.');
await page.evaluate(()=>window.galnet.setActive(false));await page.waitForTimeout(50);
const inactive=await page.evaluate(()=>window.galnet.inspect().progress);
await page.waitForTimeout(180);
require(await page.evaluate(()=>window.galnet.inspect().progress)===inactive,'Inactive page advanced the timeline.');
await page.evaluate(()=>window.galnet.setActive(true));
await page.getByRole('button',{name:'暂停自动轮播',exact:true}).click();
require(await page.evaluate(()=>window.galnet.inspect().paused),'Pause button failed.');
await page.getByRole('button',{name:'下一条新闻',exact:true}).click();
require(await page.evaluate(()=>window.galnet.inspect().progress)===0,'Manual navigation did not immediately reset the bar.');
await page.getByRole('button',{name:'继续自动轮播',exact:true}).click();
await page.evaluate(()=>document.activeElement?.blur());await page.mouse.move(-10,-10);
const index=await page.evaluate(()=>window.galnet.inspect().index);
await page.waitForFunction(index=>window.galnet.inspect().index!==index,index,{timeout:10000});
require(await page.evaluate(()=>window.galnet.inspect().progress)<800,'Automatic navigation did not restart the timeline.');
await page.evaluate(()=>window.galnet.setActive(false));
await page.screenshot({path:path.join(outputDir,'after-advance.png')});
require(errors.length===0,errors.join('\n'));
const report={passed:true,viewportWidth:3000,observedBeforeData:observed,sampledFrames:frames.length,movingFrames:moving,translationUpdatePreservesProgress:true,hoverPauseAndResume:true,inactivePagePauses:true,pauseButton:true,manualReset:true,automaticAdvance:true,errors,networkScope:'Local fixture and bundled assets only',gameCallsInvoked:false,frames};
await writeFile(path.join(outputDir,'progress-report.json'),JSON.stringify(report,null,2));
console.log(JSON.stringify({...report,frames:undefined}));
} finally {await new Promise(resolve=>server.close(resolve));}
}