import { test, expect } from '@playwright/test'; import { resolve } from 'node:path'; import { writeFile } from 'node:fs/promises'; import preset from '../../contracts/lekiwi-pick-place-v1.json' with { type: 'json' }; import type {} from '../physics/agent'; for (const seed of preset.frozenSeeds) { test(`规则物理基线:冻结种子 ${seed.id},双指抓持/搬运/释放/撤离`, async ({ page }, info) => { const errors: string[] = []; page.on('pageerror', (error) => errors.push(error.message)); await page.goto('/physics/agent.html'); await page.waitForFunction(() => Boolean(window.lekiwiAgent)); await page.evaluate( (url) => window.lekiwiAgent.boot(url), `/@fs${resolve('build/lekiwi/lekiwi-v1.zip')}`, ); try { const result = await page.evaluate((seed) => window.lekiwiAgent.baseline(seed), seed.id); await writeFile(info.outputPath('physical-evidence.json'), JSON.stringify(result, null, 2)); console.log('baseline', { ...result, trace: `${result.trace.length} control samples` }); expect(result.status, result.error).toBe('succeeded'); expect(result.succeeded).toBe(true); expect(result.transported).toBeGreaterThanOrEqual(preset.transportMin); expect(result.maxJointSpeed).toBeLessThanOrEqual(preset.jointSpeedStop); const goal = preset.goal.position.map((v, i) => v + (seed.offset[i] ?? 0)); expect(Math.hypot(...result.final.object.map((v, i) => v - goal[i]))).toBeLessThan( preset.placementTolerance, ); expect(result.final.objectSpeed).toBeLessThan(preset.placementSpeedMax); expect(result.final.onGoalSupport).toBe(true); expect(result.final.fingerForces.every((force) => force < preset.fingerForceMin)).toBe(true); expect(result.final.opening).toBeGreaterThan(0.65); expect( Math.hypot(...result.final.object.map((v, i) => v - result.final.tcp[i])), ).toBeGreaterThanOrEqual(preset.retreatMin); expect( result.trace.filter((s) => s.phase === 'carry' && s.secure && !s.supported).length, ).toBeGreaterThan(100); expect(errors).toEqual([]); } finally { await page.evaluate(() => window.lekiwiAgent.dispose()); } }); } for (const seed of preset.frozenSeeds) { test(`mock 决策闭环:冻结种子 ${seed.id},逐技能门禁与独立物理成功`, async ({ page }, info) => { await page.goto('/physics/agent.html'); await page.waitForFunction(() => Boolean(window.lekiwiAgent)); await page.evaluate( (url) => window.lekiwiAgent.boot(url), `/@fs${resolve('build/lekiwi/lekiwi-v1.zip')}`, ); try { const result = await page.evaluate((id) => window.lekiwiAgent.task(id), seed.id); await writeFile(info.outputPath('mock-closed-loop.json'), JSON.stringify(result, null, 2)); console.log('mock closed loop', { state: result.state, error: result.error, transported: result.transported, llm: result.llmCalls, jev: result.jevCalls, simTime: result.simTime, }); expect(result.ownerAtStart).toBe('agent'); expect(result.mode).toBe('mock'); expect(result.state, result.error).toBe('succeeded'); expect(result.succeeded).toBe(true); expect(result.transported).toBeGreaterThanOrEqual(preset.transportMin); expect(result.maxJointSpeed).toBeLessThanOrEqual(preset.jointSpeedStop); expect(result.llmCalls).toBe(1); expect(result.jevCalls).toBe(11); expect(result.results).toHaveLength(11); expect(result.failures).toEqual([]); expect(result.sample.onGoalSupport).toBe(true); expect(result.sample.fingerForces.every((v) => v < preset.fingerForceMin)).toBe(true); expect(result.sample.opening).toBeGreaterThanOrEqual(0.65); expect( Math.hypot(...result.sample.object.map((v, i) => v - result.sample.tcp[i])), ).toBeGreaterThanOrEqual(preset.retreatMin); } finally { await page.evaluate(() => window.lekiwiAgent.dispose()); } }); } test('agent 控制权下同一物理时钟完成基线,不建立第二个环境循环', async ({ page }, info) => { await page.goto('/physics/agent.html'); await page.waitForFunction(() => Boolean(window.lekiwiAgent)); await page.evaluate( (url) => window.lekiwiAgent.boot(url), `/@fs${resolve('build/lekiwi/lekiwi-v1.zip')}`, ); try { const result = await page.evaluate(() => window.lekiwiAgent.baseline(0, true)); await writeFile(info.outputPath('governed-baseline.json'), JSON.stringify(result, null, 2)); expect(result.ownerAtStart).toBe('agent'); expect(result.status, result.error).toBe('succeeded'); expect(result.transported).toBeGreaterThanOrEqual(0.5); } finally { await page.evaluate(() => window.lekiwiAgent.dispose()); } });