diff --git a/e2e/work-checkpoints.playwright.ts b/e2e/work-checkpoints.playwright.ts index a3c057f..513466c 100644 --- a/e2e/work-checkpoints.playwright.ts +++ b/e2e/work-checkpoints.playwright.ts @@ -7,17 +7,17 @@ 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') -type Scenario='normal'|'slow-stages'|'zero-failure'|'partial-failure'|'choice' -async function setup(request:any,scenario:Scenario='normal',awaitTask=true){ +type Scenario='normal'|'fast-ack'|'slow-stages'|'zero-failure'|'partial-failure'|'choice' +async function setup(request:any,scenario:Scenario='normal',awaitTask=true,options:{text?:string,small?:boolean}={}){ 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++){ + for(let i=0;i<(options.small?1:3);i++){ 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))}}}) + 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'+(options.small?'':('整理记录说明:保留日期、金额、限制和待确认事项。\n\n').repeat(options.text?250: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()}}) + const sent=await request.post(manifest.base+`/api/conversations/${conv.id}/messages`,{data:{text:options.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') @@ -46,6 +46,55 @@ async function open(page:any,seed:any){ await page.goto('/') await expect(page.locator('[data-conversation-work]')).toContainText('上线六月') } +test('attachment-only intake gives a safe first reply before sourced findings',async({page,request})=>{ + const seed=await setup(request,'fast-ack',false) + const cards=(await(await request.get(manifest.base+`/api/conversations/${seed.conv.id}/cards`)).json()).cards + expect(cards.filter((c:any)=>c.task)).toHaveLength(1) + expect(cards.find((c:any)=>c.task).task.checkpointSeq??0).toBe(0) + await open(page,seed) + const reply=page.getByText(/已收到 3 份材料.*待确认/) + await expect(reply).toBeVisible() + await expect(page.getByText(/剩余\s*4\s*项.*含\s*3\s*项依赖接口/)).toHaveCount(0) + await page.setViewportSize({width:390,height:844}) + await expect(reply).toBeVisible() + if(process.env.WORK_CHECKPOINT_SCREENSHOT)await page.screenshot({path:process.env.WORK_CHECKPOINT_SCREENSHOT.replace(/\.png$/, '-mobile.png'),fullPage:true}) + await page.reload() + await expect(reply).toBeVisible() +}) +test('text with long material keeps the user focus in the host reply',async({page,request})=>{ + const seed=await setup(request,'fast-ack',false,{text:'重点核对预算审批'}) + await open(page,seed) + await expect(page.getByText(/你提出的工作重点已带入整理任务.*待确认/)).toBeVisible() + const cards=(await(await request.get(manifest.base+`/api/conversations/${seed.conv.id}/cards`)).json()).cards + expect(cards.filter((c:any)=>c.task)).toHaveLength(1) +}) +test('running and completed resends reuse one material card',async({page,request})=>{ + const seed=await setup(request,'slow-stages',false) + const resend=async(key:string)=>{ + const response=await request.post(manifest.base+`/api/conversations/${seed.conv.id}/messages`,{data:{text:'',attachmentIds:seed.ids,requestId:key}}) + expect(response.ok()).toBeTruthy() + const body=await response.json() + await expect.poll(async()=> (await(await request.get(manifest.base+`/api/runs/${body.run.id}`)).json()).run.status).toBe('completed') + } + await resend('active-resend-'+Date.now()) + await open(page,seed) + await expect(page.getByText(/这批材料仍在整理.*待确认/)).toBeVisible() + await expect.poll(async()=>{ + const cards=(await(await request.get(manifest.base+`/api/conversations/${seed.conv.id}/cards`)).json()).cards + return cards.find((c:any)=>c.task)?.task.executionState + }).toBe('completed') + await resend('completed-resend-'+Date.now()) + await expect(page.getByText(/这批材料已整理完成.*待确认/)).toBeVisible() + const cards=(await(await request.get(manifest.base+`/api/conversations/${seed.conv.id}/cards`)).json()).cards + expect(cards.filter((c:any)=>c.task)).toHaveLength(1) +}) +test('text with a small attachment retains the ordinary reply path',async({page,request})=>{ + const seed=await setup(request,'normal',true,{text:'请先解释材料要点',small:true}) + await open(page,seed) + await expect(page.getByText('已收到材料,正在后台整理;请结合阶段发现继续工作。').first()).toBeVisible() + const cards=(await(await request.get(manifest.base+`/api/conversations/${seed.conv.id}/cards`)).json()).cards + expect(cards.filter((c:any)=>c.task)).toHaveLength(1) +}) test('real HTTP task recovery, source navigation and changed-version limitation',async({page,request})=>{ const seed=await setup(request);const errors:string[]=[];page.on('pageerror',e=>errors.push(e.message));await open(page,seed) await expect(page.locator('[data-conversation-work]')).toContainText('验收条件需要补齐')