Files
Mujoco_WASM/web_platform/e2e/website.embed.production.spec.ts
T
chenlin 0d986f60bd
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
feat: release v1.0.2 LeKiwi 语言控制与网站嵌入
集成服务器托管模型、自然语言移动与有界抓放、内置 LeKiwi URL 导入和双摄像头;同步部署契约与指定域名 iframe 白名单,保留原有物理安全、会话及调用预算防护。

更新 npm 包及锁文件版本、CHANGELOG 与发布文档。提交前 typecheck、120 项定向前端测试和 44 项后端测试通过(3 项可选跳过);真实 v2 云模型抓放仍待单独验收,不包含运行密钥或构建产物。
2026-09-24 15:29:49 +08:00

114 lines
4.5 KiB
TypeScript

import { expect, test } from '@playwright/test';
const site = 'https://cadworld-sim.robotquan.com';
const approvedParent = 'https://cadworld.robotquan.com';
test.skip(process.env.CADWORLD_PRODUCTION_TEST !== '1', '真实站点非付费检查须显式启用');
test('生产嵌入响应头:仅允许自身与指定同事域名,API 仍拒绝父页面来源', async ({ request }) => {
const response = await request.get(site);
expect(response.status()).toBe(200);
const headers = response.headers();
expect(headers['x-frame-options']).toBeUndefined();
expect(headers['content-security-policy']).toContain(`frame-ancestors 'self' ${approvedParent};`);
expect(headers['content-security-policy']).toContain("object-src 'none'");
expect(headers['x-content-type-options']).toBe('nosniff');
expect(headers['strict-transport-security']).toContain('max-age=31536000');
const forbidden = await request.post(`${site}/api/decision/v1/session`, {
headers: { Origin: approvedParent, 'Sec-Fetch-Site': 'same-site' },
data: {},
});
expect(forbidden.status()).toBe(403);
expect((await forbidden.json()).error).toBe('origin_forbidden');
expect(forbidden.headers()['access-control-allow-origin']).toBeUndefined();
});
test('生产嵌入:允许来源真实加载应用、Strict Cookie 会话和 CSRF 正常,无推理', async ({
page,
context,
}, info) => {
let inference = 0;
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();
});
// Only the parent HTML is synthetic. The iframe, assets and session API are live.
const parentUrl = `${approvedParent}/__cadworld_embed_acceptance__`;
await page.route(parentUrl, (route) =>
route.fulfill({
contentType: 'text/html',
body: `<html><body style="margin:0"><iframe title="CADWorld Sim" src="${site}/" style="width:100%;height:95vh;border:0" allow="fullscreen"></iframe></body></html>`,
}),
);
await page.goto(parentUrl);
const frame = page.frameLocator('iframe');
await frame.getByRole('tab', { name: '控制台', exact: true }).click();
await frame.getByRole('button', { name: '机器人语言控制', exact: true }).click();
await expect(frame.getByRole('textbox', { name: '机器人指令' })).toBeEditable();
const embedded = page.frames().find((f) => f.url() === `${site}/`);
expect(embedded).toBeTruthy();
const sessionResult = await embedded!.evaluate(async () => {
const prefix = '/api/decision/v1';
const bootstrap = await fetch(prefix + '/session', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: '{}',
});
const session = await bootstrap.json();
const status = await fetch(prefix + '/status');
const csrfGuard = await fetch(prefix + '/cancel', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: '{}',
});
const destroyed = await fetch(prefix + '/session', {
method: 'DELETE',
headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': session.csrfToken },
body: '{}',
});
const expired = await fetch(prefix + '/status');
return {
bootstrap: bootstrap.status,
ready: session.ready,
status: status.status,
csrfGuard: csrfGuard.status,
destroyed: destroyed.status,
expired: expired.status,
};
});
expect(sessionResult).toEqual({
bootstrap: 200,
ready: true,
status: 200,
csrfGuard: 403,
destroyed: 200,
expired: 401,
});
expect(
(await context.cookies(site)).filter((c) => c.name.includes('session')).map((c) => c.name),
).toHaveLength(0);
expect(inference).toBe(0);
await page.screenshot({ path: info.outputPath('approved-iframe.png') });
});
test('生产嵌入:未授权同站子域名仍被 CSP 拒绝', async ({ page }) => {
const violations: string[] = [];
page.on('console', (message) => {
if (message.type() === 'error') violations.push(message.text());
});
const parentUrl = 'https://unapproved.robotquan.com/__cadworld_embed_acceptance__';
await page.route(parentUrl, (route) =>
route.fulfill({
contentType: 'text/html',
body: `<iframe src="${site}/" title="forbidden"></iframe>`,
}),
);
await page.goto(parentUrl);
await expect.poll(() => violations.some((text) => text.includes('frame-ancestors'))).toBe(true);
await expect(
page.frameLocator('iframe').getByRole('tab', { name: '控制台', exact: true }),
).toHaveCount(0);
});