This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
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('/', `<!doctype html><html><head><meta charset="utf-8"></head><body><div id="root"></div><script type="module" src="/@fs${fixture}"></script></body></html>`)); 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(() => {});
|
||||
}
|
||||
Reference in New Issue
Block a user