// 使用受控身份和接口数据检查实际 Vue 页面,不修改业务库。 const fs = require('node:fs/promises'); const path = require('node:path'); const { chromium } = require(process.env.SW_PLAYWRIGHT_MODULE || 'playwright'); (async () => { const token = (await fs.readFile(path.join(process.env.TEMP, 'sw-kra-http-test-token'), 'utf8')).trim(); const browser = await chromium.launch({channel:'chrome', headless:true}); const results = []; try { for (const level of [0,1,2,3]) { const context = await browser.newContext({viewport:{width:1440,height:1000}}); await context.addInitScript(value => localStorage.setItem('token',value),token); const page = await context.newPage(); const pageErrors=[]; page.on('pageerror',e=>pageErrors.push(e.message)); let bindingCalls=0; const channelCalls=[]; await page.route('http://127.0.0.1:8080/api/**', async route => { const request=route.request(); const uri=new URL(request.url()); const apiPath=uri.pathname.slice(4); let body; if(apiPath==='/sw/business/list') body={code:0,msg:'成功',data:{rows:[{userId:79999,userName:'展示测试商务',level:2,parentUserName:'上级商务',realName:'测试姓名',phoneNumber:'',bindCount:0,promoteCount:0,createTime:'2026-11-03 10:00:00',status:'0',authorityId:902}],total:1}}; else if(/^\/sw\/business\/\d+\/promotes$/.test(apiPath)) { channelCalls.push(Object.fromEntries(uri.searchParams)); body={code:0,msg:'成功',data:{rows:[{promoteId:1,promoteName:'渠道测试',sourceDb:'lx',platformName:'龙翔'}],total:1,available:true}}; } else if(apiPath==='/sw/business/bindPlatform') { bindingCalls++; body={code:0,msg:'成功',data:{total:3,rows:['0','2','3'].map((status,i)=>({userId:70000+level,sourceDb:'lx',platformName:'龙翔',platformBusierId:10+i,platformBusierAccount:['正常账号','驳回账号','待审账号'][i],bindUserName:'测试商务',promoteCount:status==='0'?2:0,status,createTime:'2026-11-03 10:00:00',reviewTime:'2026-11-03 11:00:00',reviewAccount:'测试审核人',rejectReason:status==='2'?'测试驳回原因':null}))}}; } else if(/^\/sw\/business\/\d+\/bindPlatform$/.test(apiPath)) body={code:0,msg:'成功',data:[]}; if(body) {await route.fulfill({json:body});return;} const response=await route.fetch({url:'http://127.0.0.1:18000'+apiPath+uri.search,headers:{...request.headers(),'x-token':token}}); if(apiPath==='/user/getUserInfo') { body=await response.json(); const info=body.data.userInfo; Object.assign(info,{ID:70000+level,level,nickName:'页面核对账号',phone:'',email:'',authorityId:level===0?888:900+level}); await route.fulfill({response,json:body}); } else await route.fulfill({response}); }); await page.goto('http://127.0.0.1:8080/#/layout/sw-BusMgmt/BusinessList'); try { await page.getByRole('tab',{name:'商务列表',exact:true}).waitFor({timeout:15000}); } catch (error) { console.log(JSON.stringify({url:page.url(),errors:pageErrors,text:(await page.locator('body').innerText()).slice(0,1800)})); await page.screenshot({path:path.join(process.env.TEMP,'sw-kra-browser-failure.png')}); throw error; } await page.getByText('展示测试商务',{exact:true}).waitFor(); const tabs=await page.getByRole('tab',{name:'绑定列表',exact:true}).count(); const editButtons=await page.getByRole('button',{name:'编辑',exact:true}).count(); if(tabs!==(level===3?1:0)||editButtons!==0) throw new Error('页面权限或编辑入口不一致 level='+level); const result={level,bindingTab:tabs===1,editButtons,passed:true}; if(level===1 || level===2) { await page.getByRole('row').filter({hasText:'展示测试商务'}).getByRole('button',{name:'查看SDK商务绑定详情',exact:true}).click(); await page.getByRole('dialog',{name:'SDK商务绑定详情'}).waitFor(); result.sdkDetailReadonly=(await page.getByRole('dialog',{name:'SDK商务绑定详情'}).getByRole('button',{name:'解绑',exact:true}).count())===0; if(!result.sdkDetailReadonly)throw new Error('SDK 详情出现写操作'); await page.getByRole('dialog',{name:'SDK商务绑定详情'}).getByRole('button',{name:'关闭此对话框',exact:true}).click(); } if(level===3) { await page.getByRole('tab',{name:'绑定列表',exact:true}).click(); await page.getByText('正常账号',{exact:true}).waitFor(); const normal=page.getByRole('row').filter({hasText:'正常账号'}); const rejected=page.getByRole('row').filter({hasText:'驳回账号'}); const pending=page.getByRole('row').filter({hasText:'待审账号'}); result.normalUnbind=await normal.getByRole('button',{name:'解绑',exact:true}).count(); result.rejectedUnbind=await rejected.getByRole('button',{name:'解绑',exact:true}).count(); result.pendingUnbind=await pending.getByRole('button',{name:'解绑',exact:true}).count(); result.rejectedDetail=await rejected.getByRole('button',{name:'查看驳回',exact:true}).count(); if(result.normalUnbind!==1||result.rejectedUnbind||result.pendingUnbind||result.rejectedDetail!==1)throw new Error('绑定状态操作不一致'); result.rejectedChannelsDisabled=await rejected.getByRole('button',{name:'查看旗下渠道',exact:true}).isDisabled(); result.pendingChannelsDisabled=await pending.getByRole('button',{name:'查看旗下渠道',exact:true}).isDisabled(); if(!result.rejectedChannelsDisabled || !result.pendingChannelsDisabled)throw new Error('未通过绑定仍可查看渠道'); await normal.getByRole('button',{name:'查看旗下渠道',exact:true}).click(); await page.getByText('渠道测试',{exact:true}).waitFor(); const channelQuery=channelCalls.at(-1); if(channelQuery.sourceDb!=='lx'||channelQuery.platformBusierId!=='10')throw new Error('旗下渠道未携带平台和商务筛选'); result.channelFilters=true; await page.getByRole('dialog',{name:'旗下渠道',exact:true}).getByRole('button',{name:'关闭此对话框',exact:true}).click(); await rejected.getByRole('button',{name:'查看驳回',exact:true}).click(); await page.getByText('测试驳回原因',{exact:true}).waitFor(); const panel=await page.getByRole('dialog',{name:'审核详情',exact:true}).elementHandle(); await page.waitForFunction(el=>{const box=el.getBoundingClientRect();return box.x>=0 && box.right<=innerWidth+1},panel,{timeout:5000}); result.rejectDrawerInViewport=true; } else if(bindingCalls!==0) throw new Error('非三级加载了绑定列表'); result.bindingCalls=bindingCalls; result.pageErrors=pageErrors; await page.screenshot({path:path.join(process.env.TEMP,'sw-kra-business-level-'+level+'.png'),fullPage:true,animations:'disabled'}); results.push(result); console.log(JSON.stringify(result)); await context.close(); } await fs.writeFile('docs/sw6-to-kra/business-page-parity-2026-11-03.json',JSON.stringify({method:'真实 Chrome 渲染;受控身份和业务数据,权限页签与状态操作核对',results},null,2)); } finally {await browser.close();} })().catch(error=>{console.error(error.message);process.exitCode=1});