Files
makelore/tests/e2e/cloud-agents.spec.ts

321 lines
27 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { closeElectronApp, expect, getStableWindow, test } from './fixtures/electron';
test('personal cloud Agent creation, draft save and leave protection in Electron', async ({ launchElectronApp }, testInfo) => {
const app = await launchElectronApp({ skipSetup: true });
try {
const page = await getStableWindow(app);
await expect(page.getByTestId('ai-module-selection-page')).toBeVisible();
const applicationUrl = page.url();
await page.goto('about:blank');
// This UI test owns the Host API boundary; real cloud HTTP/PG is tested separately.
await app.evaluate(({ ipcMain, BrowserWindow }) => {
BrowserWindow.getAllWindows()[0].setSize(1366, 768);
let draft: Record<string, unknown> | null = null;
let recent: unknown = null;
const versions: { version: number; draft_revision: number; created_at: string }[] = [];
let knowledgeCatalogLoads = 0;
let knowledgeCreated = false;
let knowledgeUploaded = false;
let knowledgeStage = -1;
const knowledgeFile = { file_id: 'document', name: '本体Ontology学习路线图.pdf', size: 34000, error: null };
let limits: { request_limit_points: string | null; daily_limit_points: string | null } = { request_limit_points: null, daily_limit_points: null };
const jobs: Record<string, unknown>[] = [];
const configuration = { model: '', tools: [], knowledges: [], mcps: [], skills: [], preload_skills: [], subagents: [], tool_approval_mode: 'default', max_execution_steps: 40, max_output_tokens: 4096, max_run_seconds: 600 };
const result = (json: unknown) => ({ ok: true, data: { status: 200, ok: true, json } });
ipcMain.removeHandler('hostapi:fetch');
ipcMain.handle('hostapi:fetch', async (_event, request: { path?: string; method?: string; body?: string }) => {
const path = request.path ?? '';
const method = request.method ?? 'GET';
if (path === '/api/auth/session/sync') return result({
success: true, session: { accessToken: 'ui-fixture', tokenType: 'Bearer', expiresAt: Date.now() + 3600000, lastActiveAt: Date.now(), canRefresh: false },
});
if (path === '/api/auth/me') return result({
success: true, user: { username: 'creator', userId: 'creator', tenantId: null, deptId: null, authorities: [] },
moduleAccess: { programming: true, design: true, robot: true, cloud_agents: true },
});
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.startsWith('/api/cloud-agents/')) {
const body = typeof request.body === 'string' ? JSON.parse(request.body) : request.body ?? {};
if (path.endsWith('/knowledge/pick')) {
knowledgeUploaded = true;
return result({ ...knowledgeFile, status: 'uploaded', chunk_count: 0, available: false, processing_task: null });
}
if (path.endsWith('/recovery')) return result({ recent, pending: [] });
if (path.endsWith('/recovery/recent')) { recent = body; return result({ saved: true }); }
if (path.endsWith('/actions')) {
const input = body.input ?? {};
if (body.operation === 'catalog') return result({ models: [{ id: 'test-model', name: '创作模型' }], resources: {}, pricing: null });
if (body.operation === 'knowledge') return result({ databases: knowledgeCreated ? [{ kb_id: 'kb', name: '我的创作资料' }] : [], models: ++knowledgeCatalogLoads === 1 ? [] : [{ id: 'fixture:test-embedding', name: '资料向量模型', dimension: 1024 }] });
if (body.operation === 'createKnowledge') { knowledgeCreated = true; return result({ kb_id: 'kb', name: '我的创作资料' }); }
if (body.operation === 'processKnowledge') { knowledgeStage = 0; return result({ task_id: 'process-document' }); }
if (body.operation === 'knowledgeFiles') {
const stage = knowledgeStage;
if (knowledgeStage >= 0) knowledgeStage++;
return result({ next_offset: null, files: knowledgeUploaded ? [
{ ...knowledgeFile, status: stage < 1 ? 'uploaded' : stage === 1 ? 'parsing' : stage === 2 ? 'indexing' : 'indexed',
available: stage >= 3, chunk_count: stage >= 3 ? 18 : 0,
processing_task: stage < 0 ? null : { task_id: 'process-document', status: stage === 0 ? 'pending' : stage < 3 ? 'running' : 'success', error: null } },
{ file_id: 'empty', name: '扫描文档.pdf', size: 2000, status: 'indexed', available: false, chunk_count: 0, error: null },
] : [] });
}
if (body.operation === 'resources') return result({ mcps: [], skills: [], subagents: [] });
if (body.operation === 'budget' || body.operation === 'saveBudget') {
if (body.operation === 'saveBudget') limits = input;
return result({ agent_slug: draft?.slug, ...limits, daily_committed_points: '0.00', timezone: 'Asia/Shanghai', unit: '词元点数', resets_at: '2026-09-11T00:00:00+08:00' });
}
if (body.operation === 'scheduleContext') return result({ version: 1, enabled: true, configuration, result_destination: '任务历史对话与活动', payer: 'creator' });
if (body.operation === 'channelSelfCallers') return result({ items: [] });
if (body.operation === 'channelBindings') return result({ items: [], available_accounts: [] });
if (body.operation === 'threads') return result({ threads: [], next_offset: null });
if (body.operation === 'publish') { const version = versions.length + 1; if (draft) draft.published_version = version; versions.push({ version, draft_revision: input.expected_revision, created_at: new Date().toISOString() }); return result({ version, draft_revision: input.expected_revision }); }
if (body.operation === 'access') return result({ published_version: draft?.published_version, enabled: true, share_url: 'niancode://agents/' + draft?.slug, versions, grants: [], applications: [] });
if (body.operation === 'costs') return result({ items: [], next_offset: null, unit: '词元点数', summary: { count: 0, settled_points: '0.00', pending_points: '0.00' } });
if (body.operation === 'schedules') return result({ jobs });
if (body.operation === 'createSchedule') { const job = { ...input, id: 'job-1', agent_slug: draft?.slug, next_run_at: '2026-09-11T01:00:00Z', runs: [] }; jobs.push(job); return result(job); }
if (body.operation === 'submit' || body.operation === 'preview') return result({ request_id: input.request_id, thread_id: 'thread-1', run_id: 'run-1', version: '1', status: 'dispatched' });
if (body.operation === 'history') return result({ thread_id: 'thread-1', next_offset: null,
queued_requests: [], schedule_proposals: [{ id: 'proposal', name: '每天整理选题', prompt: '每天给我三个适合个人创作的选题', cron_expression: '30 8 * * *', timezone: 'Asia/Shanghai' }],
messages: [{ id: 1, role: 'assistant', content: '## 创作建议\n先从一个真实的观察开始。\n\n' + '记录今天的一次观察,把具体场景、你的感受和想分享的发现写下来。\n\n'.repeat(22) }],
run: { agent_run_id: 'run-1', request_id: 'request-1', thread_id: 'thread-1', status: 'completed', version: '1' } });
if (body.operation === 'viewed') return result({ viewed: true });
if (body.operation === 'attachments') return result({ attachments: [] });
if (body.operation === 'files') return result({ files: [] });
throw new Error('Unexpected cloud fixture operation ' + body.operation);
}
if (method === 'POST') draft = {
slug: 'ml-' + 'a'.repeat(32), name: body.name, purpose: body.purpose, system_prompt: body.purpose,
draft_revision: 1, updated_at: '2026-09-10T00:00:00Z',
configuration, published_version: null, enabled: true,
};
if (method === 'PATCH') draft = { ...draft, name: body.name, purpose: body.purpose, system_prompt: body.system_prompt, configuration: body.configuration, draft_revision: Number(draft?.draft_revision ?? 1) + 1 };
if (method === 'GET' && (path.endsWith('/agents') || path.endsWith('/bootstrap'))) return result({ agents: draft ? [draft] : [], next_cursor: null });
return result(draft);
}
return result({ success: true });
});
});
await page.goto(applicationUrl);
await page.getByTestId('ai-module-option-cloud_agents').click();
await expect(page.getByTestId('sidebar-cloud-agents-navigation')).toBeVisible();
await expect(page.getByTestId('sidebar-robot-navigation')).toHaveCount(0);
await page.getByRole('button', { name: '创建第一个智能体' }).click();
await page.screenshot({ path: testInfo.outputPath('cloud-agent-choose-purpose.png') });
await page.getByRole('button', { name: /故事搭档/ }).click();
await page.getByRole('button', { name: '先给我提示', exact: true }).click();
await page.getByLabel('名称', { exact: true }).fill('我的写作搭档');
await page.getByLabel('用途', { exact: true }).fill('整理灵感,把想法写成清晰的文章');
await page.screenshot({ path: testInfo.outputPath('cloud-agent-create-requirements.png') });
await page.getByRole('button', { name: '下一步:试一试', exact: true }).click();
await expect(page.getByTestId('cloud-agent-editor')).toBeVisible();
await page.getByLabel('特别要求').fill('使用简洁自然的中文。先理解我的目的,再协助梳理结构。');
await expect(page.getByLabel('模型', { exact: true })).toHaveValue('test-model');
await expect(page.getByRole('button', { name: '先给我提示', exact: true })).toHaveAttribute('aria-pressed', 'true');
await page.getByTestId('sidebar-module-switcher-trigger').click();
await expect(page.getByRole('dialog', { name: '未保存的修改' })).toBeVisible();
await page.getByRole('button', { name: '继续编辑', exact: true }).click();
await page.getByRole('button', { name: '保存并开始试用', exact: true }).click();
await expect(page.getByRole('button', { name: '已保存', exact: true })).toBeVisible();
await expect(page.getByTestId('titlebar-page-title')).toHaveText('智能体工作区');
const requirementsTab = page.getByRole('tab', { name: '我的要求', exact: true });
const advancedTab = page.getByRole('tab', { name: '高级设置', exact: true });
await expect(requirementsTab).toHaveAttribute('aria-selected', 'true');
await expect(page.getByRole('navigation', { name: '配置分类' })).toHaveCount(0);
await requirementsTab.focus();
await requirementsTab.press('ArrowRight');
await expect(advancedTab).toBeFocused();
await expect(advancedTab).toHaveAttribute('aria-selected', 'true');
await expect(page.getByRole('tabpanel', { name: '高级设置' })).toBeVisible();
await expect(page.getByLabel('特别要求')).toBeHidden();
await expect(page.getByRole('button', { name: '能力', exact: true })).toHaveAttribute('aria-current', 'page');
await advancedTab.click();
await expect(advancedTab).toHaveAttribute('aria-selected', 'true');
await page.screenshot({ path: testInfo.outputPath('cloud-agent-advanced-tab.png') });
const configurationScroll = page.getByRole('tabpanel').locator('.agent-config-scroll');
await configurationScroll.evaluate(el => { el.scrollTop = el.scrollHeight; });
await expect.poll(() => configurationScroll.evaluate(el => el.scrollTop)).toBeGreaterThan(0);
await requirementsTab.click();
await expect.poll(() => configurationScroll.evaluate(el => el.scrollTop)).toBe(0);
await expect(page.getByLabel('用途', { exact: true })).toBeInViewport();
await page.screenshot({ path: testInfo.outputPath('cloud-agent-requirements-tab.png') });
await advancedTab.click();
await expect.poll(() => configurationScroll.evaluate(el => el.scrollTop)).toBe(0);
await page.getByRole('button', { name: '知识', exact: true }).click();
await page.getByText('管理我的知识文档', { exact: true }).click();
const knowledgeDescription = page.getByText(/上传到知识库的文档可供/);
await expect(knowledgeDescription).toContainText('embedding 向量化由平台承担,不计入用户用量、词元点数或智能体费用上限');
await expect(knowledgeDescription).toContainText('知识处理如触发对话模型调用,仍按原模型实际用量计费');
await page.getByRole('button', { name: '新建知识库', exact: true }).click();
await expect(page.getByLabel('向量模型', { exact: true })).toBeDisabled();
await page.getByText('管理员配置说明', { exact: true }).click();
await expect(page.getByText(/Embedding 只在 Yuxi 配置/)).toBeVisible();
await expect(page.getByText(/Yuxi 后台/)).toContainText('智能体管理 → 模型供应商');
await expect(page.getByText(/Yuxi 后台/)).toContainText('服务地址和 API Key');
await expect(page.getByText(/Works Square 运营后台|one-api/)).toHaveCount(0);
await page.getByLabel('知识库名称', { exact: true }).fill('我的创作资料');
await requirementsTab.click();
await expect(page.getByRole('navigation', { name: '配置分类' })).toHaveCount(0);
await expect(page.getByLabel('特别要求')).toHaveValue('使用简洁自然的中文。先理解我的目的,再协助梳理结构。');
await advancedTab.click();
await expect(page.getByRole('button', { name: '知识', exact: true })).toHaveAttribute('aria-current', 'page');
await expect(page.getByLabel('知识库名称', { exact: true })).toHaveValue('我的创作资料');
await page.screenshot({ path: testInfo.outputPath('knowledge-empty.png') });
await page.getByRole('button', { name: '刷新知识库与模型', exact: true }).click();
await page.getByLabel('向量模型', { exact: true }).selectOption('fixture:test-embedding');
await expect(page.getByLabel('知识库名称', { exact: true })).toHaveValue('我的创作资料');
await expect(page.getByRole('button', { name: '创建并选用', exact: true })).toBeEnabled();
await expect(page.getByText('管理员配置说明', { exact: true })).toHaveCount(0);
await page.getByRole('button', { name: '创建并选用', exact: true }).click();
await expect(page.getByText('上传第一份文档,让智能体用你的资料回答。')).toBeVisible();
await page.getByRole('button', { name: '上传文档', exact: true }).click();
const document = page.getByRole('article', { name: '本体Ontology学习路线图.pdf' });
await expect(document.getByText('等待处理', { exact: true })).toBeVisible();
await expect(page.getByText('没有可用内容', { exact: true })).toBeVisible();
await document.scrollIntoViewIfNeeded();
await page.screenshot({ path: testInfo.outputPath('knowledge-queued.png') });
await expect(document.getByText('正在解析文档', { exact: true })).toBeVisible();
await expect(document.getByText('正在建立索引', { exact: true })).toBeVisible();
await expect(document.getByText('可用于回答', { exact: true })).toBeVisible();
await expect(document.getByText(/18 段可检索内容/)).toBeVisible();
await document.scrollIntoViewIfNeeded();
expect(await document.evaluate(el => el.scrollWidth - el.clientWidth)).toBeLessThanOrEqual(1);
await page.screenshot({ path: testInfo.outputPath('knowledge-ready.png') });
await page.getByRole('tab', { name: '我的要求', exact: true }).click();
const preview = page.getByRole('region', { name: '草稿预览', exact: true });
await preview.getByRole('button', { name: '保存并重新试用', exact: true }).click();
await expect(preview.getByText('当前试用 · 修订 3')).toBeVisible();
await preview.getByRole('button', { name: '你可以怎样帮助我?请举一个例子。', exact: true }).click();
await expect(preview.getByLabel('消息')).toHaveValue('你可以怎样帮助我?请举一个例子。');
await expect(preview.getByRole('heading', { name: '创作建议' })).toHaveCount(0);
await preview.getByLabel('消息').fill('先给我一些创作建议');
await preview.getByLabel('消息').press('Enter');
await expect(preview.getByRole('heading', { name: '创作建议' })).toBeAttached();
const composer = preview.getByTestId('cloud-chat-composer');
const beforeScroll = await composer.boundingBox();
await preview.getByTestId('cloud-chat-messages').evaluate(el => { el.scrollTop = 0; });
await expect.poll(async () => (await composer.boundingBox())?.y).toBe(beforeScroll?.y);
await expect.poll(() => page.getByTestId('main-content').evaluate(el => el.scrollHeight - el.clientHeight)).toBeLessThanOrEqual(1);
expect(beforeScroll!.y + beforeScroll!.height).toBeLessThanOrEqual(await page.evaluate(() => innerHeight));
await preview.getByLabel('消息').fill('保存后继续的问题');
await preview.getByLabel('消息').press('F6');
await expect(page.getByRole('region', { name: '草稿配置', exact: true }).getByRole('button', { name: '展开配置' })).toBeFocused();
await page.getByLabel('特别要求').fill('使用简洁自然的中文。先理解我的目的,再协助梳理结构。补充具体例子。');
await page.getByLabel('特别要求').press('Control+s');
await expect(page.getByText('云端已保存', { exact: true })).toBeVisible();
await expect(preview.getByLabel('消息')).toHaveValue('保存后继续的问题');
await expect(preview.getByRole('heading', { name: '创作建议' })).toBeAttached();
await expect(preview.getByText('当前试用 · 修订 3')).toBeVisible();
const divider = page.getByRole('separator', { name: '调整配置与试用宽度' });
await divider.focus();
const previousRatio = Number(await divider.getAttribute('aria-valuenow'));
await divider.press('ArrowRight');
await expect(divider).toHaveAttribute('aria-valuenow', String(previousRatio + 2));
const handle = await divider.boundingBox();
await page.mouse.move(handle!.x + 4, handle!.y + 80);
await page.mouse.down();
await page.mouse.move(handle!.x - 35, handle!.y + 80);
await page.mouse.up();
expect(Number(await divider.getAttribute('aria-valuenow'))).toBeLessThan(previousRatio + 2);
await page.getByRole('button', { name: '展开试用', exact: true }).click();
await expect(page.getByRole('region', { name: '草稿配置', exact: true })).toBeHidden();
await page.keyboard.press('Escape');
await expect(page.getByRole('region', { name: '草稿配置', exact: true })).toBeVisible();
await page.screenshot({ path: testInfo.outputPath('cloud-agent-editor.png') });
await app.evaluate(({ BrowserWindow }) => { const win = BrowserWindow.getAllWindows()[0]; win.setMinimumSize(800, 600); win.setSize(980, 680); });
await expect(divider).toBeHidden();
await page.getByRole('button', { name: '试用', exact: true }).click();
await expect(preview.getByLabel('消息')).toHaveValue('保存后继续的问题');
await expect.poll(async () => {
const bounds = await composer.boundingBox();
return bounds!.y + bounds!.height - await page.evaluate(() => innerHeight);
}).toBeLessThanOrEqual(0);
await page.screenshot({ path: testInfo.outputPath('cloud-agent-compact.png') });
await app.evaluate(({ BrowserWindow }) => BrowserWindow.getAllWindows()[0].setSize(1366, 768));
for (const factor of [1.25, 1.5]) {
await app.evaluate(({ BrowserWindow }, zoom) => BrowserWindow.getAllWindows()[0].webContents.setZoomFactor(zoom), factor);
if (await page.getByRole('button', { name: '试用', exact: true }).isVisible()) await page.getByRole('button', { name: '试用', exact: true }).click();
await expect.poll(async () => { const bounds = await composer.boundingBox(); return bounds!.y + bounds!.height - await page.evaluate(() => innerHeight); }).toBeLessThanOrEqual(0);
}
await app.evaluate(({ BrowserWindow }) => BrowserWindow.getAllWindows()[0].webContents.setZoomFactor(1));
await preview.getByRole('button', { name: '用最新草稿试用' }).click();
await expect(preview.getByText('当前试用 · 修订 4')).toBeVisible();
await expect(preview.getByLabel('消息')).toHaveValue('保存后继续的问题');
await preview.getByText('对比上一次试用 · 修订 3', { exact: true }).click();
await expect(preview.getByRole('heading', { name: '创作建议' })).toBeVisible();
await preview.getByText('对比上一次试用 · 修订 3', { exact: true }).click();
await page.getByRole('button', { name: '简单易懂', exact: true }).click();
await preview.getByRole('button', { name: '保存并重新试用', exact: true }).click();
await expect(preview.getByText('当前试用 · 修订 5')).toBeVisible();
await expect(preview.getByLabel('消息')).toHaveValue('保存后继续的问题');
await preview.getByLabel('消息').fill('');
await page.getByTestId('sidebar-module-switcher-trigger').click();
await expect(page.getByTestId('ai-module-selection-page')).toBeVisible();
await page.getByTestId('ai-module-option-cloud_agents').click();
await expect(page.getByTestId('cloud-agent-editor')).toBeVisible();
await expect(page.getByLabel('特别要求')).toHaveValue('使用简洁自然的中文。先理解我的目的,再协助梳理结构。补充具体例子。');
await page.getByRole('tab', { name: '高级设置', exact: true }).click();
await page.getByRole('button', { name: '限制', exact: true }).click();
await page.getByLabel('每次任务上限(词元点数)').fill('2.50');
await page.getByLabel('每日上限(词元点数)').fill('10.00');
await advancedTab.press('ArrowLeft');
await expect(requirementsTab).toBeFocused();
await expect(requirementsTab).toHaveAttribute('aria-selected', 'true');
await expect(page.getByLabel('每日上限(词元点数)')).toBeHidden();
await requirementsTab.press('ArrowRight');
await expect(page.getByRole('button', { name: '限制', exact: true })).toHaveAttribute('aria-current', 'page');
await expect(page.getByLabel('每日上限(词元点数)')).toHaveValue('10.00');
await page.getByRole('button', { name: '保存费用上限' }).click();
await expect(page.getByRole('button', { name: '保存费用上限' })).toBeDisabled();
await page.getByRole('button', { name: '使用与接入', exact: true }).click();
await expect(page.getByLabel('应用名称')).toHaveCount(0);
await page.screenshot({ path: testInfo.outputPath('cloud-agent-start-private-use.png') });
await page.getByRole('button', { name: '确认开始使用' }).click();
await expect(page.getByRole('region', { name: '智能体对话', exact: true })).toBeVisible();
await expect(page.getByRole('region', { name: '智能体对话', exact: true }).getByRole('heading', { name: '创作建议' })).toHaveCount(0);
await page.getByRole('button', { name: '编辑', exact: true }).click();
await page.getByLabel('名称', { exact: true }).fill('我的新版写作搭档');
await expect(page.getByRole('button', { name: '应用最新修改', exact: true })).toBeDisabled();
await page.getByRole('button', { name: '保存草稿', exact: true }).click();
await expect(page.getByText('修改已保存,尚未应用', { exact: true })).toBeVisible();
const applyButton = page.getByRole('button', { name: '应用最新修改', exact: true });
await expect(applyButton).toBeInViewport();
await page.screenshot({ path: testInfo.outputPath('cloud-agent-apply-visible.png') });
await app.evaluate(({ BrowserWindow }) => BrowserWindow.getAllWindows()[0].setSize(1024, 768));
await expect(applyButton).toBeInViewport();
await page.screenshot({ path: testInfo.outputPath('cloud-agent-apply-compact.png') });
await applyButton.click();
await expect(page.getByRole('button', { name: '已应用', exact: true })).toBeDisabled();
await expect(page.getByText('修改已保存,尚未应用', { exact: true })).toHaveCount(0);
await app.evaluate(({ BrowserWindow }) => BrowserWindow.getAllWindows()[0].setSize(1366, 768));
await page.getByRole('button', { name: '使用与接入', exact: true }).click();
await expect(page.getByRole('heading', { name: '应用与 API', exact: true })).toBeVisible();
await expect(page.getByRole('button', { name: '打开渠道', exact: true })).toBeVisible();
await expect(page.getByLabel('应用名称')).toBeVisible();
await expect(page.getByRole('heading', { name: '分享给指定用户', exact: true })).toHaveCount(0);
await expect(page.getByLabel('查找分享用户')).toHaveCount(0);
await expect(page.getByRole('button', { name: '复制分享链接', exact: true })).toHaveCount(0);
await page.screenshot({ path: testInfo.outputPath('cloud-agent-access-without-user-sharing.png') });
await page.getByRole('button', { name: '对话', exact: true }).click();
await page.getByRole('region', { name: '智能体对话', exact: true }).getByLabel('消息', { exact: true }).fill('每天早上八点半给我三个创作方向');
await page.getByRole('region', { name: '智能体对话', exact: true }).getByRole('button', { name: '发送', exact: true }).click();
await expect(page.getByRole('region', { name: '智能体对话', exact: true }).getByRole('heading', { name: '创作建议' })).toBeAttached();
await page.getByRole('button', { name: '查看并配置自动任务' }).click();
await expect(page.getByLabel('任务名称')).toHaveValue('每天整理选题');
await expect(page.getByLabel('执行时间')).toHaveValue('08:30');
await expect(page.getByLabel('保存后启用')).not.toBeChecked();
await page.getByLabel('保存后启用').check();
await expect(page.getByText('启用前确认')).toBeVisible();
await page.getByRole('button', { name: '保存并启用' }).click();
await expect(page.getByRole('heading', { name: '每天整理选题' })).toBeVisible();
await expect(page.getByText(/每天 08:30/)).toBeVisible();
await page.getByRole('button', { name: '添加任务', exact: true }).click();
await expect(page.getByLabel('保存后启用')).not.toBeChecked();
await page.getByRole('button', { name: '取消', exact: true }).click();
await page.screenshot({ path: testInfo.outputPath('cloud-agent-schedules.png') });
} finally {
await app.evaluate(({ BrowserWindow }) => { for (const win of BrowserWindow.getAllWindows()) win.destroy(); }).catch(() => undefined);
await closeElectronApp(app);
}
});