feat(web-platform): release V0.8.1 常规地图优化
This commit is contained in:
@@ -481,14 +481,14 @@ test('认证资产可点击创建场景并拖到画布落位', async ({ page })
|
||||
await expect(page.getByText('模型加载完成')).toBeVisible({ timeout: 30_000 });
|
||||
await page.getByRole('tab', { name: '地图' }).click();
|
||||
|
||||
const library = page.getByLabel('认证资产');
|
||||
const library = page.getByLabel('地图资产库');
|
||||
await expect(library.getByText('点击添加,或按住资产拖到画布落位。')).toBeVisible();
|
||||
await library.getByRole('button', { name: '添加基础方盒' }).click();
|
||||
await expect(page.getByText('正在加载 MuJoCo 与模型…')).toHaveCount(0);
|
||||
await expect(page.getByLabel('地图来源')).toHaveValue('project:maps/scene_1/map.json', {
|
||||
timeout: 30_000,
|
||||
});
|
||||
await expect(page.getByText('V3 地图编辑器')).toBeVisible();
|
||||
await expect(page.getByText('认证资产与场景对象属性')).toBeVisible();
|
||||
await expect(page.getByLabel('地图对象列表').getByText('基础方盒 · 方盒')).toBeVisible();
|
||||
|
||||
await page.locator('[data-map-asset="ramp"]').dragTo(page.locator('main canvas'));
|
||||
@@ -510,6 +510,240 @@ test('认证资产可点击创建场景并拖到画布落位', async ({ page })
|
||||
await page.mouse.move(canvasBox!.x + 104, canvasBox!.y + 50, { steps: 8 });
|
||||
await page.mouse.up({ button: 'left' });
|
||||
await expect.poll(() => gizmoLine.getAttribute('x2')).not.toBe(beforeRotation);
|
||||
await expect(page.getByLabel('对象名称')).toHaveCount(0);
|
||||
await expect(page.getByLabel('地图对象列表').getByText('基础方盒 · 方盒')).toBeVisible();
|
||||
});
|
||||
|
||||
test('认证资产自动打开地图属性并与参数地形一次编译', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.setViewportSize({ width: 1440, height: 900 });
|
||||
await page
|
||||
.locator('input[type="file"]')
|
||||
.first()
|
||||
.setInputFiles({
|
||||
name: 'unified-map-model.xml',
|
||||
mimeType: 'text/xml',
|
||||
buffer: Buffer.from(SIMPLE_MODEL),
|
||||
});
|
||||
await expect(page.getByText('模型加载完成')).toBeVisible({ timeout: 30_000 });
|
||||
await page.getByRole('tab', { name: '地图' }).click();
|
||||
|
||||
const library = page.getByLabel('地图资产库');
|
||||
await page.getByRole('tab', { name: '属性' }).click();
|
||||
await library.getByRole('button', { name: '添加基础方盒' }).click();
|
||||
await expect(page.getByRole('tab', { name: '地图' })).toHaveAttribute('aria-selected', 'true');
|
||||
await expect(page.getByText('认证资产与场景对象属性')).toBeVisible();
|
||||
await expect(page.getByText('1 项场景更改待应用')).toBeVisible();
|
||||
|
||||
const sceneTree = page.getByLabel('场景资产树');
|
||||
await expect(sceneTree.getByText('基础方盒')).toBeVisible();
|
||||
await page.getByRole('tab', { name: '属性' }).click();
|
||||
await sceneTree.getByRole('button', { name: /基础方盒/ }).click();
|
||||
await expect(page.getByRole('tab', { name: '地图' })).toHaveAttribute('aria-selected', 'true');
|
||||
|
||||
await library.getByRole('button', { name: '添加随机粗糙地形' }).click();
|
||||
await page.getByLabel('位置 X(m)').fill('4');
|
||||
await expect(page.getByText('2 项场景更改待应用')).toBeVisible();
|
||||
|
||||
// 切换到参数地形后,尚未编译的认证资产仍保留在画布中并可直接选回。
|
||||
const draftCanvasBox = await page.locator('main canvas').first().boundingBox();
|
||||
expect(draftCanvasBox).not.toBeNull();
|
||||
let selectedDraftAsset = false;
|
||||
for (let y = 0.15; y <= 0.85 && !selectedDraftAsset; y += 0.05) {
|
||||
for (let x = 0.05; x <= 0.95; x += 0.05) {
|
||||
await page.mouse.click(
|
||||
draftCanvasBox!.x + draftCanvasBox!.width * x,
|
||||
draftCanvasBox!.y + draftCanvasBox!.height * y,
|
||||
);
|
||||
await page.waitForTimeout(20);
|
||||
if (await page.getByLabel('对象名称').count()) {
|
||||
selectedDraftAsset = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
expect(selectedDraftAsset).toBe(true);
|
||||
await expect(page.getByLabel('地图来源')).toHaveValue('project:maps/scene_1/map.json');
|
||||
await sceneTree.getByRole('button', { name: '选择地图实例 随机粗糙地形' }).click();
|
||||
await expect(page.getByLabel('地图来源')).toHaveValue('builtin');
|
||||
|
||||
await page.getByRole('button', { name: '一次编译应用' }).click();
|
||||
|
||||
await expect(page.getByText('2 项场景更改待应用')).toHaveCount(0, { timeout: 30_000 });
|
||||
await expect(page.getByText(/已加载工程地图“场景 1”(1 个物理几何/)).toBeVisible();
|
||||
await expect(page.getByText(/已加载随机粗糙地形物理地图/)).toBeVisible();
|
||||
await expect(page.getByLabel('地图来源')).toHaveValue('builtin');
|
||||
|
||||
// 当前仍选中参数地形时,直接点已编译的认证资产也必须反查场景与对象,首次点击即挂载操纵器。
|
||||
const canvasBox = await page.locator('main canvas').first().boundingBox();
|
||||
expect(canvasBox).not.toBeNull();
|
||||
let selectedCompiledAsset = false;
|
||||
for (let y = 0.15; y <= 0.85 && !selectedCompiledAsset; y += 0.05) {
|
||||
for (let x = 0.05; x <= 0.95; x += 0.05) {
|
||||
await page.mouse.click(
|
||||
canvasBox!.x + canvasBox!.width * x,
|
||||
canvasBox!.y + canvasBox!.height * y,
|
||||
);
|
||||
await page.waitForTimeout(20);
|
||||
if (await page.getByLabel('对象名称').count()) {
|
||||
selectedCompiledAsset = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
expect(selectedCompiledAsset).toBe(true);
|
||||
await expect(page.getByLabel('地图来源')).toHaveValue('project:maps/scene_1/map.json');
|
||||
await expect(page.getByLabel('对象名称')).toHaveValue('基础方盒');
|
||||
await expect(page.getByLabel('地图对象列表').getByText('基础方盒 · 方盒')).toBeVisible();
|
||||
await expect(page.getByText('地图草稿尚未应用')).toHaveCount(0);
|
||||
});
|
||||
|
||||
test('放弃首次认证资产会完整回滚临时场景和工程文件', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.setViewportSize({ width: 1440, height: 900 });
|
||||
await page
|
||||
.locator('input[type="file"]')
|
||||
.first()
|
||||
.setInputFiles({
|
||||
name: 'discard-map-model.xml',
|
||||
mimeType: 'text/xml',
|
||||
buffer: Buffer.from(SIMPLE_MODEL),
|
||||
});
|
||||
await expect(page.getByText('模型加载完成')).toBeVisible({ timeout: 30_000 });
|
||||
await page.getByRole('tab', { name: '地图' }).click();
|
||||
|
||||
const library = page.getByLabel('地图资产库');
|
||||
await library.getByRole('button', { name: '添加基础方盒' }).click();
|
||||
await expect(page.getByText('1 项场景更改待应用')).toBeVisible();
|
||||
await expect(page.getByLabel('场景资产树')).toContainText('场景 1');
|
||||
await page.getByRole('button', { name: '放弃场景更改' }).click();
|
||||
await expect(page.getByText('1 项场景更改待应用')).toHaveCount(0);
|
||||
await expect(page.getByLabel('场景资产树')).toContainText('0 个实例');
|
||||
await expect(library.getByText('场景 1')).toHaveCount(0);
|
||||
|
||||
// 文件和 map id 也必须回滚;再次创建应复用 scene_1,而不是泄漏出 scene_2。
|
||||
await library.getByRole('button', { name: '添加基础方盒' }).click();
|
||||
await expect(page.getByLabel('场景资产树')).toContainText('场景 1');
|
||||
await expect(page.getByLabel('场景资产树')).not.toContainText('场景 2');
|
||||
});
|
||||
|
||||
test('工程地图与参数地形共享放置草稿、实例变换和回滚入口', async ({ page }) => {
|
||||
const mapJson = JSON.stringify({
|
||||
schemaVersion: 1,
|
||||
id: 'warehouse-draft',
|
||||
name: '草稿仓库',
|
||||
coordinateSystem: { units: 'm', up: 'Z', forward: '+X' },
|
||||
physics: { source: 'physics/world.xml' },
|
||||
spawnPoints: [],
|
||||
});
|
||||
const project = zipSync({
|
||||
'model.xml': Buffer.from(SIMPLE_MODEL),
|
||||
'maps/warehouse/map.json': Buffer.from(mapJson),
|
||||
'maps/warehouse/physics/world.xml': Buffer.from(
|
||||
'<mujoco><worldbody><geom name="wall" type="box" pos="0 0 .5" size=".1 1 .5"/></worldbody></mujoco>',
|
||||
),
|
||||
});
|
||||
await page.goto('/');
|
||||
await page.setViewportSize({ width: 1440, height: 900 });
|
||||
await page
|
||||
.locator('input[type="file"]')
|
||||
.first()
|
||||
.setInputFiles({
|
||||
name: 'unified-project-map.zip',
|
||||
mimeType: 'application/zip',
|
||||
buffer: Buffer.from(project),
|
||||
});
|
||||
await expect(page.getByText('模型加载完成')).toBeVisible({ timeout: 30_000 });
|
||||
await page.getByRole('tab', { name: '地图' }).click();
|
||||
|
||||
const library = page.getByLabel('地图资产库');
|
||||
await library.getByRole('button', { name: '放置工程地图 草稿仓库' }).click();
|
||||
await expect(page.getByText('1 项场景更改待应用')).toBeVisible();
|
||||
await expect(page.getByLabel('场景资产树')).toContainText('待应用');
|
||||
await expect(page.getByText('正在准备仿真')).toHaveCount(0);
|
||||
await page.getByLabel('位置 X(m)').fill('3');
|
||||
await page.getByLabel('位置 Y(m)').fill('-2');
|
||||
await expect(page.getByText('1 项场景更改待应用')).toBeVisible();
|
||||
|
||||
await library.getByRole('button', { name: '添加波浪地形' }).click();
|
||||
await expect(page.getByText('2 项场景更改待应用')).toBeVisible();
|
||||
await page.getByRole('button', { name: '一次编译应用' }).click();
|
||||
await expect(page.getByText('2 项场景更改待应用')).toHaveCount(0, { timeout: 30_000 });
|
||||
await expect(page.getByText(/已加载工程地图“草稿仓库”/)).toBeVisible();
|
||||
await expect(page.getByText(/已加载波浪地形物理地图/)).toBeVisible();
|
||||
|
||||
await page.getByRole('button', { name: '删除地图实例 草稿仓库' }).click();
|
||||
await expect(page.getByText('1 项场景更改待应用')).toBeVisible();
|
||||
await expect(page.getByLabel('场景资产树')).not.toContainText('草稿仓库');
|
||||
await page.getByRole('button', { name: '放弃场景更改' }).click();
|
||||
await expect(page.getByText('1 项场景更改待应用')).toHaveCount(0);
|
||||
await expect(page.getByLabel('场景资产树')).toContainText('草稿仓库');
|
||||
});
|
||||
|
||||
test('认证资产重力放置使用参数地形的真实承载高度', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.setViewportSize({ width: 1440, height: 900 });
|
||||
await page
|
||||
.locator('input[type="file"]')
|
||||
.first()
|
||||
.setInputFiles({
|
||||
name: 'terrain-surface-model.xml',
|
||||
mimeType: 'text/xml',
|
||||
buffer: Buffer.from(SIMPLE_MODEL),
|
||||
});
|
||||
await expect(page.getByText('模型加载完成')).toBeVisible({ timeout: 30_000 });
|
||||
await page.getByRole('tab', { name: '地图' }).click();
|
||||
|
||||
const library = page.getByLabel('地图资产库');
|
||||
await library.getByRole('button', { name: '添加深坑地形' }).click();
|
||||
await page.getByLabel('地形难度(0–1)').fill('1');
|
||||
await library.getByLabel('新增资产放置方式').selectOption('gravity');
|
||||
await library.getByRole('button', { name: '添加基础方盒' }).click();
|
||||
|
||||
await expect(page.getByLabel('对象放置方式')).toHaveValue('gravity');
|
||||
await expect(page.getByLabel('对象位置Z')).toHaveValue('-0.3');
|
||||
await expect(page.getByText('2 项场景更改待应用')).toBeVisible();
|
||||
});
|
||||
|
||||
test('参数化地形可连续拖到画布并一次性编译', async ({ page }) => {
|
||||
await page.goto('/');
|
||||
await page.setViewportSize({ width: 1440, height: 900 });
|
||||
await page
|
||||
.locator('input[type="file"]')
|
||||
.first()
|
||||
.setInputFiles({
|
||||
name: 'terrain-draft-model.xml',
|
||||
mimeType: 'text/xml',
|
||||
buffer: Buffer.from(SIMPLE_MODEL),
|
||||
});
|
||||
await expect(page.getByText('模型加载完成')).toBeVisible({ timeout: 30_000 });
|
||||
await page.getByRole('tab', { name: '地图' }).click();
|
||||
|
||||
await page.locator('[data-system-terrain="rough"]').dragTo(page.locator('main canvas'));
|
||||
await expect(page.getByText('1 项场景更改待应用')).toBeVisible();
|
||||
await expect(page.getByText('正在准备仿真')).toHaveCount(0);
|
||||
await page.locator('[data-system-terrain="wave"]').dragTo(page.locator('main canvas'));
|
||||
await expect(page.getByText('2 项场景更改待应用')).toBeVisible();
|
||||
await expect(page.getByLabel('场景资产树')).toContainText('随机粗糙地形');
|
||||
await expect(page.getByLabel('场景资产树')).toContainText('波浪地形');
|
||||
|
||||
await page.getByRole('tab', { name: '属性' }).click();
|
||||
const canvasBox = await page.locator('main canvas').first().boundingBox();
|
||||
expect(canvasBox).not.toBeNull();
|
||||
await page.mouse.click(
|
||||
canvasBox!.x + canvasBox!.width * 0.5,
|
||||
canvasBox!.y + canvasBox!.height * 0.78,
|
||||
);
|
||||
await expect(page.getByRole('tab', { name: '地图' })).toHaveAttribute('aria-selected', 'true');
|
||||
await expect(page.getByLabel('视口选择与变换')).toBeVisible();
|
||||
await expect(page.getByText('地图与对象属性')).toBeVisible();
|
||||
|
||||
await page.getByRole('button', { name: '一次编译应用' }).click();
|
||||
await expect(page.getByText('2 项场景更改待应用')).toHaveCount(0, { timeout: 30_000 });
|
||||
await expect(page.getByText(/已加载随机粗糙地形物理地图/)).toBeVisible({
|
||||
timeout: 30_000,
|
||||
});
|
||||
await expect(page.getByText(/已加载波浪地形物理地图/)).toBeVisible();
|
||||
});
|
||||
|
||||
test('应用内置 MJCF 楼梯物理地图', async ({ page }) => {
|
||||
@@ -638,7 +872,7 @@ test('将受支持的只读物理地图转换为可编辑副本', async ({ page
|
||||
await page.getByRole('tab', { name: '地图' }).click();
|
||||
await page.getByLabel('地图来源').selectOption({ label: '旧版基础场景' });
|
||||
await page.getByRole('button', { name: '应用并重新编译' }).click();
|
||||
const editor = page.getByText('V3 地图编辑器').locator('..');
|
||||
const editor = page.getByText('认证资产与场景对象属性').locator('..');
|
||||
await expect(editor.getByText(/保持只读/)).toBeVisible({ timeout: 30_000 });
|
||||
await editor.getByRole('button', { name: '创建可编辑副本' }).click();
|
||||
await expect(page.getByText('已创建可编辑地图副本')).toBeVisible({ timeout: 30_000 });
|
||||
@@ -684,8 +918,8 @@ test('编辑 V3 地图对象并事务式应用', async ({ page }) => {
|
||||
await page.getByRole('tab', { name: '地图' }).click();
|
||||
await page.getByLabel('地图来源').selectOption({ label: '可编辑场景' });
|
||||
await page.getByRole('button', { name: '应用并重新编译' }).click();
|
||||
await expect(page.getByText('V3 地图编辑器')).toBeVisible({ timeout: 30_000 });
|
||||
const editor = page.getByText('V3 地图编辑器').locator('..');
|
||||
await expect(page.getByText('认证资产与场景对象属性')).toBeVisible({ timeout: 30_000 });
|
||||
const editor = page.getByText('认证资产与场景对象属性').locator('..');
|
||||
await expect(editor.getByRole('button', { name: '移动工具 W' })).toHaveAttribute(
|
||||
'aria-pressed',
|
||||
'true',
|
||||
@@ -735,15 +969,29 @@ test('工程地图编译失败时保留上一仿真会话', async ({ page }) =>
|
||||
buffer: Buffer.from(project),
|
||||
});
|
||||
await expect(page.getByText('WASM 已加载')).toBeVisible({ timeout: 30_000 });
|
||||
await page.getByRole('button', { name: '▶ 播放' }).click();
|
||||
await page.waitForTimeout(200);
|
||||
const runningTime = Number(
|
||||
(await page.locator('footer').innerText()).match(/时间 ([\d.]+) s/)?.[1] ?? 0,
|
||||
);
|
||||
await page.getByRole('tab', { name: '地图' }).click();
|
||||
await page.getByLabel('地图来源').selectOption({ label: '动态错误地图' });
|
||||
await page.getByRole('button', { name: '应用并重新编译' }).click();
|
||||
await expect(page.getByRole('alert')).toContainText('模型编译失败', { timeout: 30_000 });
|
||||
await expect(page.getByLabel('地图来源')).toHaveValue('none');
|
||||
await expect(page.getByLabel('地图来源')).toHaveValue('project:maps/invalid/map.json');
|
||||
await expect(page.getByText('1 项场景更改待应用')).toBeVisible();
|
||||
await expect(page.getByLabel('场景资产树')).toContainText('待应用');
|
||||
await expect(page.getByRole('button', { name: '▶ 播放' })).toBeVisible();
|
||||
await page.getByRole('button', { name: '关闭错误' }).click();
|
||||
await page.getByRole('button', { name: '▶ 播放' }).click();
|
||||
await page.waitForTimeout(300);
|
||||
await expect(page.locator('footer')).not.toContainText('时间 0.000 s');
|
||||
await expect
|
||||
.poll(async () =>
|
||||
Number((await page.locator('footer').innerText()).match(/时间 ([\d.]+) s/)?.[1] ?? 0),
|
||||
)
|
||||
.toBeGreaterThan(runningTime);
|
||||
await page.getByRole('button', { name: '放弃场景更改' }).click();
|
||||
await expect(page.getByText('1 项场景更改待应用')).toHaveCount(0);
|
||||
await expect(page.getByLabel('地图来源')).toHaveValue('none');
|
||||
});
|
||||
|
||||
test('无效模型显示中文诊断且保留工程树', async ({ page }) => {
|
||||
|
||||
Reference in New Issue
Block a user