feat(web-platform): release V0.9.3 全模块界面重构
This commit is contained in:
@@ -0,0 +1,191 @@
|
||||
import { expect, test, type Page } from '@playwright/test';
|
||||
|
||||
const MODEL = `<mujoco model="domain-panels"><worldbody><geom type="plane" size="3 3 .1"/><body name="box" pos="0 0 1"><joint name="slide" type="slide" axis="1 0 0" range="-1 1"/><geom type="box" size=".2 .2 .2" mass="1"/></body></worldbody><actuator><motor name="motor" joint="slide" ctrlrange="-2 2"/></actuator></mujoco>`;
|
||||
async function load(page: Page) {
|
||||
await page.goto('/');
|
||||
await page
|
||||
.locator('#mujoco-project-files')
|
||||
.setInputFiles({ name: 'domain.xml', mimeType: 'text/xml', buffer: Buffer.from(MODEL) });
|
||||
await expect(page.getByRole('button', { name: '▶ 播放' })).toBeEnabled();
|
||||
}
|
||||
async function left(page: Page) {
|
||||
const button = page.getByRole('button', { name: '显示工程面板', exact: true });
|
||||
if (await button.isVisible()) await button.click();
|
||||
}
|
||||
async function right(page: Page) {
|
||||
const button = page.getByRole('button', { name: '显示右侧面板', exact: true });
|
||||
if (await button.isVisible()) await button.click();
|
||||
}
|
||||
async function screenshot(page: Page, name: string) {
|
||||
expect(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth)).toBe(true);
|
||||
await page.screenshot({
|
||||
path: test.info().outputPath(name + '.png'),
|
||||
mask: [page.getByLabel('视口状态')],
|
||||
});
|
||||
}
|
||||
|
||||
for (const theme of ['dark', 'light']) {
|
||||
for (const [width, height] of [
|
||||
[1920, 1080],
|
||||
[1440, 900],
|
||||
[1366, 768],
|
||||
[1024, 768],
|
||||
[768, 800],
|
||||
]) {
|
||||
test(`领域检查器与录制 ${theme} ${width}`, async ({ page }) => {
|
||||
const errors: string[] = [];
|
||||
page.on('pageerror', (error) => errors.push(error.message));
|
||||
await page.setViewportSize({ width, height });
|
||||
await page.addInitScript(
|
||||
(value) => localStorage.setItem('mujoco-platform-theme', value),
|
||||
theme,
|
||||
);
|
||||
await load(page);
|
||||
await left(page);
|
||||
await page
|
||||
.getByRole('navigation', { name: '模型结构树' })
|
||||
.getByRole('treeitem', { name: /box/ })
|
||||
.first()
|
||||
.click();
|
||||
await expect(page.getByText(/^Body #/)).toBeVisible();
|
||||
await expect(page.getByRole('button', { name: '标识与层级' })).toHaveAttribute(
|
||||
'aria-expanded',
|
||||
'false',
|
||||
);
|
||||
await screenshot(page, 'body');
|
||||
await page
|
||||
.getByRole('tabpanel', { name: '检查器' })
|
||||
.getByRole('button', { name: /slide/ })
|
||||
.click();
|
||||
await expect(page.getByRole('slider', { name: 'slide' })).toBeVisible();
|
||||
await page.getByRole('slider', { name: 'slide' }).focus();
|
||||
await page.keyboard.press('ArrowRight');
|
||||
await expect(page.getByRole('slider', { name: 'slide' })).not.toHaveValue('0');
|
||||
await page.getByText('增益与输出限幅', { exact: true }).click();
|
||||
await expect(page.getByText(/参数立即作用于模型/)).toBeVisible();
|
||||
await screenshot(page, 'joint-actuator');
|
||||
await page.getByRole('tab', { name: '数据录制' }).click();
|
||||
await page.getByRole('button', { name: '开始记录' }).click();
|
||||
await expect(page.getByText('记录中', { exact: true })).toBeVisible();
|
||||
await expect(page.getByLabel('采样频率', { exact: true })).toBeDisabled();
|
||||
await page.getByRole('button', { name: '▶ 播放' }).click();
|
||||
await page.waitForTimeout(150);
|
||||
await page.getByRole('button', { name: '⏸ 暂停' }).click();
|
||||
await page.getByRole('button', { name: '停止记录' }).click();
|
||||
await page.getByText('实时运动状态', { exact: true }).click();
|
||||
await expect(page.getByRole('button', { name: '导出 CSV' })).toBeEnabled();
|
||||
await screenshot(page, 'recording');
|
||||
await left(page);
|
||||
await page.getByLabel('地图资产库').getByRole('button', { name: '添加基础方盒' }).click();
|
||||
await page.getByRole('tab', { name: '检查器' }).click();
|
||||
await expect(page.getByLabel('对象参数sizeX', { exact: true })).toBeVisible();
|
||||
await expect(page.getByText('表面材质', { exact: true }).locator('..')).not.toHaveAttribute(
|
||||
'open',
|
||||
);
|
||||
await page.getByLabel('对象参数sizeX', { exact: true }).fill('1.5');
|
||||
await page.getByLabel('对象参数sizeX', { exact: true }).press('Enter');
|
||||
await expect(page.getByLabel('地图草稿状态')).toContainText('未保存');
|
||||
await screenshot(page, 'map-draft');
|
||||
if (width === 1440) {
|
||||
await page
|
||||
.getByLabel('对象参数sizeX', { exact: true })
|
||||
.locator('..')
|
||||
.getByRole('button')
|
||||
.focus();
|
||||
await expect(page.getByRole('tooltip')).toContainText('Shift 精调');
|
||||
const tip = await page.getByRole('tooltip').boundingBox();
|
||||
expect(tip!.x + tip!.width).toBeLessThanOrEqual(width);
|
||||
await screenshot(page, 'map-keyboard-tooltip');
|
||||
await page.keyboard.press('Escape');
|
||||
await expect(page.getByRole('tooltip')).toHaveCount(0);
|
||||
}
|
||||
expect(errors).toEqual([]);
|
||||
});
|
||||
}
|
||||
|
||||
test(`训练折叠详情与跨视图状态 ${theme}`, async ({ page }) => {
|
||||
await page.setViewportSize({ width: 1440, height: 900 });
|
||||
await page.addInitScript(
|
||||
(value) => localStorage.setItem('mujoco-platform-theme', value),
|
||||
theme,
|
||||
);
|
||||
let starts = 0;
|
||||
let offline = true;
|
||||
const job = {
|
||||
id: 'a'.repeat(32),
|
||||
taskId: 'Unitree-Go2-Flat',
|
||||
state: 'running',
|
||||
progress: 0.4,
|
||||
iteration: 4,
|
||||
maxIterations: 10,
|
||||
message: '模拟训练中,未启动真实作业',
|
||||
artifactReady: false,
|
||||
logs: ['Learning iteration 4 / 10', 'Mean value loss: 0.9'],
|
||||
};
|
||||
await page.route('http://127.0.0.1:8765/**', async (route) => {
|
||||
const request = route.request();
|
||||
if (offline) return route.fulfill({ status: 503, json: { error: '模拟服务断连' } });
|
||||
if (request.url().endsWith('/health'))
|
||||
return route.fulfill({
|
||||
json: {
|
||||
ready: true,
|
||||
trainerRoot: '/mock/trainer',
|
||||
tasks: ['Unitree-Go2-Flat'],
|
||||
taskMetadata: [
|
||||
{
|
||||
id: 'Unitree-Go2-Flat',
|
||||
name: 'Go2 平地',
|
||||
terrainPresets: ['plane'],
|
||||
terrainParameters: { size: { min: 4, max: 30, default: 8 } },
|
||||
sensorParameters: {},
|
||||
browserCompatible: true,
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
if (request.url().endsWith('/presets')) return route.fulfill({ json: { presets: [] } });
|
||||
if (request.method() === 'POST') starts++;
|
||||
return route.fulfill({ json: job });
|
||||
});
|
||||
await load(page);
|
||||
await right(page);
|
||||
await page.getByRole('tab', { name: '控制台' }).click();
|
||||
const tools = page.getByRole('tabpanel', { name: '控制台' });
|
||||
const group = tools.getByRole('button', { name: /强化学习任务/ });
|
||||
await group.click();
|
||||
await page.getByLabel('训练服务访问令牌').fill('mock-token');
|
||||
await tools.getByRole('button', { name: '连接', exact: true }).click();
|
||||
await expect(tools.getByRole('alert')).toContainText('模拟服务断连');
|
||||
await screenshot(page, 'training-disconnected');
|
||||
offline = false;
|
||||
await tools.getByRole('button', { name: '连接', exact: true }).click();
|
||||
await page.getByLabel('训练地形', { exact: true }).selectOption('plane');
|
||||
await tools.getByRole('button', { name: '地形详细参数' }).click();
|
||||
await page.getByLabel('地图尺寸 m', { exact: true }).fill('100');
|
||||
await tools.getByRole('button', { name: '地形详细参数' }).click();
|
||||
await tools.getByRole('button', { name: '发起本地训练' }).click();
|
||||
await expect(tools.getByRole('alert')).toContainText('超出允许范围');
|
||||
await expect(tools.getByRole('alert')).toBeInViewport();
|
||||
await expect(page.getByLabel('地图尺寸 m', { exact: true })).toBeVisible();
|
||||
expect(starts).toBe(0);
|
||||
await screenshot(page, 'training-validation');
|
||||
await page.getByLabel('地图尺寸 m', { exact: true }).fill('12');
|
||||
await page.getByLabel('运行名称', { exact: true }).fill('preserved');
|
||||
await group.click();
|
||||
await page.getByRole('tab', { name: '检查器' }).click();
|
||||
await page.getByRole('tab', { name: '数据录制' }).click();
|
||||
await page.getByRole('tab', { name: '控制台' }).click();
|
||||
await group.click();
|
||||
await expect(page.getByLabel('运行名称', { exact: true })).toHaveValue('preserved');
|
||||
await expect(page.getByLabel('训练服务访问令牌')).toHaveValue('mock-token');
|
||||
await tools.getByRole('button', { name: '发起本地训练' }).click();
|
||||
await expect(tools.getByRole('button', { name: '停止训练' })).toBeVisible();
|
||||
await screenshot(page, 'training-running');
|
||||
await group.click();
|
||||
await expect(group).toContainText('训练中');
|
||||
await page.getByRole('tab', { name: '检查器' }).click();
|
||||
await page.getByRole('tab', { name: '控制台' }).click();
|
||||
await expect(group).toContainText('训练中');
|
||||
expect(starts).toBe(1);
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user