import { test, expect } from '@playwright/test'; import { resolve } from 'node:path'; import { writeFile } from 'node:fs/promises'; import type {} from '../physics/agent'; for (const fault of [ 'empty-grasp', 'empty-once', 'alignment', 'slip', 'jam', 'navigation', 'unreachable', 'overspeed', ] as const) { test(`真实 WASM 故障:${fault} 不可冒充成功/无限恢复`, 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 r = await page.evaluate( (fault) => window.lekiwiAgent.task(0, undefined, { fault }), fault, ); await writeFile(info.outputPath('fault-evidence.json'), JSON.stringify(r, null, 2)); console.log('WASM fault', { fault, state: r.state, error: r.error, failures: r.failures, recoveries: r.recoveries, simTime: r.simTime, }); expect(r.mode).toBe('mock'); expect(r.state).toBe(fault === 'empty-once' ? 'succeeded' : 'failed'); expect(r.succeeded).toBe(fault === 'empty-once'); expect(r.simTime).toBeLessThanOrEqual(70.01); expect(r.llmCalls).toBe(1); if (fault === 'empty-grasp' || fault === 'empty-once' || fault === 'alignment') { const skill = fault === 'alignment' ? 'pregrasp' : 'close'; const code = fault === 'alignment' ? 'alignment_timeout' : 'empty_grasp'; const recovered = fault === 'empty-once'; expect(r.recoveries[skill]).toBe(recovered ? 1 : 2); expect(r.failures.map((f) => f.code)).toEqual(Array(recovered ? 1 : 3).fill(code)); if (!recovered) expect(r.trace.every((s) => !s.secure)).toBe(true); else { expect(r.transported).toBeGreaterThanOrEqual(0.5); expect(r.sample.onGoalSupport).toBe(true); } } else { expect(r.recoveries).toEqual({}); expect(r.failures).toHaveLength(1); expect(r.failures[0].code).toBe( { slip: 'lost_grasp', jam: 'joint_stall', navigation: 'navigation_timeout', unreachable: 'unreachable', overspeed: 'joint_velocity', }[fault], ); } if (fault === 'slip') { expect(Math.max(...r.trace.map((s) => s.transported))).toBeGreaterThan(0.12); expect(r.transported).toBe(0); } } finally { await page.evaluate(() => window.lekiwiAgent.dispose()); } }); } for (const goal of [ [0.237, 0.565, 0.128], [0.277, 0.665, 0.128], ]) { test(`真实 WASM 受限目标角点 ${goal.slice(0, 2).join(',')}(非任意目标泛化)`, 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 r = await page.evaluate( (goal) => window.lekiwiAgent.task(0, undefined, { goal }), goal, ); await writeFile(info.outputPath('goal-evidence.json'), JSON.stringify(r, null, 2)); expect(r.state, r.error).toBe('succeeded'); expect(r.goal).toEqual(goal); expect(r.succeeded).toBe(true); expect(r.transported).toBeGreaterThanOrEqual(0.5); expect(Math.hypot(...r.sample.object.map((v, i) => v - goal[i]))).toBeLessThan(0.04); } finally { await page.evaluate(() => window.lekiwiAgent.dispose()); } }); }