test: add PI release proof harness
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import type { ElectronApplication, Page } from 'playwright-core';
|
||||
import { appendFile } from 'node:fs/promises';
|
||||
import { expect, getStableWindow, test } from './fixtures/electron';
|
||||
|
||||
type CapturedRequest = {
|
||||
@@ -116,7 +117,7 @@ async function installCodingFirstChatHost(
|
||||
initialized: true,
|
||||
agents: [agent],
|
||||
knowledgeDirectory: 'knowledge',
|
||||
legacyConversationNotice: 'none',
|
||||
legacyConversationNotice: featureComplete ? 'pending' : 'none',
|
||||
createdAt: now,
|
||||
updatedAt: now,
|
||||
};
|
||||
@@ -150,20 +151,42 @@ async function installCodingFirstChatHost(
|
||||
title: conversation.title,
|
||||
model: { model: configuredModel, modelResolution: 'resolved' },
|
||||
},
|
||||
nodes: featureComplete ? [{
|
||||
kind: 'subagent',
|
||||
id: 'subagent-e2e',
|
||||
runId: 'run-e2e-feature',
|
||||
details: {
|
||||
schema: 'subagent.v1',
|
||||
dispatchId: 'dispatch-e2e',
|
||||
mode: 'parallel',
|
||||
tasks: [
|
||||
{ taskId: 'task-reader', agentId: 'reader', toolProfile: 'read-only', status: 'complete', summary: 'Read complete' },
|
||||
{ taskId: 'task-builder', agentId: 'builder', toolProfile: 'coding', status: 'running' },
|
||||
],
|
||||
nodes: featureComplete ? [
|
||||
{
|
||||
kind: 'subagent',
|
||||
id: 'subagent-e2e',
|
||||
runId: 'run-e2e-feature',
|
||||
details: {
|
||||
schema: 'subagent.v1',
|
||||
dispatchId: 'dispatch-e2e',
|
||||
mode: 'parallel',
|
||||
tasks: [
|
||||
{ taskId: 'task-reader', agentId: 'reader', toolProfile: 'read-only', status: 'complete', summary: 'Read complete' },
|
||||
{ taskId: 'task-builder', agentId: 'builder', toolProfile: 'coding', status: 'running' },
|
||||
],
|
||||
},
|
||||
},
|
||||
}] : [],
|
||||
{
|
||||
kind: 'compaction',
|
||||
id: 'compaction-e2e',
|
||||
runId: 'run-e2e-feature',
|
||||
source: 'automatic',
|
||||
status: 'error',
|
||||
willRetry: true,
|
||||
summary: '保留了本轮关键上下文。',
|
||||
},
|
||||
{
|
||||
kind: 'tool',
|
||||
id: 'browser-tool-e2e',
|
||||
toolCallId: 'browser-call-e2e',
|
||||
toolName: 'agent_browser',
|
||||
title: '浏览器状态',
|
||||
inputText: 'status',
|
||||
status: 'complete',
|
||||
output: [{ kind: 'text', id: 'browser-output-e2e', text: 'Browser ready', status: 'complete' }],
|
||||
details: { schema: 'agent-browser.v1', action: 'status' },
|
||||
},
|
||||
] : [],
|
||||
run: featureComplete ? { status: 'running', runId: 'run-e2e-feature', mode: 'prompt' } : { status: 'idle' },
|
||||
queue: { items: featureComplete ? [{ id: 'queue-e2e', clientRequestId: 'request-queued', mode: 'follow-up', text: 'Queued follow-up', attachmentIds: [] }] : [] },
|
||||
context: featureComplete
|
||||
@@ -196,6 +219,11 @@ async function installCodingFirstChatHost(
|
||||
run: { status: 'idle' },
|
||||
queue: { items: [] },
|
||||
pendingInteractions: [],
|
||||
worker: {
|
||||
status: 'error',
|
||||
generation: 1,
|
||||
error: { code: 'CODING_RUNTIME_START_FAILED', message: 'Worker stopped', recoverable: true },
|
||||
},
|
||||
};
|
||||
const respond = (json: unknown, status = 200) => ({
|
||||
ok: true,
|
||||
@@ -294,6 +322,16 @@ async function installCodingFirstChatHost(
|
||||
if (path === `/api/coding/projects/config?projectId=${project.id}`) {
|
||||
return respond({ snapshot: { project, config, knowledgeFiles: [] } });
|
||||
}
|
||||
if (path === '/api/coding/projects/legacy-conversation-notice/acknowledge'
|
||||
&& method === 'POST') {
|
||||
return respond({
|
||||
snapshot: {
|
||||
project,
|
||||
config: { ...config, legacyConversationNotice: 'acknowledged' },
|
||||
knowledgeFiles: [],
|
||||
},
|
||||
});
|
||||
}
|
||||
if (path === `/api/coding/projects/conversations?projectId=${project.id}`) {
|
||||
return respond({
|
||||
conversations: featureComplete
|
||||
@@ -416,15 +454,20 @@ test('first PI Conversation is editable under 500 ms and submits before runtime
|
||||
performance.now() - performance.getEntriesByName('pi-first-chat-start').at(-1)!.startTime
|
||||
));
|
||||
expect(editableMs).toBeLessThan(500);
|
||||
const performanceFragment = process.env.MAKELORE_PI_PERF_COMPOSER_FRAGMENT;
|
||||
if (performanceFragment) {
|
||||
await appendFile(performanceFragment, `${JSON.stringify({ editableMs })}\n`);
|
||||
}
|
||||
|
||||
const pixelPng = Buffer.from(
|
||||
'iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=',
|
||||
'base64',
|
||||
);
|
||||
const largePng = Buffer.concat([pixelPng, Buffer.alloc(1024 * 1024)]);
|
||||
await page.getByTestId('coding-file-attachment-input').setInputFiles({
|
||||
name: 'pixel.png',
|
||||
name: 'large.png',
|
||||
mimeType: 'image/png',
|
||||
buffer: pixelPng,
|
||||
buffer: largePng,
|
||||
});
|
||||
await composer.fill('Build the first PI scene');
|
||||
await expect(page.getByRole('button', { name: '发送' })).toBeEnabled();
|
||||
@@ -453,7 +496,7 @@ test('first PI Conversation is editable under 500 ms and submits before runtime
|
||||
));
|
||||
expect(uploads).toHaveLength(1);
|
||||
expect(uploads[0]).toMatchObject({
|
||||
byteLength: pixelPng.byteLength,
|
||||
byteLength: largePng.byteLength,
|
||||
contentType: 'image/png',
|
||||
});
|
||||
expect(state.captured.some((request) => (
|
||||
@@ -466,7 +509,7 @@ test('first PI Conversation is editable under 500 ms and submits before runtime
|
||||
expect(prompt?.body?.attachments).toEqual([
|
||||
{ attachmentId: expect.any(String) },
|
||||
]);
|
||||
expect(JSON.stringify(state.captured)).not.toContain(pixelPng.toString('base64'));
|
||||
expect(JSON.stringify(prompt?.body)).not.toContain('data:image');
|
||||
} finally {
|
||||
await releaseSnapshot(electronApp);
|
||||
}
|
||||
@@ -494,7 +537,13 @@ test('PI feature UI isolates Conversations and exposes queue, interaction, model
|
||||
await expect(page.getByTestId('coding-conversation-header')).toContainText('生成中');
|
||||
await expect(page.getByTestId('coding-conversation-header')).toContainText('队列 1');
|
||||
await expect(page.getByText('并行子任务')).toBeVisible();
|
||||
await expect(page.getByText('系统会自动重试。')).toBeVisible();
|
||||
await page.locator('[data-node-id="browser-tool-e2e"] > summary').click();
|
||||
await expect(page.getByText('浏览器操作 · status')).toBeVisible();
|
||||
await expect(page.getByText('允许继续?')).toBeVisible();
|
||||
await expect(page.getByTestId('legacy-conversation-notice')).toBeVisible();
|
||||
await page.getByTestId('legacy-conversation-notice').getByRole('button', { name: '知道了' }).click();
|
||||
await expect(page.getByTestId('legacy-conversation-notice')).toHaveCount(0);
|
||||
await page.getByRole('button', { name: '确认', exact: true }).click();
|
||||
|
||||
const mode = page.getByRole('combobox', { name: '消息发送方式' });
|
||||
@@ -517,6 +566,12 @@ test('PI feature UI isolates Conversations and exposes queue, interaction, model
|
||||
JSON.stringify(['account-e2e', 'model-b']),
|
||||
);
|
||||
await expect(page.getByRole('textbox')).toHaveValue('');
|
||||
await page.getByRole('button', { name: '恢复' }).click();
|
||||
await page.getByRole('button', { name: '整理上下文' }).click();
|
||||
|
||||
await page.getByRole('button', { name: '打开编程工具' }).click();
|
||||
await page.getByRole('tab', { name: '技能' }).click();
|
||||
await expect(page.getByRole('dialog')).toContainText('Research');
|
||||
|
||||
await expect(page.getByText(/分享|取消分享|回滚|恢复回滚|待办|全局运行时/)).toHaveCount(0);
|
||||
const state = await readState(electronApp);
|
||||
@@ -525,6 +580,9 @@ test('PI feature UI isolates Conversations and exposes queue, interaction, model
|
||||
expect(state.captured.some((request) => request.path.includes('/interactions/') && request.path.endsWith('/respond'))).toBe(true);
|
||||
expect(state.captured.some((request) => request.path.endsWith('/commands'))).toBe(true);
|
||||
expect(state.captured.some((request) => request.path.endsWith('/changes'))).toBe(true);
|
||||
expect(state.captured.some((request) => request.path.endsWith('/recover') && request.method === 'POST')).toBe(true);
|
||||
expect(state.captured.some((request) => request.path.endsWith('/compact') && request.method === 'POST')).toBe(true);
|
||||
expect(state.captured.some((request) => request.path.includes('legacy-conversation-notice'))).toBe(true);
|
||||
expect(state.captured.every((request) => !request.path.includes('/api/opencode/share'))).toBe(true);
|
||||
} finally {
|
||||
await releaseSnapshot(electronApp);
|
||||
|
||||
Reference in New Issue
Block a user