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 | 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[] = []; 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); });