Files
Mujoco_WASM/web_platform/e2e/website.live.spec.ts
T
chenlin 7519309a27
web-platform-ci / Standalone decision service (no cloud credentials) (push) Has been cancelled
web-platform-ci / TypeScript, lint, unit, build (push) Has been cancelled
web-platform-ci / Playwright E2E (push) Has been cancelled
lekiwi-compatibility / cpu-compatibility (push) Has been cancelled
fix: release v1.0.3 预设渲染与语言搬运监督修复
2026-09-24 17:02:10 +08:00

176 lines
6.5 KiB
TypeScript

import { expect, test } from '@playwright/test';
import { writeFile } from 'node:fs/promises';
import { collectRobotResults, loadWebsiteRobot, runWebsiteCommand } from './website.helpers';
test('显式授权:公网内置模型,前进/转向/抓放,无手填密钥或自动重试', async ({ page }, info) => {
test.skip(
process.env.CADWORLD_LIVE_API !== '1' || process.env.CADWORLD_LIVE_PRESET === '1',
'仅经用户授权后运行;预设专项测试不重复执行运动测试',
);
test.setTimeout(360000);
const calls = { command: 0, plan: 0, decide: 0, blocked: 0 };
let csrf = '',
cleanup = false;
await page.route('**/api/decision/v1/**', async (route) => {
const endpoint = new URL(route.request().url()).pathname.split('/').at(-1);
if (endpoint === 'command' || endpoint === 'plan' || endpoint === 'decide') {
const limit = { command: 3, plan: 1, decide: 13 };
if (calls[endpoint] >= limit[endpoint]) {
calls.blocked++;
await route.abort();
return;
}
calls[endpoint]++;
} else if (endpoint === 'test') {
calls.blocked++;
await route.abort();
return;
}
await route.continue();
});
let capture: Promise<void> | undefined;
const captureSession = () =>
page
.waitForResponse((r) => r.url().endsWith('/session') && r.request().method() === 'POST')
.then(async (r) => {
csrf = (await r.json()).csrfToken;
})
.catch(() => undefined);
// Observe capture on all outcomes; no pre-existing keys/Cookies or storage state used.
try {
await loadWebsiteRobot(page);
capture = captureSession();
const move = await runWebsiteCommand(page, '前进0.5米', info, 'live-forward');
expect(move.distance).toBeGreaterThan(0.49);
expect(move.distance).toBeLessThan(0.51);
const turn = await runWebsiteCommand(page, '左转90度', info, 'live-turn');
expect(turn.turnDegrees).toBeGreaterThan(89);
expect(turn.turnDegrees).toBeLessThan(91);
expect(turn.start.x).toBeCloseTo(move.sample.x, 2);
const panel = page.getByLabel('机器人语言控制', { exact: true });
await panel.getByRole('button', { name: '载入预设场景' }).click();
await expect(panel.getByRole('status')).toContainText('预设已载入', { timeout: 90000 });
const grasp = await runWebsiteCommand(page, '把方块搬到 B 区', info, 'live-pick-place');
expect(grasp.transported).toBeGreaterThanOrEqual(0.5);
expect(grasp.sample.onGoalSupport).toBe(true);
expect(calls).toEqual({ command: 3, plan: 1, decide: 13, blocked: 0 });
await page.screenshot({ path: info.outputPath('hosted-control.png') });
} finally {
await capture;
if (csrf)
cleanup = await page
.evaluate(
async (token) =>
(
await fetch('/api/decision/v1/session', {
method: 'DELETE',
headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': token },
body: '{}',
})
).status === 200,
csrf,
)
.catch(() => false);
await page.close();
await writeFile(
info.outputPath('hosted-live-summary.json'),
JSON.stringify(
{
calls,
sessionDestroyed: cleanup,
maximumUpstreamCalls: { llm: 4, jev: 16 },
retries: 0,
credentialInput: false,
},
null,
2,
),
);
}
expect(cleanup).toBe(true);
});
test('显式授权:空工作台默认预设指令完成真实模型抓放', async ({ page }, info) => {
test.skip(
process.env.CADWORLD_LIVE_API !== '1' || process.env.CADWORLD_LIVE_PRESET !== '1',
'仅在显式选择预设专项实测时调用付费 API',
);
test.setTimeout(360000);
const calls = { command: 0, plan: 0, decide: 0, blocked: 0 };
let csrf = '',
cleanup = false;
const captures: Promise<void>[] = [];
page.on('response', (response) => {
if (response.url().endsWith('/session') && response.request().method() === 'POST')
captures.push(
response.json().then((value) => {
csrf = value.csrfToken;
}),
);
});
await page.route('**/api/decision/v1/**', async (route) => {
const endpoint = new URL(route.request().url()).pathname.split('/').at(-1);
if (endpoint === 'command' || endpoint === 'plan' || endpoint === 'decide') {
if (calls[endpoint] >= { command: 1, plan: 1, decide: 13 }[endpoint]) {
calls.blocked++;
await route.abort();
return;
}
calls[endpoint]++;
} else if (endpoint === 'test') {
calls.blocked++;
await route.abort();
return;
}
await route.continue();
});
try {
await collectRobotResults(page);
await page.goto('/');
await page.getByRole('tab', { name: '控制台', exact: true }).click();
await page.getByRole('button', { name: '机器人语言控制', exact: true }).click();
const panel = page.getByLabel('机器人语言控制', { exact: true });
await panel.getByRole('button', { name: '载入预设场景' }).click();
await expect(panel.getByRole('status')).toContainText('预设已载入并暂停', { timeout: 120000 });
await expect(panel.getByRole('textbox', { name: '机器人指令' })).toHaveValue('把方块搬到 B 区');
const result = await runWebsiteCommand(page, '把方块搬到 B 区', info, 'live-default-preset');
expect(result.goal).toEqual([0.257, 0.75, 0.128]);
expect(result.transported).toBeGreaterThan(0.7);
expect(result.sample.onGoalSupport).toBe(true);
expect(result.llmCalls).toBe(2);
expect(result.jevCalls).toBe(14);
expect(calls).toEqual({ command: 1, plan: 1, decide: 13, blocked: 0 });
await page.screenshot({ path: info.outputPath('live-default-preset.png') });
} finally {
await Promise.all(captures);
if (csrf)
cleanup = await page
.evaluate(
async (token) =>
(
await fetch('/api/decision/v1/session', {
method: 'DELETE',
headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': token },
body: '{}',
})
).status === 200,
csrf,
)
.catch(() => false);
await writeFile(
info.outputPath('live-default-summary.json'),
JSON.stringify(
{
calls,
sessionDestroyed: cleanup,
maximumUpstreamCalls: { llm: 2, jev: 14 },
retries: 0,
},
null,
2,
),
);
}
expect(cleanup).toBe(true);
});