import assert from 'node:assert/strict'; import { once } from 'node:events'; import { createServer } from 'node:http'; import { unlink, writeFile } from 'node:fs/promises'; import { createRequire } from 'node:module'; import { dirname, join } from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; const root = dirname(dirname(fileURLToPath(import.meta.url))); const ui = join(root, 'ui'); process.env.BROWSERSLIST_IGNORE_OLD_DATA = 'true'; process.chdir(ui); const require = createRequire(join(ui, 'package.json')); const { createServer: createViteServer } = await import(pathToFileURL(require.resolve('vite')).href); const { chromium } = require('playwright'); const fixture = `/tmp/execution-conversations-${process.pid}.js`; let vite, server, browser; try { await writeFile(fixture, ` import React from 'react'; import { createRoot } from 'react-dom/client'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import '${ui}/src/index.css'; import i18n from '${ui}/src/i18n/index.ts'; import { TaskExecutionsProvider } from '${ui}/src/hooks/useTaskExecutions.ts'; import { ExecutionsTab } from '${ui}/src/components/detail/tabs/ExecutionsTab.tsx'; import { useVisibleDetailTabs } from '${ui}/src/components/detail/detailTabs.ts'; import { SpaceChatRail } from '${ui}/src/components/spaces/SpaceChatRail.tsx'; function App() { const [kind, setKind] = React.useState('delegate'); const tabs = useVisibleDetailTabs(1).filter(t => ['delegates','subtasks'].includes(t.id)); return React.createElement('div', {style:{display:'flex',width:'100%',minWidth:0}}, React.createElement('main', {style:{flex:1,minWidth:0,padding:12}}, React.createElement(ExecutionsTab,{key:kind,taskId:1,kind})), React.createElement(SpaceChatRail,{items:tabs,activeId:kind==='delegate'?'delegates':'subtasks',paneOpen:true,onSelect:id=>setKind(id==='delegates'?'delegate':'subtask'),ariaLabel:'Executions',renderLabel:t=>i18n.t(t.labelKey,{ns:'detail'}),appearClass:()=>''})); } window.setLanguage = async lang => {await i18n.changeLanguage(lang); render();}; const tree = createRoot(document.getElementById('root')); const client = new QueryClient({defaultOptions:{queries:{retry:false}}}); function render(){ tree.render(React.createElement(QueryClientProvider,{client},React.createElement(TaskExecutionsProvider,{taskId:1},React.createElement(App)))); } render(); `); vite = await createViteServer({ root: ui, appType: 'custom', logLevel: 'error', server: { middlewareMode: true, hmr: false, fs: { allow: [root, '/tmp'] } } }); const executions = [ ...Array.from({ length: 80 }, (_, i) => ({ id: `delegate:j:d${i}`, kind: 'delegate', jobId: 'j', rootJobId: 'j', title: `Research ${i + 1}`, status: 'success', createdAt: '2026-09-08T00:00:00Z' })), { id: 'subtask:k', kind: 'subtask', jobId: 'k', rootJobId: 'j', title: 'Implementation', status: 'succeeded', createdAt: '2026-09-08T00:01:00Z' }, ]; const conversationRequests = []; server = createServer(async (req, res) => { if (req.url.startsWith('/api/')) { let payload = { available: false, active: false }; if (req.url.endsWith('/executions')) payload = { executions, truncated: false }; if (req.url.endsWith('/conversation')) { conversationRequests.push(req.url); payload = { source: 'history', truncated: false, messages: [ { role: 'user', content: 'Inspect the example files.' }, { role: 'assistant', content: '**Readable result**', thinking: 'Recorded thinking details', llm: { durationMs: 1800, promptTokens: 400, completionTokens: 60 }, toolCalls: [{ id: 'call1', name: 'Read', arguments: '{"path":"sample.txt"}' }] }, { role: 'tool', toolCallId: 'call1', content: 'Recorded tool result', isError: true, durationMs: 1234, cacheHit: false }, ] }; } res.setHeader('content-type', 'application/json'); res.end(JSON.stringify(payload)); return; } if (req.url === '/') { res.setHeader('content-type', 'text/html'); res.end(await vite.transformIndexHtml('/', `
`)); return; } vite.middlewares(req, res); }); server.listen(0, '127.0.0.1'); await once(server, 'listening'); browser = await chromium.launch({ headless: true, args: ['--no-sandbox', '--disable-dev-shm-usage'] }); const page = await browser.newPage({ viewport: { width: 600, height: 700 } }); await page.goto(`http://127.0.0.1:${server.address().port}/`); await page.getByText('Readable result', { exact: true }).waitFor(); assert.equal(conversationRequests.length, 1, 'only the selected conversation is fetched'); for (const lang of ['ja', 'en']) { await page.evaluate(lang => window.setLanguage(lang), lang); for (const width of [320, 600, 900]) { await page.setViewportSize({ width, height: 700 }); await page.evaluate(() => document.fonts.ready); for (const kind of ['delegates', 'subtasks']) { await page.getByTestId(`space-chat-tab-${kind}`).click(); await page.getByText('Readable result', { exact: true }).waitFor(); const panel = page.getByTestId(kind === 'delegates' ? 'delegate-conversations' : 'subtask-conversations'); assert.equal(await panel.getByRole('tablist').getAttribute('aria-orientation'), 'vertical'); if (kind === 'delegates') { const tab = page.getByRole('tab', { name: /^Research 1 / }); await tab.click(); await tab.focus(); await page.keyboard.press('End'); assert.equal(await page.getByRole('tab', { name: /^Research 80 / }).getAttribute('aria-selected'), 'true'); await page.getByRole('searchbox').fill('Research 79'); assert.equal(await panel.getByRole('tab').count(), 1); await page.getByRole('tab', { name: /^Research 79 / }).click(); await page.getByRole('searchbox').fill(''); assert.equal(await page.getByRole('tab', { name: /^Research 79 / }).getAttribute('aria-selected'), 'true'); } await page.getByText('Readable result', { exact: true }).waitFor(); const thinking = page.getByRole('button', { name: lang === 'ja' ? '思考' : 'Thinking', exact: true }); await thinking.click(); await page.getByText('Recorded thinking details', { exact: true }).waitFor(); assert.equal(await page.getByText('1.8s', { exact: true }).count(), 1); assert.equal(await page.getByText('Recorded tool result', { exact: true }).count(), 0); await page.getByRole('button', { name: /Read.*sample/ }).click(); await page.getByText('Recorded tool result', { exact: true }).waitFor(); await page.getByRole('button', { name: lang === 'ja' ? '実行一覧を隠す' : 'Hide execution list' }).click(); assert.equal(await panel.getByRole('tablist').count(), 0); assert(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth), `${lang}/${width}/${kind}: page overflow`); await page.getByRole('button', { name: lang === 'ja' ? '実行一覧を表示' : 'Show execution list' }).click(); assert(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth), `${lang}/${width}/${kind}: list overflow`); } } } if (process.env.SCREENSHOT_PATH) await page.screenshot({ path: process.env.SCREENSHOT_PATH }); console.log('execution conversations: 12 language/width/kind cases, 80-run search, keyboard, thinking, tool failure and collapse passed'); } finally { await browser?.close(); await vite?.close(); server?.close(); await unlink(fixture).catch(() => {}); }