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-v2-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(15000); const errors=[]; page.on('pageerror',error=>errors.push(error.message)); const login=await fetch('http://127.0.0.1:18866/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,method='GET',body)=>{ const response=await fetch(`http://127.0.0.1:18866/api/v2/knowledge/${path}?account_id=${fixture.account_id}`,{method,headers:{Authorization:`Bearer ${login.access_token}`,'Content-Type':'application/json'},body:body?JSON.stringify(body):undefined}); if (!response.ok) throw new Error(`API ${path}: ${response.status}`); return response.json(); }; const checks=[]; try { await page.goto('http://127.0.0.1:18866/#/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('http://127.0.0.1:18866/#/knowledge/center'); await page.getByRole('button',{name:'选择账号开始整理',exact:true}).waitFor(); await page.screenshot({path:`${root}/01-account-guide.png`,fullPage:true}); await page.getByRole('button',{name:'选择账号开始整理',exact:true}).click(); await page.locator('.ant-select-dropdown:visible').getByText('流程验收客服(QA001)',{exact:true}).click(); const wizard=page.getByRole('dialog').filter({has:page.getByText('从聊天整理知识',{exact:true})}); await wizard.waitFor(); await wizard.getByRole('button',{name:'预览整理范围',exact:true}).click(); await wizard.getByText('范围内单聊消息',{exact:false}).waitFor(); const previewText=await wizard.innerText(); if (!previewText.includes('身份明确的有效文本')) throw new Error('Preview not rendered'); await page.screenshot({path:`${root}/02-job-preview.png`,fullPage:true}); checks.push('账号选择引导、来源自动选择、范围预览'); await wizard.getByRole('checkbox',{name:'确认来源账号由客服使用,所选聊天允许用于知识整理。'}).check(); await wizard.getByRole('button',{name:'确认创建任务',exact:true}).click(); await wizard.waitFor({state:'hidden'}); await page.getByText('所选范围已读取完成',{exact:true}).waitFor(); await page.screenshot({path:`${root}/03-job-progress.png`,fullPage:true}); checks.push('创建后台任务、真实分母和完成原因'); await page.getByRole('tab',{name:'知识与审核',exact:true}).click(); const data=await api('items'); if (data.items.length!==3) throw new Error(`Expected 3 drafts, got ${data.items.length}`); const chosen=data.items.slice(0,2); for (const item of chosen) { await page.getByRole('button',{name:item.title,exact:true}).click(); const editor=page.getByRole('dialog').filter({has:page.getByText('知识审核与来源',{exact:true})}); await editor.locator('.ant-form-item').filter({hasText:'适用条件(必填)'}).locator('textarea').fill('适用于一般门店预约咨询;具体安排以客服当天核实为准。'); const saved=page.waitForResponse(response=>response.url().includes(`/knowledge/items/${item.id}`)&&response.request().method()==='PUT'&&response.status()===200); await editor.getByRole('button',{name:'保存草稿',exact:true}).click(); await saved; await editor.locator('.ant-modal-close').click(); await editor.waitFor({state:'hidden'}); } for (const item of chosen) await page.locator('tbody tr').filter({has:page.getByRole('button',{name:item.title,exact:true})}).getByRole('checkbox').check(); await page.getByRole('button',{name:'批量审核',exact:true}).click(); const review=page.getByRole('dialog').filter({has:page.getByText('批量审核通过 · 2 条',{exact:true})}); await review.getByText('可操作 2 条 · 需先处理 0 条',{exact:true}).waitFor(); if (await review.getByRole('button',{name:'确认审核通过 2 条',exact:true}).isEnabled()) throw new Error('Review skipped per-item confirmation'); for (const item of chosen) { await review.locator('.ant-collapse-header').filter({hasText:item.title}).click(); } const boxes=review.getByRole('checkbox'); for (let index=0;index