import {createRequire} from 'node:module'; import {readFileSync,writeFileSync} from 'node:fs'; import assert from 'node:assert/strict'; const require=createRequire('C:/kefu/admin-web/package.json');const{chromium}=require('playwright'); const folder='C:/kefu/deploy/loading-performance-20260917'; const browser=await chromium.launch({executablePath:'C:/Program Files/Google/Chrome/Application/chrome.exe',headless:true}); const results=[],errors=[]; for(const [label,root] of [['protocol','C:/wechat_rpa'],['original','C:/kefu/wechat_rpa']]){ const context=await browser.newContext({viewport:{width:1440,height:900}}); await context.route('**/*',route=>route.request().url().startsWith('file:')?route.continue():route.abort()); async function page(view='wechatdb',extra={}){ const p=await context.newPage();p.on('pageerror',e=>errors.push({label,error:e.message})); await p.goto(`file:///${root}/assets/ui/zhenyang-ai-console.html`);await p.waitForTimeout(150); await p.evaluate(({view,extra})=>{ window.calls=[];window.qtBridge=new Proxy({}, {get:(_,method)=>(...args)=>calls.push({method,args})}); window.db={loading:false,query:'',selectedAccount:'account-a',selectedConversation:'conv-a',accounts:[{id:'account-a',name:'测试账号A'},{id:'account-b',name:'测试账号B'}],conversations:[{id:'conv-a',name:'测试会话A'},{id:'conv-b',name:'测试会话B'}],messages:[{id:'msg',content:'合成聊天消息',direction:'inbound'}],...extra}; applyState({view,ready:true,demo:false,wechatDb:db,system:{scale:'自动'},automation:{transport:'protocol',protocol:{status:'idle'}}}); },{view,extra});return p; } async function check(name,fn){try{const detail=await fn();results.push({label,name,passed:true,...(detail||{})});}catch(e){results.push({label,name,passed:false,error:String(e)});}} await check('刷新五次只发一次并立即禁用',async()=>{ const p=await page();const count=await p.evaluate(()=>{const b=document.querySelector('[data-action="wechat-db-refresh"]');for(let i=0;i<5;i++)b.click();return calls.filter(c=>c.method==='refreshWeChatDatabase').length}); assert.equal(count,1);assert.equal(await p.locator('[data-action="wechat-db-refresh"]').isDisabled(),true);await p.close();return {bridgeCalls:count}; }); await check('加载完成恢复刷新按钮,加载中继续禁用',async()=>{ const p=await page();await p.locator('[data-action="wechat-db-refresh"]').click(); await p.evaluate(()=>applyState({view:'wechatdb',wechatDb:{...db,loading:true}}));await p.waitForTimeout(900); assert.equal(await p.locator('[data-action="wechat-db-refresh"]').isDisabled(),true); await p.evaluate(()=>applyState({view:'wechatdb',wechatDb:{...db,loading:false}}));assert.equal(await p.locator('[data-action="wechat-db-refresh"]').isDisabled(),false);await p.close(); }); await check('同账号会话不重复请求,切换中可改选其他目标',async()=>{ const p=await page();const got=await p.evaluate(()=>{ const click=(action,key,n=1)=>{const b=document.querySelector(`[data-action="${action}"][data-key="${key}"]`);for(let i=0;i{ const p=await page('wechatdb',{loading:true});await p.locator('[data-action="wechat-db-conversation"][data-key="conv-b"]').click();await p.locator('#wechat-db-search').fill('最终查询');await p.waitForTimeout(350); assert.deepEqual(await p.evaluate(()=>calls),[{method:'selectWeChatConversation',args:['conv-b']},{method:'setWeChatDatabaseQuery',args:['最终查询']}]);await p.close(); }); await check('患者关联加载不阻塞聊天或搜索',async()=>{ const p=await page('wechatdb',{patientContext:{loading:true,permissions:{can_search:true,can_bind:true,can_unbind:true},patient_bindings:[{is_primary:true,patient_name:'合成人名'}]}}); assert.match(await p.locator('[data-patient-loading]').innerText(),/患者关联加载中/);assert.equal(await p.locator('[data-action="wechat-patient-bind"],[data-action="wechat-patient-unbind"]').count(),0); assert.match(await p.locator('.db-chat').innerText(),/合成聊天消息/);assert.equal(await p.locator('#wechat-db-search').isEnabled(),true);assert.equal(await p.locator('[data-action="wechat-db-conversation"][data-key="conv-b"]').isEnabled(),true); await p.screenshot({path:`${folder}/${label}-patient-loading.png`,scale:'css'});await p.close(); }); await check('中文组字不提交半成品,只查询最终中文',async()=>{ const p=await page();await p.locator('#wechat-db-search').evaluate(input=>input.dispatchEvent(new CompositionEvent('compositionstart',{bubbles:true}))); for(const text of ['ni','nihao','你好']){await p.locator('#wechat-db-search').evaluate((input,text)=>{input.value=text;input.dispatchEvent(new InputEvent('input',{bubbles:true,isComposing:true}));},text);await p.waitForTimeout(330);} assert.equal(await p.evaluate(()=>calls.length),0);await p.locator('#wechat-db-search').evaluate(input=>input.dispatchEvent(new CompositionEvent('compositionend',{bubbles:true})));await p.waitForTimeout(350); assert.deepEqual(await p.evaluate(()=>calls),[{method:'setWeChatDatabaseQuery',args:['你好']}]);await p.close(); }); await check('加载快照保留搜索节点、输入与光标',async()=>{ const p=await page();await p.locator('#wechat-db-search').fill('正在输入');const kept=await p.evaluate(()=>{const input=document.getElementById('wechat-db-search');input.setSelectionRange(2,2);applyState({view:'wechatdb',wechatDb:{...db,loading:true}});return {same:input===document.getElementById('wechat-db-search'),active:document.activeElement===input,value:input.value,position:input.selectionStart}}); assert.deepEqual(kept,{same:true,active:true,value:'正在输入',position:2});await p.close(); }); if(label==='protocol')await check('协议检测五次只发一次并立即禁用',async()=>{ const p=await page('automation');const n=await p.evaluate(()=>{const b=document.querySelector('[data-action="probe-protocol"]');for(let i=0;i<5;i++)b.click();return calls.filter(c=>c.method==='probeProtocol').length});assert.equal(n,1);assert.equal(await p.locator('[data-action="probe-protocol"]').isDisabled(),true);await p.close();return {bridgeCalls:n}; }); await context.close(); } await browser.close();const report={results,errors,total:results.length,passed:results.filter(r=>r.passed).length};writeFileSync(`${folder}/frontend-results.json`,JSON.stringify(report,null,2));console.log(JSON.stringify(report));if(results.some(r=>!r.passed)||errors.length)process.exitCode=1;