test: add PI release proof harness

This commit is contained in:
2026-08-24 13:44:39 +08:00
parent 977445ba45
commit 8820e82530
19 changed files with 1407 additions and 44 deletions

View File

@@ -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);