Files
pigway-console-adapter-fram…/tests/ui.cjs
T
2026-10-18 23:58:37 +08:00

74 lines
5.7 KiB
JavaScript

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