Files
wehub-resource-sync 9b395f5cc3
E2E Headed Chrome / e2e-headed (macos-15) (push) Has been cancelled
E2E Headed Chrome / e2e-headed (ubuntu-latest) (push) Has been cancelled
E2E Headed Chrome / e2e-headed (windows-latest) (push) Has been cancelled
CI / build (macos-latest) (push) Has been cancelled
CI / build (ubuntu-latest) (push) Has been cancelled
CI / build (windows-latest) (push) Has been cancelled
CI / unit-test (push) Has been cancelled
CI / bun-test (push) Has been cancelled
CI / adapter-test (push) Has been cancelled
CI / smoke-test (macos-latest) (push) Has been cancelled
CI / smoke-test (ubuntu-latest) (push) Has been cancelled
Security Audit / audit (push) Has been cancelled
Build Chrome Extension / build (push) Has been cancelled
Trigger Website Rebuild (Docs Updated) / dispatch (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:39:48 +08:00

182 lines
11 KiB
JavaScript

import { beforeEach, describe, expect, it, vi } from 'vitest';
const { mockGetCurrentGeminiUrl, mockReadGeminiSnapshot, mockSelectGeminiTool, mockSendGeminiMessage, mockStartNewGeminiChat, mockWaitForGeminiSubmission, mockWaitForGeminiConfirmButton, mockGetLatestGeminiAssistantResponse, } = vi.hoisted(() => ({
mockGetCurrentGeminiUrl: vi.fn(),
mockReadGeminiSnapshot: vi.fn(),
mockSelectGeminiTool: vi.fn(),
mockSendGeminiMessage: vi.fn(),
mockStartNewGeminiChat: vi.fn(),
mockWaitForGeminiSubmission: vi.fn(),
mockWaitForGeminiConfirmButton: vi.fn(),
mockGetLatestGeminiAssistantResponse: vi.fn(),
}));
vi.mock('./utils.js', () => ({
GEMINI_DOMAIN: 'gemini.google.com',
GEMINI_APP_URL: 'https://gemini.google.com/app',
GEMINI_DEEP_RESEARCH_DEFAULT_TOOL_LABELS: ['Deep Research', 'Deep research', '\u6df1\u5ea6\u7814\u7a76'],
GEMINI_DEEP_RESEARCH_DEFAULT_CONFIRM_LABELS: [
'Start research',
'Start Research',
'Start deep research',
'Start Deep Research',
'Generate research plan',
'Generate Research Plan',
'Generate deep research plan',
'Generate Deep Research Plan',
'\u5f00\u59cb\u7814\u7a76',
'\u5f00\u59cb\u6df1\u5ea6\u7814\u7a76',
'\u5f00\u59cb\u8c03\u7814',
'\u751f\u6210\u7814\u7a76\u8ba1\u5212',
'\u751f\u6210\u8c03\u7814\u8ba1\u5212',
],
resolveGeminiLabels: (value, fallback) => {
const label = String(value ?? '').trim();
return label ? [label] : fallback;
},
getCurrentGeminiUrl: mockGetCurrentGeminiUrl,
getLatestGeminiAssistantResponse: mockGetLatestGeminiAssistantResponse,
readGeminiSnapshot: mockReadGeminiSnapshot,
selectGeminiTool: mockSelectGeminiTool,
sendGeminiMessage: mockSendGeminiMessage,
startNewGeminiChat: mockStartNewGeminiChat,
waitForGeminiSubmission: mockWaitForGeminiSubmission,
waitForGeminiConfirmButton: mockWaitForGeminiConfirmButton,
}));
import { deepResearchCommand } from './deep-research.js';
describe('gemini/deep-research', () => {
const page = {};
const runCommand = (kwargs) => deepResearchCommand.func(page, { timeout: 180, ...kwargs });
beforeEach(() => {
vi.clearAllMocks();
mockGetCurrentGeminiUrl.mockResolvedValue('https://gemini.google.com/app/chat');
mockReadGeminiSnapshot.mockResolvedValue({
turns: [],
transcriptLines: [],
composerHasText: false,
isGenerating: false,
structuredTurnsTrusted: true,
});
mockSelectGeminiTool.mockResolvedValue('Deep Research');
mockSendGeminiMessage.mockResolvedValue();
mockStartNewGeminiChat.mockResolvedValue();
mockWaitForGeminiSubmission.mockResolvedValue({
snapshot: { turns: [], transcriptLines: [], composerHasText: false, isGenerating: false, structuredTurnsTrusted: true },
preSendAssistantCount: 0,
userAnchorTurn: null,
reason: 'user_turn',
});
mockWaitForGeminiConfirmButton.mockResolvedValue('Start research');
mockGetLatestGeminiAssistantResponse.mockResolvedValue('');
});
it('starts a new chat by default, then sends prompt and confirms deep research', async () => {
const result = await runCommand({ prompt: 'research this topic' });
expect(mockStartNewGeminiChat).toHaveBeenCalledTimes(1);
expect(mockSelectGeminiTool).toHaveBeenCalledTimes(1);
expect(mockSendGeminiMessage).toHaveBeenCalledWith(page, 'research this topic');
expect(mockWaitForGeminiSubmission).toHaveBeenCalledTimes(1);
expect(mockWaitForGeminiConfirmButton).toHaveBeenCalledWith(page, expect.arrayContaining(['Start research', 'Start deep research', 'Generate research plan', '\u751f\u6210\u7814\u7a76\u8ba1\u5212']), 180);
expect(result).toEqual([{ status: 'started', url: 'https://gemini.google.com/app/chat' }]);
});
it('returns tool-not-found when the tool cannot be selected', async () => {
mockSelectGeminiTool.mockResolvedValue('');
const result = await runCommand({ prompt: 'research this topic' });
expect(result).toEqual([{ status: 'tool-not-found', url: 'https://gemini.google.com/app/chat' }]);
expect(mockSendGeminiMessage).not.toHaveBeenCalled();
expect(mockWaitForGeminiSubmission).not.toHaveBeenCalled();
expect(mockWaitForGeminiConfirmButton).not.toHaveBeenCalled();
});
it('retries send once when first submission cannot be confirmed', async () => {
mockWaitForGeminiSubmission.mockResolvedValueOnce(null).mockResolvedValueOnce({
snapshot: { turns: [], transcriptLines: [], composerHasText: false, isGenerating: false, structuredTurnsTrusted: true },
preSendAssistantCount: 0,
userAnchorTurn: null,
reason: 'user_turn',
});
const result = await runCommand({ prompt: 'research this topic' });
expect(mockSelectGeminiTool).toHaveBeenCalledTimes(2);
expect(mockReadGeminiSnapshot).toHaveBeenCalledTimes(2);
expect(mockSendGeminiMessage).toHaveBeenCalledTimes(2);
expect(mockWaitForGeminiSubmission).toHaveBeenCalledTimes(2);
expect(result).toEqual([{ status: 'started', url: 'https://gemini.google.com/app/chat' }]);
});
it('returns submit-not-found when submission cannot be confirmed after retry', async () => {
mockWaitForGeminiSubmission.mockResolvedValue(null);
const result = await runCommand({ prompt: 'research this topic' });
expect(mockSelectGeminiTool).toHaveBeenCalledTimes(2);
expect(mockSendGeminiMessage).toHaveBeenCalledTimes(2);
expect(mockWaitForGeminiConfirmButton).not.toHaveBeenCalled();
expect(result).toEqual([{ status: 'submit-not-found', url: 'https://gemini.google.com/app/chat' }]);
});
it('returns confirm-not-found when no confirm button is found', async () => {
mockWaitForGeminiConfirmButton.mockResolvedValue('');
const result = await runCommand({ prompt: 'research this topic' });
expect(result).toEqual([{ status: 'confirm-not-found', url: 'https://gemini.google.com/app/chat' }]);
});
it('returns started when confirm is missing but research appears to be running', async () => {
mockWaitForGeminiConfirmButton.mockResolvedValue('');
mockGetCurrentGeminiUrl.mockResolvedValue('https://gemini.google.com/app/abc123');
mockGetLatestGeminiAssistantResponse.mockResolvedValue('Researching websites now');
const result = await runCommand({ prompt: 'research this topic' });
expect(result).toEqual([{ status: 'started', url: 'https://gemini.google.com/app/abc123' }]);
});
it('does not treat conversation url alone as started when confirm is missing', async () => {
mockWaitForGeminiConfirmButton.mockResolvedValue('');
mockGetCurrentGeminiUrl.mockResolvedValue('https://gemini.google.com/app/abc999');
mockGetLatestGeminiAssistantResponse.mockResolvedValue('I drafted a plan. Start research');
const result = await runCommand({ prompt: 'research this topic' });
expect(result).toEqual([{ status: 'confirm-not-found', url: 'https://gemini.google.com/app/abc999' }]);
});
it('retries once when stuck on root app URL and starts successfully on second confirm', async () => {
mockWaitForGeminiConfirmButton.mockResolvedValueOnce('').mockResolvedValueOnce('Start research');
mockGetCurrentGeminiUrl.mockResolvedValueOnce('https://gemini.google.com/app').mockResolvedValueOnce('https://gemini.google.com/app/retry123');
const result = await runCommand({ prompt: 'research this topic', timeout: 20 });
expect(mockSelectGeminiTool).toHaveBeenCalledTimes(2);
expect(mockSendGeminiMessage).toHaveBeenCalledTimes(1);
expect(mockWaitForGeminiConfirmButton).toHaveBeenCalledTimes(2);
expect(result).toEqual([{ status: 'started', url: 'https://gemini.google.com/app/retry123' }]);
});
it('treats root-url confirm as false-positive and retries', async () => {
mockWaitForGeminiConfirmButton.mockResolvedValueOnce('Start research').mockResolvedValueOnce('Start research');
mockGetCurrentGeminiUrl.mockResolvedValueOnce('https://gemini.google.com/app').mockResolvedValueOnce('https://gemini.google.com/app/retry456');
const result = await runCommand({ prompt: 'research this topic', timeout: 20 });
expect(mockSelectGeminiTool).toHaveBeenCalledTimes(2);
expect(mockSendGeminiMessage).toHaveBeenCalledTimes(1);
expect(result).toEqual([{ status: 'started', url: 'https://gemini.google.com/app/retry456' }]);
});
it('does not resend prompt during root-url retry to avoid duplicate chats', async () => {
mockWaitForGeminiConfirmButton.mockResolvedValueOnce('Start research').mockResolvedValueOnce('');
mockGetCurrentGeminiUrl.mockResolvedValueOnce('https://gemini.google.com/app').mockResolvedValueOnce('https://gemini.google.com/app');
mockGetLatestGeminiAssistantResponse.mockResolvedValue('');
const result = await runCommand({ prompt: 'research this topic', timeout: 20 });
expect(mockSelectGeminiTool).toHaveBeenCalledTimes(2);
expect(mockSendGeminiMessage).toHaveBeenCalledTimes(1);
expect(mockWaitForGeminiConfirmButton).toHaveBeenCalledTimes(2);
expect(result).toEqual([{ status: 'confirm-not-found', url: 'https://gemini.google.com/app' }]);
});
it('attempts one more confirm click when still waiting for start research', async () => {
mockWaitForGeminiConfirmButton
.mockResolvedValueOnce('')
.mockResolvedValueOnce('Start research');
mockGetCurrentGeminiUrl
.mockResolvedValueOnce('https://gemini.google.com/app/xyz123')
.mockResolvedValueOnce('https://gemini.google.com/app/xyz123');
mockGetLatestGeminiAssistantResponse
.mockResolvedValueOnce('I drafted a plan. Start research')
.mockResolvedValueOnce('Researching websites now');
const result = await runCommand({ prompt: 'research this topic', timeout: 20 });
expect(mockWaitForGeminiConfirmButton).toHaveBeenCalledTimes(2);
expect(mockWaitForGeminiConfirmButton).toHaveBeenNthCalledWith(2, page, expect.arrayContaining(['Start research', 'Start deep research', '开始研究', '开始深度研究']), 8);
expect(mockSendGeminiMessage).toHaveBeenCalledTimes(1);
expect(result).toEqual([{ status: 'started', url: 'https://gemini.google.com/app/xyz123' }]);
});
it('uses custom tool/confirm labels when provided', async () => {
await runCommand({
prompt: 'research this topic',
tool: 'Custom Tool',
confirm: 'Custom Confirm',
timeout: 42,
});
expect(mockSelectGeminiTool).toHaveBeenCalledWith(page, ['Custom Tool']);
expect(mockWaitForGeminiConfirmButton).toHaveBeenCalledWith(page, ['Custom Confirm'], 42);
});
});