Cover progressive material outcomes in browser tests

Why: The r0.4 acceptance must show running findings, zero and partial failure, and a source-bound choice in the real UI.

Evidence: Six task-owned HTTP/Playwright cases passed; npm test (265 tests) and npm run build passed.

Impact: Browser test coverage only; no production UI change.

Refs: U3 r0.4 Task 6
This commit is contained in:
2026-10-14 03:34:23 +00:00
parent 006c815675
commit 34950997f8
+37 -4
View File
@@ -7,22 +7,24 @@ test.skip(!manifestPath,'Run explicitly with a task-owned mechanism instance man
const manifest=manifestPath?JSON.parse(readFileSync(manifestPath,'utf8')):null
if(manifest&&(manifest.mode!=='mechanism'||!manifest.dataDir.includes('fast-slow-u2-u3-')||readlinkSync(`/proc/${manifest.pid}/exe`)!==manifest.binary||createHash('sha256').update(readFileSync(manifest.binary)).digest('hex')!==manifest.binarySHA256))throw new Error('Owned mechanism instance verification failed')
async function setup(request:any){
type Scenario='normal'|'slow-stages'|'zero-failure'|'partial-failure'|'choice'
async function setup(request:any,scenario:Scenario='normal',awaitTask=true){
const project=await(await request.post(manifest.base+'/api/projects',{data:{name:'browser-'+Date.now()}})).json()
const conv=await(await request.post(manifest.base+`/api/projects/${project.id}/conversations`,{data:{title:'材料整理',profileId:'checkpoint-eval'}})).json()
const ids:string[]=[]
for(let i=0;i<3;i++){
const res=await request.post(manifest.base+`/api/projects/${project.id}/uploads`,{multipart:{file:{name:`来源${i+1}.txt`,mimeType:'text/plain',buffer:Buffer.from('上线六月。验收条件需要补齐,预算50万元尚未审批。交付包含12项需求,其中3项依赖外部接口。第一期先完成8项核心需求。\n\n'+('整理记录说明:保留日期、金额、限制和待确认事项。\n\n').repeat(100))}}})
const marker=i===0&&scenario!=='normal'?`[fixture:${scenario}]\n`:''
const res=await request.post(manifest.base+`/api/projects/${project.id}/uploads`,{multipart:{file:{name:`来源${i+1}.txt`,mimeType:'text/plain',buffer:Buffer.from(marker+'上线六月。验收条件需要补齐,预算50万元尚未审批。交付包含12项需求,其中3项依赖外部接口。第一期先完成8项核心需求。\n\n'+('整理记录说明:保留日期、金额、限制和待确认事项。\n\n').repeat(100))}}})
expect(res.ok()).toBeTruthy();ids.push((await res.json()).artifactId)
}
const sent=await request.post(manifest.base+`/api/conversations/${conv.id}/messages`,{data:{text:'',attachmentIds:ids,requestId:'browser-'+Date.now()}})
expect(sent.ok()).toBeTruthy()
const sendBody=await sent.json()
await expect.poll(async()=> (await(await request.get(manifest.base+`/api/runs/${sendBody.run.id}`)).json()).run.status).toBe('completed')
await expect.poll(async()=>{
if(awaitTask)await expect.poll(async()=>{
const out=await(await request.get(manifest.base+`/api/conversations/${conv.id}/cards`)).json()
return out.cards.find((c:any)=>c.task)?.task.executionState
}).toBe('completed')
}).toBe(scenario.endsWith('failure')?'failed':'completed')
return {project,conv,ids}
}
async function open(page:any,seed:any){
@@ -61,3 +63,34 @@ test('background replay does not steal the current file, focus or viewport',asyn
await expect(composer).toBeFocused();await expect(composer).toHaveValue('正在记录自己的想法')
expect(await page.locator('.tab-strip').textContent()).toBe(tabs)
})
test('two sourced stages appear while the material task is still running',async({page,request})=>{
const seed=await setup(request,'slow-stages',false)
await open(page,seed)
const cards=async()=>(await(await request.get(manifest.base+`/api/conversations/${seed.conv.id}/cards`)).json()).cards
await expect.poll(async()=>{const card=(await cards()).find((c:any)=>c.task);return card?.task.executionState}).toBe('running')
await expect(page.locator('[data-conversation-work]')).toContainText('上线六月')
await expect.poll(async()=>{const card=(await cards()).find((c:any)=>c.task);return card?.task.findings?.length}).toBeGreaterThan(1)
await expect(page.locator('[data-conversation-work]')).toContainText('验收条件需要补齐')
await expect.poll(async()=>{const card=(await cards()).find((c:any)=>c.task);return card?.task.executionState}).toBe('completed')
})
for(const scenario of ['zero-failure','partial-failure'] as const){
test(`${scenario} shows the saved scope without a deliverable choice`,async({page,request})=>{
const seed=await setup(request,scenario)
await page.addInitScript(({id}:{id:string})=>{localStorage.setItem('as.conv',id);localStorage.setItem('as.ui',JSON.stringify({theme:'light',rightCollapsed:true,fontSize:14}))},{id:seed.conv.id})
await page.goto('/')
const work=page.locator('[data-conversation-work]')
await expect(work).toContainText('执行失败')
if(scenario==='zero-failure')await expect(work).toContainText('暂无阶段结论记录')
else await expect(work).toContainText('上线六月')
await expect(page.locator('.hitl-choice')).toHaveCount(0)
})
}
test('completed sourced work offers only its grounded suggestions',async({page,request})=>{
const seed=await setup(request,'choice')
await open(page,seed)
await expect(page.locator('.hitl-choice')).toHaveCount(3)
for(const label of ['会议摘要','验收清单','联调计划'])await expect(page.locator('.hitl-choice').filter({hasText:label})).toHaveCount(1)
await expect(page.locator('.hitl-card')).toContainText('基于整理结果,接下来生成什么?')
await page.reload()
await expect(page.locator('.hitl-choice')).toHaveCount(3)
})