import { createRequire } from 'node:module'; import { readFileSync, writeFileSync } from 'node:fs'; const require=createRequire('C:/kefu/admin-web/package.json'); const { chromium }=require('playwright'); const root='C:/kefu/deploy/knowledge-v3-field-fix/qa'; const fixture=JSON.parse(readFileSync(`${root}/fixture.json`,'utf8')); const browser=await chromium.launch({executablePath:'C:/Program Files/Google/Chrome/Application/chrome.exe',headless:true,args:['--no-proxy-server']}); const context=await browser.newContext({viewport:{width:1600,height:1040},locale:'zh-CN'}); await context.route('**/*', route=>new URL(route.request().url()).hostname==='127.0.0.1'?route.continue():route.abort()); const page=await context.newPage(); page.setDefaultTimeout(20000); const errors=[]; const checks=[]; page.on('pageerror',error=>errors.push(error.message)); const origin='http://127.0.0.1:18868'; const login=await fetch(`${origin}/api/v2/auth/login`,{method:'POST',headers:{'Content-Type':'application/json'},body:JSON.stringify({username:'admin',password:'KnowledgeQA@2026'})}).then(r=>r.json()); const api=async(path)=>{ const r=await fetch(`${origin}/api/v2/knowledge/${path}?account_id=${fixture.account_id}`,{headers:{Authorization:`Bearer ${login.access_token}`}}); if(!r.ok) throw new Error(`API ${path}: ${r.status}`); return r.json(); }; const dialog=(title)=>page.getByRole('dialog').filter({has:page.getByText(title,{exact:true})}); const select=async(container,label,value)=>{ await container.locator('.ant-form-item').filter({has:page.locator('label').getByText(label,{exact:true})}).locator('.ant-select-selector').click(); await page.locator('.ant-select-dropdown:visible').getByText(value,{exact:true}).click(); }; try { await page.goto(`${origin}/#/auth/login`); await page.getByPlaceholder('用户名',{exact:true}).fill('admin'); await page.getByPlaceholder('密码',{exact:true}).fill('KnowledgeQA@2026'); await page.getByRole('button',{name:'login',exact:true}).click(); await page.waitForURL(url=>!url.hash.includes('/auth/login')); await page.goto(`${origin}/#/knowledge/center`); await page.locator('.ant-card-extra .ant-select-selector').click(); await page.locator('.ant-select-dropdown:visible').getByText('流程验收客服(QA001)',{exact:true}).click(); await page.getByRole('button',{name:'从聊天整理知识',exact:true}).click(); const wizard=dialog('从聊天整理知识'); await wizard.waitFor(); await select(wizard,'整理方式','模型整理:脱敏后调用指定模型'); await select(wizard,'整理模型','alpha · openai(默认)'); await wizard.locator('.ant-form-item').filter({hasText:'单次等待时间(秒)'}).locator('input').fill('60'); await wizard.getByRole('button',{name:'预览整理范围',exact:true}).click(); await wizard.getByRole('checkbox',{name:'确认来源账号由客服使用,所选聊天允许用于知识整理。'}).check(); await page.screenshot({path:`${root}/01-model-selection.png`,fullPage:true}); await wizard.getByRole('button',{name:'确认创建任务',exact:true}).click(); await wizard.waitFor({state:'hidden'}); await page.getByText('所选范围已读取完成',{exact:true}).waitFor(); const jobs=await api('jobs'); const job=jobs.items[0]; if(job.status!=='completed' || job.created_items!==3 || job.model_calls!==3 || job.options.model_format_fallbacks!==2) throw new Error('Malformed response interrupted job or lost results'); await page.getByText('模型格式异常 2 条,已保留原文待审。',{exact:false}).waitFor(); await page.screenshot({path:`${root}/02-completed-with-format-warnings.png`,fullPage:true}); checks.push('缺失辅助字段、空答案和超长答案均未卡住任务;显示格式异常数量'); await page.getByRole('tab',{name:'知识与审核',exact:true}).click(); const data=await api('items'); const fallback=data.items.find(item=>item.flags.some(flag=>flag.includes('模型格式异常'))); if(!fallback || fallback.conditions) throw new Error('Fallback review gate missing'); await page.getByRole('button',{name:fallback.title,exact:true}).click(); const editor=dialog('知识审核与来源'); await editor.getByText('已核对客服身份、事实、适用条件和脱敏;内容可作为通用经验。涉及医疗内容已完成专业审核。',{exact:true}).click(); if(await editor.getByRole('button',{name:'审核通过',exact:true}).isEnabled()) throw new Error('Missing conditions can be approved'); if(!(await editor.innerText()).includes('不能直接发布')) throw new Error('Fallback warning not shown'); const detail=await api('items/'+fallback.id); if(detail.sources.length!==2) throw new Error('Original evidence lost'); await page.screenshot({path:`${root}/03-original-draft-review.png`,fullPage:true}); checks.push('异常输出保留原文和完整来源,明确警示,未补条件不能审核'); if(errors.length) throw new Error(JSON.stringify(errors)); writeFileSync(`${root}/result.json`,JSON.stringify({passed:true,checks,pageErrors:errors},null,2)); console.log(JSON.stringify({passed:true,checks,pageErrors:errors})); } catch(error) { await page.screenshot({path:`${root}/failure.png`,fullPage:true}); writeFileSync(`${root}/failure.txt`,String(error)+'\n'+await page.locator('body').innerText()); throw error; } finally { await browser.close(); }