import { test, expect, type Page } from '@playwright/test'; import { createServer, type ViteDevServer } from 'vite'; import { resolve } from 'node:path'; let server: ViteDevServer; let baseUrl: string; test.use({ headless: true, channel: process.env.MAKELORE_LAYOUT_BROWSER_CHANNEL, video: 'off' }); // Exercise actual navigation controls and persisted layout state without // starting Electron or connecting to an account or any cloud service. test.beforeAll(async () => { const entry = resolve('tests/e2e/__cloud-agent-navigation-layout-harness.tsx'); server = await createServer({ configFile: false, cacheDir: 'node_modules/.vite-cloud-agent-navigation-layout', resolve: { alias: { '@': resolve('src') } }, esbuild: { jsx: 'automatic' }, server: { host: '127.0.0.1', port: 0 }, plugins: [{ name: 'isolated-cloud-agent-navigation-layout', enforce: 'pre', resolveId(id) { if (id === '/__cloud-agent-navigation-layout-harness.tsx') return entry; }, load(id) { if (id === entry) return ` import { createRoot } from 'react-dom/client'; import { MemoryRouter, Route, Routes, useLocation } from 'react-router-dom'; import { TitleBar } from '/src/components/layout/TitleBar.tsx'; import { useSettingsStore } from '/src/stores/settings.ts'; import '/src/styles/globals.css'; function AgentFrame() { const collapsed = useSettingsStore(state => state.sidebarCollapsed); const location = useLocation(); return
{!collapsed &&
; } createRoot(document.getElementById('root')).render( } />首页模块卡片} /> ); `; if (id.endsWith('/src/stores/coding-workspace.ts')) return 'export function useCodingWorkspaceStore(select) { return select({ activeProject: null }); }'; if (id.endsWith('/src/stores/user-sync.ts')) return 'export const useUserSyncStore = { getState: () => ({ pushPreferences: async () => undefined }) };'; }, configureServer(vite) { vite.middlewares.use((req, res, next) => { if (req.url !== '/' && !req.url?.startsWith('/?')) return next(); res.setHeader('Content-Type', 'text/html'); res.end('
'); }); }, }], }); await server.listen(); baseUrl = server.resolvedUrls!.local[0]; }); test.afterAll(async () => { await server?.close(); }); async function expectNavigationRow(page: Page, width: number) { const back = page.getByRole('button', { name: '返回首页', exact: true }); const toggle = page.getByTestId('titlebar-sidebar-toggle'); await expect(back).toHaveCount(1); await expect(back).toBeVisible(); await expect(toggle).toBeVisible(); const a = (await back.boundingBox())!; const b = (await toggle.boundingBox())!; expect(Math.abs((a.y + a.height / 2) - (b.y + b.height / 2))).toBeLessThanOrEqual(1); expect(a.x + a.width).toBeLessThanOrEqual(b.x); for (const box of [a, b]) { expect(box.x).toBeGreaterThanOrEqual(0); expect(box.x + box.width).toBeLessThanOrEqual(width); expect(box.y).toBeGreaterThanOrEqual(0); expect(box.width).toBeGreaterThanOrEqual(32); expect(box.height).toBeGreaterThanOrEqual(32); } return b; } for (const platform of ['darwin', 'win32', 'linux']) { for (const width of [375, 1024]) { test(`${platform} Agent return and sidebar toggle share one usable row at ${width}px`, async ({ page }) => { const pageErrors: string[] = []; page.on('pageerror', error => pageErrors.push(error.message)); await page.setViewportSize({ width, height: 760 }); await page.addInitScript(platform => { Object.defineProperty(window, 'electron', { value: { platform, ipcRenderer: { invoke: async (channel: string) => { if (channel === 'window:isMaximized' || channel === 'window:isFullScreen') return false; throw new Error('Unexpected fixture IPC ' + channel); }, on: () => () => {}, } } }); }, platform); await page.goto(baseUrl); await expect(page.getByTestId('fixture-location')).toHaveText('/cloud-agents'); const expandedToggle = await expectNavigationRow(page, width); await expect(page.getByTestId('titlebar-sidebar-toggle')).toHaveAttribute('aria-label', '折叠侧栏'); // 375px stresses application controls; Windows' native desktop frame has // a larger minimum width, so validate its chrome separation at 1024px. if (platform !== 'win32' || width === 1024) { const logo = (await page.getByTestId('titlebar-logo').boundingBox())!; expect(expandedToggle.x + expandedToggle.width).toBeLessThanOrEqual(logo.x); } await page.getByTestId('titlebar-sidebar-toggle').click(); await expect(page.getByRole('complementary', { name: '智能体侧栏' })).toHaveCount(0); await expect(page.getByTestId('titlebar-sidebar-toggle')).toHaveAttribute('aria-label', '展开侧栏'); const collapsedToggle = await expectNavigationRow(page, width); const collapsedLogo = (await page.getByTestId('titlebar-logo').boundingBox())!; expect(collapsedToggle.x + collapsedToggle.width).toBeLessThanOrEqual(collapsedLogo.x); await page.getByTestId('titlebar-sidebar-toggle').click(); await expect(page.getByRole('complementary', { name: '智能体侧栏' })).toBeVisible(); await expectNavigationRow(page, width); await page.getByTestId('titlebar-sidebar-toggle').click(); await page.getByRole('button', { name: '返回首页', exact: true }).click(); await expect(page.getByText('首页模块卡片')).toBeVisible(); expect(pageErrors).toEqual([]); }); } }