Consolidate approved trial client updates and preserve composer runtime

This commit is contained in:
鲨鱼辣椒 committed 2026-10-13 20:11:01 +08:00
1 parent 154a21a295
commit 020b36673d
32 files changed
+3668 -947

No files matched your search

+81 -5
View File
@@ -12,6 +12,36 @@ test('personal cloud Agent identity, configuration and separate published chat i
await expect(page.getByTestId('ai-module-selection-page')).toBeVisible();
const applicationUrl = page.url();
await page.goto('about:blank');
// Exercise the shared voice path without requesting a real microphone.
await page.addInitScript(() => {
class FixtureMediaRecorder {
static isTypeSupported() { return true; }
state = 'inactive';
mimeType = 'audio/webm';
ondataavailable: ((event: { data: Blob }) => void) | null = null;
onstop: (() => void) | null = null;
start() { this.state = 'recording'; }
stop() {
this.state = 'inactive';
this.ondataavailable?.({ data: new Blob(['fixture voice'], { type: 'audio/webm' }) });
this.onstop?.();
}
}
class FixtureAudioContext {
async decodeAudioData() { return { duration: 0.1 }; }
async close() { /* The fixture does not open an audio device. */ }
}
class FixtureOfflineAudioContext {
destination = {};
createBufferSource() { return { buffer: null, connect() {}, start() {} }; }
async startRendering() { return { getChannelData: () => new Float32Array(1600) }; }
}
Object.defineProperty(navigator, 'mediaDevices', { configurable: true,
value: { getUserMedia: async () => ({ getTracks: () => [{ stop() {} }] }) } });
Object.defineProperty(window, 'MediaRecorder', { configurable: true, value: FixtureMediaRecorder });
Object.defineProperty(window, 'AudioContext', { configurable: true, value: FixtureAudioContext });
Object.defineProperty(window, 'OfflineAudioContext', { configurable: true, value: FixtureOfflineAudioContext });
});
// This UI test owns the Host API boundary; real cloud HTTP/PG is tested separately.
await app.evaluate(({ ipcMain, BrowserWindow }, uploadedAvatar) => {
BrowserWindow.getAllWindows()[0].setSize(1366, 768);
@@ -21,7 +51,8 @@ test('personal cloud Agent identity, configuration and separate published chat i
const creationInputs: Record<string, unknown>[] = [];
let pending: Record<string, unknown>[] = [];
const operations: string[] = [];
Reflect.set(globalThis, '__cloudAgentUiFixture', { snapshot: () => ({ draft, createInput, creationInputs, operations }) });
let speechRequests = 0;
Reflect.set(globalThis, '__cloudAgentUiFixture', { snapshot: () => ({ draft, createInput, creationInputs, operations, speechRequests }) });
const versions: { version: number; draft_revision: number; created_at: string }[] = [];
let knowledgeCatalogLoads = 0;
let knowledgeCreated = false;
@@ -46,6 +77,16 @@ test('personal cloud Agent identity, configuration and separate published chat i
if (path === '/api/works/user/agent-profile') return result({
success: true, profile: { display_name: '创作者', age: null, gender: null, avatar_url: null, share_age_with_agents: false, share_gender_with_agents: false, analysis_enabled: true, completed: true, version: 1, updated_at: '2026-09-10T00:00:00Z' },
});
if (path === '/api/works/speech/transcriptions') {
const speech = JSON.parse(request.body ?? '{}');
if (speech.mimeType !== 'audio/wav' || speech.fileName !== 'voice.wav' || speech.language !== 'zh'
|| typeof speech.audioBase64 !== 'string' || !speech.audioBase64.startsWith('UklGR')) {
throw new Error('Unexpected speech transcription fixture payload');
}
speechRequests++;
await new Promise(resolve => setTimeout(resolve, 800));
return result({ success: true, transcription: { text: '加上语音描述', model: 'fixture-transcribe' } });
}
if (path.startsWith('/api/cloud-agents/')) {
const body = typeof request.body === 'string' ? JSON.parse(request.body) : request.body ?? {};
if (path.endsWith('/avatars/pick')) return result(uploadedAvatar);
@@ -189,9 +230,9 @@ test('personal cloud Agent identity, configuration and separate published chat i
await page.getByRole('button', { name: '下一步', exact: true }).click();
await expect(page.getByLabel('名称', { exact: true })).toBeDisabled();
await expect(page.getByRole('button', { name: '更换头像', exact: true })).toBeDisabled();
await expect(page.getByRole('complementary', { name: '未确认的云端操作' })).toBeVisible();
await page.getByRole('button', { name: '移除提醒', exact: true }).click();
await expect(page.getByRole('complementary', { name: '未确认的云端操作' })).toHaveCount(0);
await expect(page.getByRole('button', { name: '返回列表确认', exact: true })).toBeVisible();
await expect(page.getByRole('dialog', { name: '创建记录', exact: true })).toHaveCount(0);
await page.getByRole('button', { name: '重试创建', exact: true }).click();
await expect(page.getByRole('alert')).toContainText('创建结果暂未确认');
await expect(page.getByLabel('名称', { exact: true })).toBeDisabled();
@@ -306,6 +347,41 @@ test('personal cloud Agent identity, configuration and separate published chat i
expect(await messageOperations()).toEqual([]);
await expect(formalChat.getByRole('heading', { name: 'Hi,今天想聊些什么?', exact: true })).toBeVisible();
await expect(formalChat.locator('.agent-chat-welcome').getByRole('img', { name: '我的写作搭档头像', exact: true })).toHaveAttribute('src', avatar.icon_url);
const voiceComposer = formalChat.getByTestId('cloud-chat-composer');
const voiceDraft = formalChat.getByLabel('消息', { exact: true });
await expect(voiceComposer.locator('.agent-attachment-composer')).toHaveCount(1);
await expect(voiceComposer.getByRole('button', { name: '语音输入', exact: true })).toHaveClass(/rounded-full/);
await expect(voiceDraft).toHaveAttribute('rows', '1');
const sendGeometry = await voiceComposer.getByRole('button', { name: '发送', exact: true }).evaluate(element => {
const style = getComputedStyle(element);
return { width: element.clientWidth, height: element.clientHeight, radius: Number.parseFloat(style.borderTopLeftRadius) };
});
expect(sendGeometry.width).toBe(32);
expect(sendGeometry.height).toBe(32);
expect(sendGeometry.radius).toBeGreaterThanOrEqual(sendGeometry.width / 2);
const toolbarPositions = await voiceComposer.locator('[aria-label="添加文件或图片"], [aria-label="语音输入"], [aria-label="发送"]').evaluateAll(elements =>
Object.fromEntries(elements.map(element => {
const bounds = element.getBoundingClientRect();
return [element.getAttribute('aria-label'), { left: bounds.left, right: bounds.right }];
})));
expect(toolbarPositions['语音输入'].left).toBeGreaterThan(toolbarPositions['添加文件或图片'].right);
expect(toolbarPositions['发送'].left).toBeGreaterThan(toolbarPositions['语音输入'].right);
expect(toolbarPositions['发送'].left - toolbarPositions['语音输入'].right).toBeCloseTo(4, 1);
await voiceDraft.fill('原来的想法');
await voiceComposer.getByRole('button', { name: '语音输入', exact: true }).click();
await expect(voiceComposer.getByRole('button', { name: '停止语音输入' })).toBeVisible();
await voiceDraft.fill('录音时继续编辑');
await expect(voiceComposer.getByRole('button', { name: '发送', exact: true })).toBeDisabled();
await voiceDraft.press('Enter');
await voiceComposer.getByRole('button', { name: '停止语音输入' }).click();
await expect(voiceComposer.getByRole('button', { name: '正在识别语音' })).toBeDisabled();
await voiceDraft.fill('识别时继续编辑');
await expect(voiceDraft).toHaveValue('识别时继续编辑\n加上语音描述');
await expect(voiceDraft).toHaveAttribute('rows', '2');
expect((await fixtureState()).speechRequests).toBe(1);
expect(await messageOperations()).toEqual([]);
await page.screenshot({ path: testInfo.outputPath('cloud-agent-published-voice-composer.png') });
await voiceDraft.fill('');
const expectCleanChat = async () => {
await expect(page.getByRole('navigation', { name: '对话来源', exact: true })).toHaveCount(0);
await expect(page.getByRole('textbox', { name: '名称', exact: true })).toHaveCount(0);
@@ -331,10 +407,10 @@ test('personal cloud Agent identity, configuration and separate published chat i
const userBubble = formalChat.locator('.agent-chat-message[data-role="user"]');
await expect(userBubble).toHaveText(/先给我一些创作建议/);
await expect(userBubble.locator('time')).toHaveAttribute('datetime', '2026-10-13T09:24:30.000Z');
await expect(userBubble.locator('time')).toHaveAccessibleName(/^发送时间:/);
await expect(userBubble.locator('time')).toHaveAttribute('aria-label', /^发送时间:/);
const answerTime = formalChat.locator('.agent-chat-message[data-role="assistant"] time');
await expect(answerTime).toHaveAttribute('datetime', '2026-10-13T09:25:40.000Z');
await expect(answerTime).toHaveAccessibleName(/^回复时间:/);
await expect(answerTime).toHaveAttribute('aria-label', /^回复时间:/);
const bubbleBody = userBubble.locator('.agent-chat-message-body');
const bubbleStyle = await bubbleBody.evaluate(element => {
const style = getComputedStyle(element);