import { expect, test } from '@playwright/test'; import { startBridge } from './fixtures/controlBridge'; import type {} from '../physics/runner'; test('通用单关节:真实 Python SDK ↔ 本机桥接 ↔ 浏览器 WASM,断开后安全保持', async ({ page }) => { const bridge = await startBridge(); try { await page.goto('http://127.0.0.1:4174/physics/runner.html'); await page.waitForFunction(() => Boolean(window.lekiwiPhysics)); await page.evaluate(() => window.lekiwiPhysics.bootSingle()); await page.evaluate( ({ endpoint, token }) => window.lekiwiPhysics.connectBridge(endpoint, token), { endpoint: bridge.endpoint, token: bridge.token }, ); const run = await bridge.python(` import json, os, time from mujoco_control_bridge import SimRobotClient with SimRobotClient(os.environ['MUJOCO_CONTROL_ENDPOINT']) as robot: start=time.monotonic() for i in range(60): accepted=robot.send_action({'slider.position': .5}) obs=robot.get_observation() time.sleep(max(0,start+(i+1)/30-time.monotonic())) print(json.dumps({'accepted':accepted['values'], 'measured':obs['values'], 'seq':obs['appliedActionSeq']})) `); const result = JSON.parse(run.stdout.trim()) as { accepted: Record; measured: Record; seq: number; }; expect(result.accepted['slider.position']).toBe(0.5); expect(result.measured['slider.position']).toBeGreaterThan(0.48); expect(result.measured['slider.position']).toBeLessThan(0.51); expect(result.seq).toBeGreaterThan(55); await expect .poll( async () => (await page.evaluate(() => window.lekiwiPhysics.externalState())).observation?.paused, ) .toBe(true); expect(bridge.errors()).toBe(''); } finally { await page.evaluate(() => window.lekiwiPhysics?.dispose()).catch(() => {}); await bridge.stop(); } });