diff --git a/e2e/work-checkpoints.playwright.ts b/e2e/work-checkpoints.playwright.ts index 81e0f2c..20ba4a4 100644 --- a/e2e/work-checkpoints.playwright.ts +++ b/e2e/work-checkpoints.playwright.ts @@ -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) +})