import { expect, test } from '@playwright/test'; import { resolve } from 'node:path'; test.skip(process.env.CADWORLD_PRODUCTION_TEST !== '1', '真实站点非付费检查须显式启用'); test('生产 HTTPS:Pyodide 与精简语言界面,不触发推理', async ({ page }) => { let inference = 0; const errors: string[] = []; page.on('pageerror', (e) => errors.push(e.message)); page.on('request', (r) => { if (/\/api\/decision\/v1\/(command|plan|decide|test)$/.test(r.url())) inference++; }); expect((await page.goto('/'))?.status()).toBe(200); await page .locator('input[type=file]') .first() .setInputFiles(resolve('web_platform/fixtures/python_controller/model.xml')); await expect(page.getByRole('button', { name: '▶ 播放' })).toBeEnabled({ timeout: 90000 }); await page.getByRole('tab', { name: '控制台', exact: true }).click(); await expect(page.getByRole('button', { name: '强化学习任务', exact: true })).toHaveCount(0); await page.getByRole('button', { name: /Python 脚本控制/ }).click(); await page .locator('input[accept=".py,text/x-python"]') .setInputFiles(resolve('web_platform/fixtures/python_controller/balance.py')); await expect(page.getByText('倒立摆 PD 平衡控制', { exact: true })).toBeVisible({ timeout: 90000, }); await page.getByRole('button', { name: '启用', exact: true }).click(); await expect(page.getByRole('button', { name: /Python 脚本控制/ })).toContainText('运行'); await page.getByRole('button', { name: '机器人语言控制', exact: true }).click(); await expect(page.locator('input[type=password]')).toHaveCount(0); await expect(page.getByRole('textbox', { name: '机器人指令' })).toBeEditable(); await page.getByRole('button', { name: '执行指令', exact: true }).click(); await expect(page.getByText('请先导入并加载 LeKiwi v1 机器人', { exact: true })).toBeVisible(); expect(inference).toBe(0); expect(errors).toEqual([]); }); test('生产 v2:空工作台载入预设、文字目标及双相机,无付费推理', async ({ page }, info) => { let inference = 0; const errors: string[] = []; page.on('pageerror', (error) => errors.push(error.message)); await page.route('**/api/decision/v1/**', async (route) => { if (/\/(command|plan|decide|test)$/.test(new URL(route.request().url()).pathname)) { inference++; await route.abort(); } else await route.continue(); }); expect((await page.goto('/'))?.status()).toBe(200); await page.getByRole('tab', { name: '控制台', exact: true }).click(); await page.getByRole('button', { name: '机器人语言控制', exact: true }).click(); const panel = page.getByLabel('机器人语言控制', { exact: true }); await expect(panel.getByRole('combobox', { name: '预设场景' })).toBeVisible(); await expect(panel.locator('input[type=password], details')).toHaveCount(0); await panel.getByRole('button', { name: '载入预设场景' }).click(); await expect(panel.getByRole('status')).toContainText('预设已载入并暂停', { timeout: 120000 }); await expect(page.getByRole('button', { name: '▶ 播放' })).toBeEnabled(); await expect(page.getByRole('combobox', { name: '摄像头视角' }).locator('option')).toHaveCount(2); await panel.getByRole('textbox', { name: '机器人指令' }).fill('把方块搬到 (0.25,0.50) 米'); await expect(panel.getByRole('button', { name: '执行指令', exact: true })).toBeEnabled(); await panel.getByRole('textbox', { name: '机器人指令' }).fill('停止'); await panel.getByRole('button', { name: '执行指令', exact: true }).click(); await expect(panel.getByRole('status')).toContainText('用户停止'); expect(inference).toBe(0); expect(errors).toEqual([]); await page.screenshot({ path: info.outputPath('language-v2-production.png') }); }); test('生产默认配置受保护、订阅关闭、静态目录无密钥;退出销毁会话,无推理', async ({ page }) => { await page.goto('/'); const result = await page.evaluate(async () => { const prefix = '/api/decision/v1'; const headers = { 'Content-Type': 'application/json' }; const bootstrap = await fetch(prefix + '/session', { method: 'POST', headers, body: '{}' }); const session = await bootstrap.json(); const secured = { ...headers, 'X-CSRF-Token': session.csrfToken, 'X-Config-Version': String(session.configVersion), }; const config = await fetch(prefix + '/configuration', { method: 'PUT', headers: secured, body: '{}', }); const subscription = await fetch(prefix + '/codex/login', { method: 'POST', headers: secured, body: '{}', }); const env = await fetch('/.env'); const secret = await fetch('/run/secrets/model-keys.env'); const destroy = await fetch(prefix + '/session', { method: 'DELETE', headers: secured, body: '{}', }); const expired = await fetch(prefix + '/status'); return { ready: session.ready, llm: session.configuration.llm.model, keyTypes: [typeof session.configuration.llm.hasKey, typeof session.configuration.jev.hasKey], locked: config.status, subscription: subscription.status, env: env.status, secret: secret.status, destroyed: destroy.status, expired: expired.status, }; }); expect(result).toEqual({ ready: true, llm: 'deepseek-flash', keyTypes: ['boolean', 'boolean'], locked: 403, subscription: 403, env: 404, secret: 404, destroyed: 200, expired: 401, }); });