import { expect, test } from '@playwright/test'; import { resolve } from 'node:path'; import { existsSync } from 'node:fs'; import { readFile, writeFile } from 'node:fs/promises'; test.skip( process.env.CADWORLD_PRODUCTION_TEST !== '1', '真实站点检查须显式 opt-in;不使用任何现存密钥', ); test('生产 HTTPS:模型导入、Pyodide、精简设置、安全会话与订阅不可用边界', async ({ page, }, info) => { const errors: string[] = []; const external: string[] = []; page.on('pageerror', (e) => errors.push(e.message)); page.on('request', (r) => { if ( r.url().startsWith('http') && new URL(r.url()).origin !== 'https://cadworld-sim.robotquan.com' ) external.push(r.url()); }); const response = await page.goto('/'); expect(response?.status()).toBe(200); await expect(page).toHaveTitle(/CADWorld Sim/); 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: 'LeKiwi 智能抓放', exact: true }).click(); await page.getByText('模型设置 · API_KEY / ChatGPT 订阅', { exact: true }).click(); await expect(page.getByRole('button', { name: '保存设置' })).toBeEnabled({ timeout: 20000 }); await expect(page.getByLabel('本机服务令牌')).toHaveCount(0); const cookie = (await page.context().cookies()).find((c) => c.name === '__Host-cadworld-session'); expect(cookie?.secure).toBe(true); expect(cookie?.httpOnly).toBe(true); expect(cookie?.sameSite).toBe('Strict'); await page.screenshot({ path: info.outputPath('website-api-settings.png') }); await page.getByRole('button', { name: 'ChatGPT 订阅', exact: true }).click(); // No login or model invocation. Availability must be explicit; user's login stays interactive. await expect(page.getByText(/当前服务器可能无法连接官方服务/)).toBeVisible(); await expect(page.getByRole('button', { name: '登录 ChatGPT 订阅', exact: true })).toBeVisible(); await page.screenshot({ path: info.outputPath('website-subscription-entry.png') }); await page.getByText('连接测试与凭据管理', { exact: true }).click(); await page.getByRole('button', { name: '清除本次凭据', exact: true }).click(); await expect(page.getByText(/本次凭据已清除/)).toBeVisible(); expect(errors).toEqual([]); expect(external).toEqual([]); }); test('官方订阅入口网络探测:不登录账号、不推理,完成后销毁会话', async ({ page }, info) => { await page.goto('/'); const evidence = 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 protectedHeaders = { ...headers, 'X-CSRF-Token': session.csrfToken, 'X-Config-Version': String(session.configVersion), }; const response = await fetch(prefix + '/codex/login', { method: 'POST', headers: protectedHeaders, body: '{}', }); const result = await response.json(); const cleared = await fetch(prefix + '/session', { method: 'DELETE', headers: protectedHeaders, body: '{}', }); return { status: response.status, error: typeof result.error === 'string' ? result.error : undefined, deviceFlowOffered: Boolean(result.verificationUrl && result.userCode), cleared: cleared.status, accountLoginPerformed: false, inferenceCalls: 0, }; }); expect(evidence.cleared).toBe(200); expect([200, 409, 429, 502, 504]).toContain(evidence.status); if (evidence.status !== 200) expect(evidence.error).toMatch(/^[a-z0-9_]+$/); await writeFile( info.outputPath('subscription-network-status.json'), JSON.stringify(evidence, null, 2), ); }); test('公网真实 WASM A 抓放(明确 mock,不调用付费 API)', async ({ page }, info) => { test.skip(!existsSync('build/lekiwi/lekiwi-v1.zip'), '无本地 A 资产'); let modelCalls = 0; page.on('request', (r) => { if (/\/api\/decision\/v1\/(plan|decide|test)$/.test(r.url())) modelCalls++; }); await page.goto('/'); await page .locator('input[type=file]') .first() .setInputFiles(resolve('build/lekiwi/lekiwi-v1.zip')); const dialog = page.getByRole('dialog', { name: '配置 URDF 仿真组件' }); await expect(dialog).toBeVisible({ timeout: 30000 }); await dialog.getByLabel('URDF 机器人 profile').selectOption('lekiwi-v1'); await dialog.getByRole('button', { name: '转换并加载' }).click(); await expect(page.getByRole('button', { name: '▶ 播放' })).toBeEnabled({ timeout: 90000 }); await page.getByRole('tab', { name: '控制台', exact: true }).click(); await page.getByRole('button', { name: 'LeKiwi 智能抓放', exact: true }).click(); const panel = page.getByLabel('LeKiwi 智能抓放任务', { exact: true }); await panel.getByRole('button', { name: '开始任务(mock)', exact: true }).click(); await expect(panel.getByTestId('agent-task-state')).toContainText('物理验收成功', { timeout: 180000, }); await panel.getByText('高级操作与证据导出', { exact: true }).click(); const download = page.waitForEvent('download'); await panel.getByRole('button', { name: '导出任务证据', exact: true }).click(); const data = JSON.parse(await readFile((await (await download).path())!, 'utf8')); expect(data.succeeded).toBe(true); expect(data.transported).toBeGreaterThanOrEqual(0.5); expect(data.mode).toBe('mock'); expect(modelCalls).toBe(0); await writeFile( info.outputPath('production-mock-physical-evidence.json'), JSON.stringify(data, null, 2), ); await page.screenshot({ path: info.outputPath('production-physical-success.png') }); });