This commit is contained in:
+71
-33
@@ -1,35 +1,73 @@
|
||||
const {chromium}=require('playwright');
|
||||
const {chromium}=require(process.env.PLAYWRIGHT_MODULE||'playwright');
|
||||
const fs=require('node:fs'),path=require('node:path'),assert=require('node:assert/strict');
|
||||
const root=path.resolve(__dirname,'..'),consoleRoot=path.resolve(root,'../pigway-device-console');
|
||||
const manifest=JSON.parse(fs.readFileSync(path.join(root,'plugin/manifest.json')));
|
||||
(async()=>{
|
||||
const browser=await chromium.launch({headless:true,channel:'chrome'});
|
||||
try {
|
||||
const source=fs.readFileSync(path.join(__dirname,'../plugin/ui/index.js'),'utf8').replace(/^import .*;\n/gm,'').replace('export function hostCard','function hostCard');
|
||||
const labels={service_ready:{zh:'服务就绪',en:'Service ready'},device_online:{zh:'画框在线',en:'Frame online'},battery_percent:{zh:'电量',en:'Battery'}};
|
||||
const entry={host:{name:'Frame Flow'},dataCurrent:true,snapshot:{resources:[{id:'service',name:'Frame Flow',type:'service'},{id:'device:living-room',name:'渡口浪人的相框',type:'device'}],metrics:[{resource_id:'service',key:'service_ready',value:1,unit:'boolean',label:labels.service_ready},{resource_id:'device:living-room',key:'device_online',value:1,unit:'boolean',label:labels.device_online},{resource_id:'device:living-room',key:'battery_percent',value:28,unit:'percent',label:labels.battery_percent}],alerts:[]}};
|
||||
entry.snapshot.resources.push({id:'job:slideshow',name:'家庭照片轮播',type:'job'});
|
||||
entry.snapshot.metrics.push({resource_id:'job:slideshow',key:'job_enabled',value:1,unit:'boolean'},{resource_id:'job:slideshow',key:'last_run_success',value:1,unit:'boolean'});
|
||||
for(const lang of ['zh','en'])for(const width of [1440,820,390]) {
|
||||
const page=await browser.newPage({bypassCSP:true,viewport:{width,height:1000}});
|
||||
await page.goto('http://192.168.89.19/');
|
||||
const result=await page.evaluate(async ({source,entry,lang})=>{
|
||||
const {scopeAlertHealth,summaryStatus}=await import('/assets/js/overview-health.js');
|
||||
const state={LANG:lang};const text=(zh,en)=>lang==='zh'?zh:en;
|
||||
const esc=v=>String(v??'').replace(/[&<>"']/g,c=>({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c]));
|
||||
const html=eval(source+'\nhostCard(entry)');
|
||||
document.body.innerHTML=`<main style="max-width:100%;padding:16px">${html}</main>`;
|
||||
return {text:document.body.innerText,overflow:document.documentElement.scrollWidth>innerWidth,subtitle:document.querySelector(".host-head .device-details")?.textContent,nested:document.querySelectorAll(".group .metric, .metric .metric").length,status:document.querySelector(".metric [data-semantic]")?.textContent};
|
||||
},{source,entry,lang});
|
||||
assert(result.text.includes('渡口浪人的相框'));
|
||||
assert(!result.text.includes('living-room'));
|
||||
assert(result.text.includes(lang==='zh'?'服务就绪':'Service ready'));
|
||||
assert.equal(result.overflow,false);
|
||||
assert.equal(result.nested,0);
|
||||
assert.equal(result.subtitle,'渡口浪人的相框');
|
||||
assert.equal(result.status,lang==='zh'?'正常':'Normal');
|
||||
assert(result.text.includes(lang==='zh'?'轮播任务':'Slideshow task'));
|
||||
assert(result.text.includes('家庭照片轮播 · '+(lang==='zh'?'启用':'Enabled')));
|
||||
await page.close();
|
||||
}
|
||||
console.log('6 browser checks passed');
|
||||
} finally {await browser.close()}
|
||||
})().catch(e=>{console.error(e);process.exit(1)});
|
||||
const browser=await chromium.launch({headless:true});
|
||||
try{
|
||||
for(const lang of ['zh','en'])for(const theme of ['light','dark'])for(const width of [1440,820,390]){
|
||||
let host={id:'frame-service',name:'Frame Flow',adapter_id:manifest.id,category:'host',adapter_enabled:true,capabilities:['status'],url:'https://frames.example.test',connection:{url:'https://frames.example.test'}};
|
||||
let writes=[],failSave=false;
|
||||
const page=await browser.newPage({viewport:{width,height:1000}}),errors=[];
|
||||
page.on('pageerror',error=>errors.push(error.message));
|
||||
await page.addInitScript(({lang,theme})=>{localStorage.setItem('pigwayLanguage',lang);localStorage.setItem('pigwayTheme',theme)},{lang,theme});
|
||||
await page.route('http://console.test/**',route=>{
|
||||
const p=new URL(route.request().url()).pathname;
|
||||
if(p.startsWith('/api/')){
|
||||
if(route.request().method()==='PUT'){
|
||||
assert.equal(p,'/api/v1/hosts');const body=route.request().postDataJSON();writes.push(body);
|
||||
assert.equal(body.operation,'save');assert.equal(body.adapter_id,manifest.id);assert.equal(body.connection.token,'');
|
||||
if(failSave)return route.fulfill({status:500,json:{error:'Save failed'}});
|
||||
host={...host,url:body.connection.url,connection:{...body.connection}};
|
||||
return route.fulfill({json:{hosts:[host]}});
|
||||
}
|
||||
const values={
|
||||
'/api/v1/auth/status':{authenticated:true,setup_required:false},
|
||||
'/api/v1/hosts':{hosts:[host]},
|
||||
'/api/v1/adapters':{adapters:[{...manifest,enabled:true,state:'ready'}]},
|
||||
'/api/v1/fleet/status':{hosts:[{host,online:true,snapshot:{identity:{},resources:[{id:'frame',name:'Living room frame',type:'device'},{id:'job:slideshow',name:'Slideshow',type:'job'}],metrics:[{resource_id:'job:slideshow',key:'job_enabled',value:1},{resource_id:'job:slideshow',key:'last_run_success',value:1}],alerts:[],alerts_complete:true}}]},
|
||||
};
|
||||
return route.fulfill({json:values[p]||{}});
|
||||
}
|
||||
const file=p.startsWith('/adapter-assets/')?path.join(root,'plugin/ui/index.js'):path.join(consoleRoot,'web',p==='/'?'index.html':p.replace(/^\/assets\//,''));
|
||||
if(!fs.existsSync(file))return route.fulfill({status:404});
|
||||
return route.fulfill({contentType:file.endsWith('.js')?'text/javascript':file.endsWith('.css')?'text/css':'text/html',body:fs.readFileSync(file)});
|
||||
});
|
||||
const link=()=>page.locator('#hostCards .host-title a');
|
||||
const config=async()=>{
|
||||
await page.locator('[data-tab="config"]').click();
|
||||
await page.locator('[data-config-section="frame-flow"]').click();
|
||||
await page.locator('#saveFrameFlowConnection').waitFor();
|
||||
};
|
||||
await page.goto('http://console.test');await link().waitFor();
|
||||
assert.equal(await link().getAttribute('href'),'https://frames.example.test/');
|
||||
assert.equal(await link().getAttribute('target'),'_blank');
|
||||
assert.equal(await link().getAttribute('rel'),'noopener noreferrer');
|
||||
assert.equal(await link().getAttribute('aria-label'),lang==='zh'?'打开 Frame Flow 管理':'Open Frame Flow management');
|
||||
assert.equal(await page.locator('#hostCards .device-details').textContent(),'Living room frame');
|
||||
await config();
|
||||
assert.equal(await page.locator('[data-frame-flow-connection="url"]').inputValue(),'https://frames.example.test');
|
||||
assert.equal(await page.locator('[data-frame-flow-connection="token"]').inputValue(),'');
|
||||
assert(await page.evaluate(()=>document.documentElement.scrollWidth<=innerWidth+1));
|
||||
if(lang==='zh'&&theme==='light')await page.locator('#config').screenshot({path:'/tmp/frame-flow-config-'+width+'.png'});
|
||||
if(lang==='zh'&&theme==='light'&&width===1440){
|
||||
const management=page.locator('[data-frame-flow-connection="management_url"]');
|
||||
await management.fill('javascript:alert(1)');await page.locator('#saveFrameFlowConnection').click();
|
||||
await page.locator('#frameFlowConnectionMessage.error').waitFor();assert.equal(writes.length,0);
|
||||
await management.fill('https://manage.example.test');await page.locator('#saveFrameFlowConnection').click();
|
||||
await page.locator('#frameFlowConnectionMessage.success').waitFor();assert.equal(writes.length,1);
|
||||
await page.reload();await link().waitFor();assert.equal(await link().getAttribute('href'),'https://manage.example.test/');
|
||||
await config();failSave=true;
|
||||
await page.locator('[data-frame-flow-connection="management_url"]').fill('https://changed.example.test');await page.locator('#saveFrameFlowConnection').click();
|
||||
await page.getByText('Save failed',{exact:true}).waitFor();
|
||||
assert.equal(await page.locator('[data-frame-flow-connection="management_url"]').inputValue(),'https://changed.example.test');
|
||||
failSave=false;await page.locator('[data-frame-flow-connection="management_url"]').fill('');await page.locator('#saveFrameFlowConnection').click();
|
||||
await page.locator('#frameFlowConnectionMessage.success').waitFor();await page.reload();await link().waitFor();
|
||||
assert.equal(await link().getAttribute('href'),'https://frames.example.test/');
|
||||
host.connection.management_url='javascript:alert(1)';await page.reload();await page.locator('#hostCards h2').waitFor();assert.equal(await link().count(),0);
|
||||
}
|
||||
assert.deepEqual(errors,[]);await page.close();
|
||||
}
|
||||
console.log('PASS Frame Flow management link/configuration: default/custom/reset URL, save failure, unsafe URLs, API-only edits; 12 language/theme/viewport combinations');
|
||||
}finally{await browser.close();}
|
||||
})().catch(error=>{console.error(error);process.exit(1)});
|
||||
|
||||
Reference in New Issue
Block a user