import {test,expect,type Page} from '@playwright/test' import {mkdirSync} from 'node:fs' const evidence='docs/verification/2026-09-10-bot-panels' async function create(page:Page,name:string){ await page.locator('.desktop-sidebar .sidebar-create-trigger').click() await page.getByRole('menuitem',{name:'新建 Bot',exact:true}).click() const editor=page.getByRole('dialog');await editor.getByRole('textbox',{name:'名称',exact:true}).fill(name) await editor.getByRole('button',{name:'保存',exact:true}).click() await expect(page.getByRole('heading',{name,exact:true})).toBeVisible() } test('inspector latest records stay at the top on desktop and phone',async({page},testInfo)=>{ await page.goto('/conversations') await page.getByRole('textbox',{name:'账号',exact:true}).fill('fixture') await page.getByRole('textbox',{name:'密码',exact:true}).fill('fixture-pass') await page.getByRole('button',{name:'登录',exact:true}).click() await expect(page.locator('.desktop-sidebar .sidebar-create-trigger')).toBeVisible() await create(page,'排序验收') let revision=0 await page.route('**/api/app/conversations/*/inspector',route=>{ const entries=Array.from({length:90},(_,index)=>['event','response'].map(direction=>({id:`${direction}-${index+revision}`,at:1726200000000+(index+revision)*1000,direction,method:direction==='event'?'message.complete':'session.usage',agentId:'agent',runId:'run',data:{text:`记录 ${index+revision}`}}))).flat() // A later request batch can arrive before older execution records. return route.fulfill({json:{entries:[...entries.slice(120),...entries.slice(0,120)]}}) }) await page.getByRole('button',{name:'Inspector',exact:true}).click() const panel=page.locator('.inspector'),list=panel.locator('.inspector-list') await expect(panel.locator('summary').first()).toContainText('记录 89') expect(await list.evaluate(el=>el.scrollTop)).toBe(0) const times=await panel.locator('pre').allTextContents() expect(times.map(text=>JSON.parse(text).at)).toEqual(times.map(text=>JSON.parse(text).at).sort((a,b)=>b-a)) await list.evaluate(el=>{el.scrollTop=500;el.dispatchEvent(new Event('scroll'))}) await panel.getByRole('button',{name:'Raw',exact:true}).click() await expect.poll(()=>list.evaluate(el=>el.scrollTop)).toBe(0) expect(JSON.parse(await panel.locator('pre').first().textContent()||'{}').id).toBe('response-89') revision=1 await panel.getByRole('button',{name:'刷新请求记录',exact:true}).click() await expect.poll(async()=>JSON.parse(await panel.locator('pre').first().textContent()||'{}').id).toBe('response-90') expect(await list.evaluate(el=>el.scrollTop)).toBe(0) await page.screenshot({path:testInfo.outputPath('inspector-desktop.png')}) await page.setViewportSize({width:375,height:812});await page.emulateMedia({colorScheme:'dark',reducedMotion:'reduce'}) await panel.getByRole('button',{name:'Events',exact:true}).click() await expect(panel.locator('summary').first()).toBeInViewport() await expect(panel.locator('summary').first()).toContainText('记录 90') expect(await list.evaluate(el=>el.scrollTop)).toBe(0) await page.screenshot({path:testInfo.outputPath('inspector-phone.png')}) }) test('robot sidebar, source changes, requests, routines and group boundaries work on desktop and phone',async({page})=>{ mkdirSync(evidence,{recursive:true}) await page.goto('/conversations') await page.getByRole('textbox',{name:'账号',exact:true}).fill('fixture') await page.getByRole('textbox',{name:'密码',exact:true}).fill('fixture-pass') await page.getByRole('button',{name:'登录',exact:true}).click() await expect(page.locator('.desktop-sidebar .sidebar-create-trigger')).toBeVisible() await create(page,'侧栏验收') await page.getByRole('button',{name:'聊天设置',exact:true}).click() const editor=page.getByRole('dialog') await editor.getByLabel('基础机器人',{exact:false}).selectOption(JSON.stringify(['local','server'])) await editor.getByRole('button',{name:'保存',exact:true}).click() await expect(editor).not.toBeVisible() const agents=(await(await page.request.get('/api/app/agents')).json()).agents const first=agents.find((a:any)=>a.name==='侧栏验收');expect(first.profile).toBe('server') const composer=page.getByRole('textbox',{name:'输入消息,Enter 发送,Shift + Enter 换行'}) await composer.fill('侧栏真实请求验收 '+Date.now()) await page.getByRole('button',{name:'发送消息',exact:true}).click() await expect(page.locator('.message--assistant')).toContainText('会话独立保存') await page.getByRole('button',{name:'电脑与定时任务',exact:true}).click() const panel=page.getByRole('complementary',{name:'机器人右侧栏'}) await expect(panel.getByRole('tab',{name:'自动',exact:true})).toBeVisible() await expect(panel.getByRole('tab',{name:'云端 · Grok Bot',exact:true})).toBeVisible() await expect(panel.getByRole('button',{name:'电脑',exact:true})).toHaveAttribute('aria-pressed','true') await expect(panel.locator(':scope > header .tabs')).toHaveCSS('justify-content','center') await page.screenshot({path:evidence+'/desktop-computer.png'}) await page.getByRole('button',{name:'Inspector',exact:true}).click() await expect(panel.locator(':scope > header')).toHaveCount(0) await panel.getByRole('button',{name:'Raw',exact:true}).click() await expect(panel.locator('summary').filter({hasText:'prompt.submit'}).first()).toBeVisible() await page.screenshot({path:evidence+'/desktop-inspector.png'}) await page.getByRole('button',{name:'电脑与定时任务',exact:true}).click() await panel.getByRole('button',{name:'定时任务',exact:true}).click() await panel.getByRole('button',{name:'新建定时任务',exact:true}).click() const routineEditor=page.getByRole('dialog',{name:'新建定时任务',exact:true}) await routineEditor.getByRole('textbox',{name:'名称',exact:true}).fill('每小时核对') await routineEditor.getByRole('textbox',{name:'任务内容',exact:true}).fill('核对项目状态并给出结果') await routineEditor.getByLabel('重复',{exact:true}).selectOption('interval') await routineEditor.getByRole('button',{name:'保存',exact:true}).click() await expect(panel.getByRole('heading',{name:'每小时核对',exact:true})).toBeVisible() await panel.getByRole('heading',{name:'每小时核对',exact:true}).click() await page.getByRole('dialog',{name:'编辑定时任务',exact:true}).getByRole('button',{name:'立即执行',exact:true}).click() await expect(panel.locator('.run-state')).toContainText('已完成',{timeout:15000}) await page.screenshot({path:evidence+'/desktop-routine-logs.png'}) await panel.getByRole('button',{name:'返回定时任务列表',exact:true}).click() await page.screenshot({path:evidence+'/desktop-routines.png'}) await panel.getByRole('button',{name:'关闭机器人侧栏',exact:true}).click() await create(page,'群聊成员') const all=(await(await page.request.get('/api/app/agents')).json()).agents const second=all.find((a:any)=>a.name==='群聊成员') const capabilities=await(await page.request.get('/api/realtime/capabilities')).json() const response=await page.request.post('/api/app/conversations',{headers:{'X-CSRF-Token':capabilities.csrfToken,Origin:'http://127.0.0.1:18832'},data:{name:'群聊侧栏验收',memberIds:[first.id,second.id],administratorId:first.id}}) expect(response.ok(),await response.text()).toBe(true) const group=(await response.json()).conversation await page.goto('/conversations/'+group.id) await expect(page.getByRole('button',{name:'电脑与定时任务',exact:true})).toHaveCount(0) const taskPicker=page.getByRole('combobox',{name:'当前话题',exact:true}) const optionCount=await taskPicker.locator('option').count() await taskPicker.selectOption('__create_task__') await expect(page).toHaveURL(/taskId=/) await expect(page.getByRole('combobox',{name:'当前话题',exact:true}).locator('option')).toHaveCount(optionCount+1) await page.screenshot({path:evidence+'/desktop-group-task-picker.png'}) for(let index=1;index<=12;index++){ const created=await page.request.post('/api/app/agents',{headers:{'X-CSRF-Token':capabilities.csrfToken,Origin:'http://127.0.0.1:18832'},data:{name:`滚动验收 ${index}`,profile:'default'}}) expect(created.ok(),await created.text()).toBe(true) } await page.reload() const agentList=page.locator('.desktop-sidebar .sidebar-list') await expect.poll(()=>agentList.evaluate(element=>element.scrollHeight-element.clientHeight)).toBeGreaterThan(0) await agentList.evaluate(element=>{element.scrollTop=element.scrollHeight}) await expect.poll(()=>agentList.evaluate(element=>element.scrollTop)).toBeGreaterThan(0) await expect(agentList).toHaveCSS('overflow-y','auto') expect(await agentList.evaluate(element=>getComputedStyle(element,'::-webkit-scrollbar-thumb').backgroundColor)).not.toBe('rgba(0, 0, 0, 0)') await agentList.hover() await page.screenshot({path:evidence+'/desktop-agent-list-scroll.png'}) await page.getByRole('button',{name:'Inspector',exact:true}).click() await expect(panel.locator(':scope > header')).toHaveCount(0) await expect(panel.getByRole('button',{name:'电脑',exact:true})).toHaveCount(0) await expect(panel.getByRole('button',{name:'定时任务',exact:true})).toHaveCount(0) await page.setViewportSize({width:375,height:812});await page.emulateMedia({colorScheme:'dark',reducedMotion:'reduce'}) await page.screenshot({path:evidence+'/phone-group-inspector.png'}) const box=await panel.boundingBox();expect(box!.width).toBeLessThanOrEqual(375) await expect(panel.getByRole('button',{name:'关闭 Inspector',exact:true})).toBeVisible() })