95 lines
7.1 KiB
JavaScript
95 lines
7.1 KiB
JavaScript
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/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:18867';
|
||
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.getByRole('button',{name:'选择账号开始整理',exact:true}).click();
|
||
await page.locator('.ant-select-dropdown:visible').getByText('流程验收客服(QA001)',{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();
|
||
let jobs=await api('jobs');
|
||
if(jobs.items[0].created_items!==1 || jobs.items[0].pending_items!==2) throw new Error('First successful candidate not durable');
|
||
if(jobs.items[0].options.model_provider_id!=='alpha' || jobs.items[0].options.model_timeout_seconds!==60) throw new Error('Model settings not saved');
|
||
checks.push('创建任务指定模型和超时;第二条失败时第一条草稿已保存');
|
||
await page.getByRole('button',{name:'配置 / 继续',exact:true}).click();
|
||
const config=dialog('调整配置并继续任务');
|
||
await select(config,'整理模型','beta · dify');
|
||
await config.locator('.ant-form-item').filter({hasText:'单次等待时间(秒)'}).locator('input').fill('120');
|
||
await page.screenshot({path:`${root}/02-retry-model.png`,fullPage:true});
|
||
await config.getByRole('button',{name:'保存并继续',exact:true}).click();
|
||
await config.waitFor({state:'hidden'});
|
||
await page.getByText('所选范围已读取完成',{exact:true}).waitFor();
|
||
jobs=await api('jobs');
|
||
if(jobs.items[0].created_items!==3 || jobs.items[0].model_calls!==4 || jobs.items[0].options.model_provider_id!=='beta') throw new Error('Resume repeated successful candidates');
|
||
checks.push('更换模型后从待处理问答继续,已完成草稿不重跑');
|
||
await page.getByRole('tab',{name:'知识与审核',exact:true}).click();
|
||
const data=await api('items'); const single=data.items[0];
|
||
await page.getByRole('button',{name:single.title,exact:true}).click();
|
||
const editor=dialog('知识审核与来源');
|
||
if(await editor.getByRole('button',{name:'删除已驳回知识',exact:true}).count()) throw new Error('Draft can delete before rejection');
|
||
await editor.getByRole('button',{name:/驳\s*回/,exact:true}).click();
|
||
await editor.getByRole('button',{name:'删除已驳回知识',exact:true}).click();
|
||
await page.getByRole('button',{name:'确认删除',exact:true}).click();
|
||
await editor.waitFor({state:'hidden'});
|
||
if((await api('items')).total!==2) throw new Error('Single delete failed');
|
||
checks.push('草稿不可删除,驳回后可确认删除单条');
|
||
const remaining=(await api('items')).items;
|
||
for(const item of remaining) await page.locator('tbody tr').filter({has:page.getByRole('button',{name:item.title,exact:true})}).getByRole('checkbox').check();
|
||
if(await page.getByRole('button',{name:'删除已驳回',exact:true}).isEnabled()) throw new Error('Batch draft deletion enabled');
|
||
await page.getByRole('button',{name:'驳回选中',exact:true}).click();
|
||
const reject=dialog('批量驳回 · 2 条');
|
||
await reject.getByRole('button',{name:'确认驳回 2 条',exact:true}).click();
|
||
await reject.getByText('成功 2 · 失败 0 · 未执行 0',{exact:true}).waitFor();
|
||
await reject.getByRole('button',{name:/完\s*成/}).click();
|
||
for(const item of remaining) 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 deletion=dialog('批量删除 · 2 条');
|
||
await deletion.getByText('可操作 2 条 · 需先处理 0 条',{exact:true}).waitFor();
|
||
await page.screenshot({path:`${root}/03-batch-delete.png`,fullPage:true});
|
||
await deletion.getByRole('button',{name:'确认删除 2 条',exact:true}).click();
|
||
await deletion.getByText('成功 2 · 失败 0 · 未执行 0',{exact:true}).waitFor();
|
||
await deletion.getByRole('button',{name:/完\s*成/}).click();
|
||
if((await api('items')).total!==0) throw new Error('Batch deletion failed');
|
||
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(); }
|