import { expect, test, type Locator } from '@playwright/test'; async function expectWithinViewport(locator: Locator, width: number, height: number) { await expect(locator).toBeVisible(); await expect .poll(async () => { const box = await locator.boundingBox(); return Boolean( box && box.x >= 7 && box.y >= 7 && box.x + box.width <= width - 7 && box.y + box.height <= height - 7, ); }) .toBe(true); } for (const theme of ['dark', 'light']) { for (const [width, height] of [ [1920, 1080], [1440, 900], [1366, 768], [1024, 768], [768, 800], ]) { test(`共享浮层边界与主题 ${theme} ${width}×${height}`, async ({ page }) => { await page.setViewportSize({ width, height }); await page.addInitScript( (value) => localStorage.setItem('mujoco-platform-theme', value), theme, ); await page.goto('/'); const trigger = page.getByRole('button', { name: /隐藏工程面板|显示工程面板/ }); await trigger.focus(); const tooltip = page.getByRole('tooltip'); await expectWithinViewport(tooltip, width, height); await expect(trigger).toHaveAttribute( 'aria-describedby', (await tooltip.getAttribute('id')) as string, ); expect( await tooltip.evaluate((node) => node.closest('.theme-dark, .theme-light')?.className), ).toContain(`theme-${theme}`); await page.keyboard.press('Escape'); await expect(tooltip).toHaveCount(0); await page.getByRole('button', { name: /FPS \d/ }).click(); const popover = page.getByRole('dialog', { name: '性能详情' }); await expectWithinViewport(popover, width, height); await page.screenshot({ path: test.info().outputPath(`performance-${theme}-${width}.png`), fullPage: true, }); await page.setViewportSize({ width: width - 40, height: height - 40 }); await expectWithinViewport(popover, width - 40, height - 40); await page.keyboard.press('Escape'); await expect(popover).toHaveCount(0); }); } } test('边缘 hover 提示可移入,disabled 控件说明可经键盘访问', async ({ page }) => { await page.setViewportSize({ width: 1440, height: 900 }); await page.goto('/'); const theme = page.getByRole('button', { name: '打开命令面板' }); await theme.hover(); const tooltip = page.getByRole('tooltip'); await expectWithinViewport(tooltip, 1440, 900); await tooltip.hover(); await expect(tooltip).toBeVisible(); await page.keyboard.press('Escape'); await expect(tooltip).toHaveCount(0); const step = page.getByRole('button', { name: '单步', exact: true }); await expect(step).toBeDisabled(); await step.locator('..').focus(); await expect(tooltip).toHaveText('单步(暂停时可用)'); expect(await theme.evaluate((node) => node.parentElement?.hasAttribute('tabindex'))).toBe(false); }); test('全屏中的对话框 Tooltip 不裁剪且 Escape 仅关闭顶层', async ({ page }) => { await page.setViewportSize({ width: 1440, height: 900 }); await page.goto('/'); await page.getByRole('button', { name: '更多工作台操作' }).click(); await page.getByRole('menuitem', { name: '进入全屏' }).click(); await page.getByRole('button', { name: '更多工作台操作' }).click(); await page.getByRole('menuitem', { name: '工作台设置' }).click(); const dialog = page.getByRole('dialog', { name: '工作台设置' }); await expect(dialog).toBeVisible(); const close = dialog.getByRole('button', { name: '关闭', exact: true }); await close.focus(); const tooltip = page.getByRole('tooltip'); await expectWithinViewport(tooltip, 1440, 900); expect(await tooltip.evaluate((node) => document.fullscreenElement?.contains(node))).toBe(true); expect(await tooltip.evaluate((node) => Boolean(node.closest('[role="dialog"]')))).toBe(false); await page.screenshot({ path: test.info().outputPath('fullscreen-dialog-tooltip.png'), fullPage: true, }); await page.keyboard.press('Escape'); await expect(tooltip).toHaveCount(0); await expect(dialog).toBeVisible(); await page.keyboard.press('Escape'); await expect(dialog).toHaveCount(0); await page.getByRole('button', { name: '更多工作台操作' }).click(); await page.getByRole('menuitem', { name: '退出全屏' }).click(); });