mirror of
https://github.com/stablyai/orca.git
synced 2026-10-04 00:02:21 +00:00
798 lines
29 KiB
TypeScript
798 lines
29 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
|
import type { AgentBrowserBridge } from '../browser/agent-browser-bridge'
|
|
import { REMOTE_RUNTIME_MAX_OUTBOUND_BINARY_FRAME_BYTES } from '../../shared/remote-runtime-memory-limits'
|
|
import type { RuntimeBrowserCommandHost } from './orca-runtime-browser'
|
|
import { RuntimeBrowserPageRegistry } from './runtime-browser-page-registry'
|
|
|
|
const {
|
|
ipcMainOnMock,
|
|
ipcMainRemoveListenerMock,
|
|
webContentsFromIdMock,
|
|
startBrowserScreencastMock,
|
|
waitForTabRegistrationMock,
|
|
waitForWorktreeTabRegistrationMock,
|
|
browserSessionRegistryMock
|
|
} = vi.hoisted(() => ({
|
|
ipcMainOnMock: vi.fn(),
|
|
ipcMainRemoveListenerMock: vi.fn(),
|
|
webContentsFromIdMock: vi.fn(),
|
|
startBrowserScreencastMock: vi.fn(),
|
|
waitForTabRegistrationMock: vi.fn(),
|
|
waitForWorktreeTabRegistrationMock: vi.fn(),
|
|
browserSessionRegistryMock: {
|
|
profiles: new Map([
|
|
[
|
|
'default',
|
|
{
|
|
id: 'default',
|
|
scope: 'default',
|
|
partition: 'persist:orca-browser',
|
|
label: 'Default',
|
|
source: null
|
|
}
|
|
],
|
|
[
|
|
'profile-isolated',
|
|
{
|
|
id: 'profile-isolated',
|
|
scope: 'isolated',
|
|
partition: 'persist:orca-browser-session-profile-isolated',
|
|
label: 'Isolated',
|
|
source: null
|
|
}
|
|
]
|
|
]),
|
|
getDefaultProfile: vi.fn(),
|
|
getProfile: vi.fn(),
|
|
resolveKnownPartition: vi.fn(),
|
|
createProfile: vi.fn()
|
|
}
|
|
}))
|
|
|
|
vi.mock('electron', () => ({
|
|
ipcMain: { on: ipcMainOnMock, removeListener: ipcMainRemoveListenerMock },
|
|
webContents: { fromId: webContentsFromIdMock }
|
|
}))
|
|
|
|
vi.mock('../browser/browser-screencast-stream', () => ({
|
|
startBrowserScreencast: startBrowserScreencastMock
|
|
}))
|
|
|
|
vi.mock('../ipc/browser-tab-registration-wait', () => ({
|
|
waitForTabRegistration: waitForTabRegistrationMock,
|
|
waitForWorktreeTabRegistration: waitForWorktreeTabRegistrationMock
|
|
}))
|
|
|
|
vi.mock('../browser/browser-session-registry', () => ({
|
|
browserSessionRegistry: browserSessionRegistryMock
|
|
}))
|
|
|
|
function deferred<T>() {
|
|
let resolve!: (value: T) => void
|
|
let reject!: (reason?: unknown) => void
|
|
const promise = new Promise<T>((res, rej) => {
|
|
resolve = res
|
|
reject = rej
|
|
})
|
|
return { promise, resolve, reject }
|
|
}
|
|
|
|
function createHost(overrides: Partial<RuntimeBrowserCommandHost> = {}): RuntimeBrowserCommandHost {
|
|
const runtimeBrowserPages = new RuntimeBrowserPageRegistry()
|
|
const bridge = overrides.getAgentBrowserBridge
|
|
? overrides.getAgentBrowserBridge()
|
|
: ({
|
|
getRegisteredTabs: vi.fn(() => new Map([['page-1', 100]])),
|
|
getActivePageId: vi.fn(() => 'page-1'),
|
|
tabList: vi.fn(() => ({
|
|
tabs: [
|
|
{
|
|
browserPageId: 'page-1',
|
|
index: 0,
|
|
url: 'about:blank',
|
|
title: 'Browser',
|
|
active: true
|
|
}
|
|
]
|
|
}))
|
|
} as unknown as AgentBrowserBridge)
|
|
return {
|
|
resolveWorktreeSelector: async (selector) => ({ id: selector.replace(/^id:/, '') }),
|
|
resolveBrowserWorkspace: async (selector) => ({ id: selector.replace(/^id:/, '') }),
|
|
getRuntimeBrowserPageRegistry: () => runtimeBrowserPages,
|
|
getAuthoritativeWindow: vi.fn(),
|
|
getAvailableAuthoritativeWindow: vi.fn(() => null),
|
|
getOffscreenBrowserBackend: vi.fn(() => null),
|
|
...overrides,
|
|
getAgentBrowserBridge: () => bridge
|
|
} as unknown as RuntimeBrowserCommandHost
|
|
}
|
|
|
|
describe('RuntimeBrowserCommands browser screencast', () => {
|
|
beforeEach(() => {
|
|
ipcMainOnMock.mockReset()
|
|
ipcMainRemoveListenerMock.mockReset()
|
|
webContentsFromIdMock.mockReset()
|
|
startBrowserScreencastMock.mockReset()
|
|
waitForTabRegistrationMock.mockReset()
|
|
waitForTabRegistrationMock.mockResolvedValue(undefined)
|
|
waitForWorktreeTabRegistrationMock.mockReset()
|
|
waitForWorktreeTabRegistrationMock.mockResolvedValue(undefined)
|
|
browserSessionRegistryMock.getDefaultProfile.mockReset()
|
|
browserSessionRegistryMock.getDefaultProfile.mockImplementation(() =>
|
|
browserSessionRegistryMock.profiles.get('default')
|
|
)
|
|
browserSessionRegistryMock.getProfile.mockReset()
|
|
browserSessionRegistryMock.getProfile.mockImplementation(
|
|
(profileId: string) => browserSessionRegistryMock.profiles.get(profileId) ?? null
|
|
)
|
|
browserSessionRegistryMock.resolveKnownPartition.mockReset()
|
|
browserSessionRegistryMock.resolveKnownPartition.mockImplementation(
|
|
(profileId: string | null | undefined) => {
|
|
if (!profileId) {
|
|
return 'persist:orca-browser'
|
|
}
|
|
return browserSessionRegistryMock.profiles.get(profileId)?.partition ?? null
|
|
}
|
|
)
|
|
browserSessionRegistryMock.createProfile.mockReset()
|
|
})
|
|
|
|
it('creates profiles with the requested user-agent mode', async () => {
|
|
const { RuntimeBrowserCommands } = await import('./orca-runtime-browser')
|
|
const profile = {
|
|
id: 'profile-google',
|
|
scope: 'isolated',
|
|
partition: 'persist:orca-browser-session-profile-google',
|
|
label: 'Google',
|
|
source: null,
|
|
userAgentMode: 'native'
|
|
}
|
|
browserSessionRegistryMock.createProfile.mockReturnValue(profile)
|
|
const commands = new RuntimeBrowserCommands(createHost())
|
|
|
|
await expect(
|
|
commands.browserProfileCreate({
|
|
label: 'Google',
|
|
scope: 'isolated',
|
|
userAgentMode: 'native'
|
|
})
|
|
).resolves.toEqual({ profile })
|
|
expect(browserSessionRegistryMock.createProfile).toHaveBeenCalledWith('isolated', 'Google', {
|
|
userAgentMode: 'native'
|
|
})
|
|
})
|
|
|
|
it('waits for explicit worktree browser registration after requesting a hidden mount', async () => {
|
|
const { RuntimeBrowserCommands } = await import('./orca-runtime-browser')
|
|
const send = vi.fn()
|
|
const bridge = {
|
|
getRegisteredTabs: vi.fn(() => new Map()),
|
|
tabList: vi.fn(() => ({ tabs: [] }))
|
|
} as unknown as AgentBrowserBridge
|
|
const commands = new RuntimeBrowserCommands(
|
|
createHost({
|
|
getAgentBrowserBridge: () => bridge,
|
|
getAuthoritativeWindow: vi.fn(() => ({ webContents: { send } }) as never)
|
|
})
|
|
)
|
|
|
|
await commands.browserTabList({ worktree: 'id:wt-1' })
|
|
|
|
expect(send).toHaveBeenCalledWith('browser:activateView', { worktreeId: 'wt-1' })
|
|
expect(waitForWorktreeTabRegistrationMock).toHaveBeenCalledWith('wt-1')
|
|
expect(bridge.tabList).toHaveBeenCalledWith('wt-1')
|
|
})
|
|
|
|
it('re-wakes an explicit worktree when the only registered browser tab is stale', async () => {
|
|
const { RuntimeBrowserCommands } = await import('./orca-runtime-browser')
|
|
webContentsFromIdMock.mockReturnValue({ isDestroyed: () => true })
|
|
const send = vi.fn()
|
|
const bridge = {
|
|
getRegisteredTabs: vi.fn(() => new Map([['page-stale', 404]])),
|
|
tabList: vi.fn(() => ({ tabs: [] }))
|
|
} as unknown as AgentBrowserBridge
|
|
const commands = new RuntimeBrowserCommands(
|
|
createHost({
|
|
getAgentBrowserBridge: () => bridge,
|
|
getAuthoritativeWindow: vi.fn(() => ({ webContents: { send } }) as never)
|
|
})
|
|
)
|
|
|
|
await commands.browserTabList({ worktree: 'id:wt-1' })
|
|
|
|
expect(send).toHaveBeenCalledWith('browser:activateView', { worktreeId: 'wt-1' })
|
|
expect(waitForWorktreeTabRegistrationMock).toHaveBeenCalledWith('wt-1')
|
|
expect(bridge.tabList).toHaveBeenCalledWith('wt-1')
|
|
})
|
|
|
|
it('waits for any browser registration after requesting a hidden mount without worktree scope', async () => {
|
|
const { RuntimeBrowserCommands } = await import('./orca-runtime-browser')
|
|
const send = vi.fn()
|
|
const bridge = {
|
|
getRegisteredTabs: vi.fn(() => new Map()),
|
|
tabList: vi.fn(() => ({ tabs: [] }))
|
|
} as unknown as AgentBrowserBridge
|
|
const commands = new RuntimeBrowserCommands(
|
|
createHost({
|
|
getAgentBrowserBridge: () => bridge,
|
|
getAuthoritativeWindow: vi.fn(() => ({ webContents: { send } }) as never)
|
|
})
|
|
)
|
|
|
|
await commands.browserTabList({})
|
|
|
|
expect(send).toHaveBeenCalledWith('browser:activateView', {})
|
|
expect(waitForWorktreeTabRegistrationMock).toHaveBeenCalledWith(undefined)
|
|
expect(bridge.tabList).toHaveBeenCalledWith(undefined)
|
|
})
|
|
|
|
it('creates the first explicit-worktree browser tab without waiting for an existing registration', async () => {
|
|
const { RuntimeBrowserCommands } = await import('./orca-runtime-browser')
|
|
const webContents = { send: vi.fn() }
|
|
const send = vi.fn((channel: string, data: { requestId: string }) => {
|
|
expect(channel).toBe('browser:requestTabCreate')
|
|
const handler = ipcMainOnMock.mock.calls.find(
|
|
([eventName]) => eventName === 'browser:tabCreateReply'
|
|
)?.[1] as
|
|
| ((
|
|
event: unknown,
|
|
reply: { requestId: string; browserPageId?: string; error?: string }
|
|
) => void)
|
|
| undefined
|
|
handler?.({ sender: { send: vi.fn() } } as never, {
|
|
requestId: data.requestId,
|
|
error: 'spoofed renderer reply'
|
|
})
|
|
handler?.({ sender: webContents } as never, {
|
|
requestId: data.requestId,
|
|
browserPageId: 'page-new'
|
|
})
|
|
})
|
|
webContents.send = send
|
|
const bridge = {
|
|
getRegisteredTabs: vi.fn(() => new Map([['page-new', 101]])),
|
|
getActivePageId: vi.fn(() => 'page-new'),
|
|
setActiveTab: vi.fn(),
|
|
tabList: vi.fn(() => ({ tabs: [] }))
|
|
} as unknown as AgentBrowserBridge
|
|
const commands = new RuntimeBrowserCommands(
|
|
createHost({
|
|
getAgentBrowserBridge: () => bridge,
|
|
getAvailableAuthoritativeWindow: vi.fn(() => ({}) as never),
|
|
getAuthoritativeWindow: vi.fn(() => ({ webContents }) as never)
|
|
})
|
|
)
|
|
|
|
await expect(
|
|
commands.browserTabCreate({ worktree: 'id:wt-1', url: 'about:blank' })
|
|
).resolves.toEqual({ browserPageId: 'page-new' })
|
|
|
|
expect(waitForWorktreeTabRegistrationMock).not.toHaveBeenCalled()
|
|
// Why: with no explicit profile, main must leave sessionProfileId/
|
|
// sessionPartition undefined so the renderer applies the user's configured
|
|
// default-profile inheritance instead of being forced onto the shared
|
|
// default partition.
|
|
expect(send).toHaveBeenCalledWith(
|
|
'browser:requestTabCreate',
|
|
expect.objectContaining({
|
|
url: 'about:blank',
|
|
worktreeId: 'wt-1',
|
|
sessionProfileId: undefined,
|
|
sessionPartition: undefined
|
|
})
|
|
)
|
|
expect(waitForTabRegistrationMock).toHaveBeenCalledWith('page-new')
|
|
expect(bridge.setActiveTab).toHaveBeenCalledWith(101, 'wt-1')
|
|
})
|
|
|
|
it('sends the resolved isolated profile partition when creating a renderer tab', async () => {
|
|
const { RuntimeBrowserCommands } = await import('./orca-runtime-browser')
|
|
const webContents = { send: vi.fn() }
|
|
const send = vi.fn((channel: string, data: { requestId: string }) => {
|
|
expect(channel).toBe('browser:requestTabCreate')
|
|
const handler = ipcMainOnMock.mock.calls.find(
|
|
([eventName]) => eventName === 'browser:tabCreateReply'
|
|
)?.[1] as
|
|
| ((event: unknown, reply: { requestId: string; browserPageId?: string }) => void)
|
|
| undefined
|
|
handler?.({ sender: webContents } as never, {
|
|
requestId: data.requestId,
|
|
browserPageId: 'page-isolated'
|
|
})
|
|
})
|
|
webContents.send = send
|
|
const bridge = {
|
|
getRegisteredTabs: vi.fn(() => new Map([['page-isolated', 101]])),
|
|
getActivePageId: vi.fn(() => 'page-isolated'),
|
|
setActiveTab: vi.fn(),
|
|
tabList: vi.fn(() => ({ tabs: [] }))
|
|
} as unknown as AgentBrowserBridge
|
|
const commands = new RuntimeBrowserCommands(
|
|
createHost({
|
|
getAgentBrowserBridge: () => bridge,
|
|
getAvailableAuthoritativeWindow: vi.fn(() => ({}) as never),
|
|
getAuthoritativeWindow: vi.fn(() => ({ webContents }) as never)
|
|
})
|
|
)
|
|
|
|
await expect(
|
|
commands.browserTabCreate({
|
|
worktree: 'id:wt-1',
|
|
url: 'https://example.com',
|
|
profileId: 'profile-isolated'
|
|
})
|
|
).resolves.toEqual({ browserPageId: 'page-isolated' })
|
|
|
|
expect(send).toHaveBeenCalledWith(
|
|
'browser:requestTabCreate',
|
|
expect.objectContaining({
|
|
sessionProfileId: 'profile-isolated',
|
|
sessionPartition: 'persist:orca-browser-session-profile-isolated'
|
|
})
|
|
)
|
|
})
|
|
|
|
it('returns the renderer page identity before navigation settles', async () => {
|
|
const { RuntimeBrowserCommands } = await import('./orca-runtime-browser')
|
|
const navigation = deferred<{ title: string; url: string }>()
|
|
const webContents = { send: vi.fn() }
|
|
webContents.send = vi.fn((_channel: string, data: { requestId: string }) => {
|
|
const handler = ipcMainOnMock.mock.calls.find(
|
|
([eventName]) => eventName === 'browser:tabCreateReply'
|
|
)?.[1] as
|
|
| ((event: unknown, reply: { requestId: string; browserPageId?: string }) => void)
|
|
| undefined
|
|
handler?.({ sender: webContents } as never, {
|
|
requestId: data.requestId,
|
|
browserPageId: 'page-slow-navigation'
|
|
})
|
|
})
|
|
const bridge = {
|
|
getRegisteredTabs: vi.fn(() => new Map([['page-slow-navigation', 101]])),
|
|
goto: vi.fn(() => navigation.promise),
|
|
setActiveTab: vi.fn()
|
|
} as unknown as AgentBrowserBridge
|
|
const commands = new RuntimeBrowserCommands(
|
|
createHost({
|
|
getAgentBrowserBridge: () => bridge,
|
|
getAvailableAuthoritativeWindow: vi.fn(() => ({}) as never),
|
|
getAuthoritativeWindow: vi.fn(() => ({ webContents }) as never)
|
|
})
|
|
)
|
|
|
|
let created: { browserPageId: string } | null = null
|
|
const creation = commands
|
|
.browserTabCreate({
|
|
worktree: 'id:wt-1',
|
|
url: 'https://example.com/slow',
|
|
waitForRegistration: true
|
|
})
|
|
.then((result) => {
|
|
created = result
|
|
return result
|
|
})
|
|
await vi.waitFor(() => expect(bridge.goto).toHaveBeenCalledOnce())
|
|
await new Promise<void>((resolve) => setImmediate(resolve))
|
|
|
|
try {
|
|
expect(created).toEqual({ browserPageId: 'page-slow-navigation' })
|
|
} finally {
|
|
navigation.resolve({ title: 'Slow page', url: 'https://example.com/slow' })
|
|
await creation
|
|
}
|
|
})
|
|
|
|
it('rejects unknown explicit profile ids before requesting a renderer tab', async () => {
|
|
const { RuntimeBrowserCommands } = await import('./orca-runtime-browser')
|
|
const send = vi.fn()
|
|
const commands = new RuntimeBrowserCommands(
|
|
createHost({
|
|
getAvailableAuthoritativeWindow: vi.fn(() => ({}) as never),
|
|
getAuthoritativeWindow: vi.fn(() => ({ webContents: { send } }) as never)
|
|
})
|
|
)
|
|
|
|
await expect(
|
|
commands.browserTabCreate({
|
|
worktree: 'id:wt-1',
|
|
url: 'https://example.com',
|
|
profileId: 'missing-profile'
|
|
})
|
|
).rejects.toThrow(/Browser profile missing-profile was not found/)
|
|
|
|
expect(send).not.toHaveBeenCalled()
|
|
})
|
|
|
|
it('sends the resolved partition when switching a renderer tab profile', async () => {
|
|
const { RuntimeBrowserCommands } = await import('./orca-runtime-browser')
|
|
const webContents = { send: vi.fn() }
|
|
const send = vi.fn((channel: string, data: { requestId: string }) => {
|
|
expect(channel).toBe('browser:requestTabSetProfile')
|
|
const handler = ipcMainOnMock.mock.calls.find(
|
|
([eventName]) => eventName === 'browser:tabSetProfileReply'
|
|
)?.[1] as ((event: unknown, reply: { requestId: string; error?: string }) => void) | undefined
|
|
handler?.({ sender: webContents } as never, { requestId: data.requestId })
|
|
})
|
|
webContents.send = send
|
|
const bridge = {
|
|
getRegisteredTabs: vi.fn(() => new Map([['page-1', 101]])),
|
|
getActivePageId: vi.fn(() => 'page-1')
|
|
} as unknown as AgentBrowserBridge
|
|
const commands = new RuntimeBrowserCommands(
|
|
createHost({
|
|
getAgentBrowserBridge: () => bridge,
|
|
getAvailableAuthoritativeWindow: vi.fn(() => ({}) as never),
|
|
getAuthoritativeWindow: vi.fn(() => ({ webContents }) as never)
|
|
})
|
|
)
|
|
|
|
await expect(
|
|
commands.browserTabSetProfile({ worktree: 'id:wt-1', profileId: 'profile-isolated' })
|
|
).resolves.toEqual({
|
|
browserPageId: 'page-1',
|
|
profileId: 'profile-isolated',
|
|
profileLabel: 'Isolated'
|
|
})
|
|
|
|
expect(send).toHaveBeenCalledWith(
|
|
'browser:requestTabSetProfile',
|
|
expect.objectContaining({
|
|
browserPageId: 'page-1',
|
|
profileId: 'profile-isolated',
|
|
sessionPartition: 'persist:orca-browser-session-profile-isolated'
|
|
})
|
|
)
|
|
expect(waitForTabRegistrationMock).toHaveBeenCalledWith('page-1')
|
|
})
|
|
|
|
it('wakes the requested page instead of the first worktree tab for page-scoped commands', async () => {
|
|
const { RuntimeBrowserCommands } = await import('./orca-runtime-browser')
|
|
webContentsFromIdMock.mockReturnValue({ isDestroyed: () => true })
|
|
const send = vi.fn()
|
|
const snapshot = vi.fn(() => ({
|
|
origin: 'about:blank',
|
|
refs: {},
|
|
snapshot: '(empty page)',
|
|
browserPageId: 'page-target'
|
|
}))
|
|
const bridge = {
|
|
getRegisteredTabs: vi.fn(() => new Map([['page-target', 101]])),
|
|
getActivePageId: vi.fn(() => 'page-other'),
|
|
snapshot
|
|
} as unknown as AgentBrowserBridge
|
|
const commands = new RuntimeBrowserCommands(
|
|
createHost({
|
|
getAgentBrowserBridge: () => bridge,
|
|
getAuthoritativeWindow: vi.fn(() => ({ webContents: { send } }) as never)
|
|
})
|
|
)
|
|
|
|
await expect(
|
|
commands.browserSnapshot({ worktree: 'id:wt-1', page: 'page-target' })
|
|
).resolves.toEqual({
|
|
origin: 'about:blank',
|
|
refs: {},
|
|
snapshot: '(empty page)',
|
|
browserPageId: 'page-target'
|
|
})
|
|
|
|
expect(send).toHaveBeenCalledWith('browser:activateView', {
|
|
worktreeId: 'wt-1',
|
|
browserPageId: 'page-target'
|
|
})
|
|
expect(waitForTabRegistrationMock).toHaveBeenCalledWith('page-target')
|
|
expect(waitForWorktreeTabRegistrationMock).not.toHaveBeenCalled()
|
|
expect(snapshot).toHaveBeenCalledWith('wt-1', 'page-target')
|
|
})
|
|
|
|
it('wakes the requested page before showing page-scoped tab metadata', async () => {
|
|
const { RuntimeBrowserCommands } = await import('./orca-runtime-browser')
|
|
webContentsFromIdMock.mockReturnValue({ isDestroyed: () => true })
|
|
const send = vi.fn()
|
|
const bridge = {
|
|
getRegisteredTabs: vi.fn(() => new Map([['page-target', 101]])),
|
|
getActivePageId: vi.fn(() => 'page-other'),
|
|
tabList: vi.fn(() => ({
|
|
tabs: [
|
|
{
|
|
browserPageId: 'page-target',
|
|
index: 1,
|
|
url: 'about:blank',
|
|
title: 'Target',
|
|
active: false
|
|
}
|
|
]
|
|
}))
|
|
} as unknown as AgentBrowserBridge
|
|
const commands = new RuntimeBrowserCommands(
|
|
createHost({
|
|
getAgentBrowserBridge: () => bridge,
|
|
getAuthoritativeWindow: vi.fn(() => ({ webContents: { send } }) as never)
|
|
})
|
|
)
|
|
|
|
const result = await commands.browserTabShow({ worktree: 'id:wt-1', page: 'page-target' })
|
|
|
|
expect(send).toHaveBeenCalledWith('browser:activateView', {
|
|
worktreeId: 'wt-1',
|
|
browserPageId: 'page-target'
|
|
})
|
|
expect(waitForTabRegistrationMock).toHaveBeenCalledWith('page-target')
|
|
expect(waitForWorktreeTabRegistrationMock).not.toHaveBeenCalled()
|
|
expect(result.tab.browserPageId).toBe('page-target')
|
|
})
|
|
|
|
it('wakes the requested page before showing page-scoped profile metadata', async () => {
|
|
const { RuntimeBrowserCommands } = await import('./orca-runtime-browser')
|
|
webContentsFromIdMock.mockReturnValue({ isDestroyed: () => true })
|
|
const send = vi.fn()
|
|
const bridge = {
|
|
getRegisteredTabs: vi.fn(() => new Map([['page-target', 101]])),
|
|
getActivePageId: vi.fn(() => 'page-other'),
|
|
tabList: vi.fn(() => ({
|
|
tabs: [
|
|
{
|
|
browserPageId: 'page-target',
|
|
index: 1,
|
|
url: 'about:blank',
|
|
title: 'Target',
|
|
active: false
|
|
}
|
|
]
|
|
}))
|
|
} as unknown as AgentBrowserBridge
|
|
const commands = new RuntimeBrowserCommands(
|
|
createHost({
|
|
getAgentBrowserBridge: () => bridge,
|
|
getAuthoritativeWindow: vi.fn(() => ({ webContents: { send } }) as never)
|
|
})
|
|
)
|
|
|
|
const result = await commands.browserTabProfileShow({
|
|
worktree: 'id:wt-1',
|
|
page: 'page-target'
|
|
})
|
|
|
|
expect(send).toHaveBeenCalledWith('browser:activateView', {
|
|
worktreeId: 'wt-1',
|
|
browserPageId: 'page-target'
|
|
})
|
|
expect(waitForTabRegistrationMock).toHaveBeenCalledWith('page-target')
|
|
expect(waitForWorktreeTabRegistrationMock).not.toHaveBeenCalled()
|
|
expect(result.browserPageId).toBe('page-target')
|
|
})
|
|
|
|
it('closes the requested renderer page without waiting for guest registration', async () => {
|
|
const { RuntimeBrowserCommands } = await import('./orca-runtime-browser')
|
|
webContentsFromIdMock.mockReturnValue({ isDestroyed: () => true })
|
|
const send = vi.fn((channel: string, data: { requestId?: string }) => {
|
|
if (channel !== 'browser:requestTabClose') {
|
|
return
|
|
}
|
|
const handler = ipcMainOnMock.mock.calls.find(
|
|
([eventName]) => eventName === 'browser:tabCloseReply'
|
|
)?.[1] as ((event: unknown, reply: { requestId: string; error?: string }) => void) | undefined
|
|
handler?.({} as never, { requestId: data.requestId ?? '' })
|
|
})
|
|
const bridge = {
|
|
getRegisteredTabs: vi.fn(() => new Map([['page-target', 101]])),
|
|
getActivePageId: vi.fn(() => 'page-other'),
|
|
getActiveWebContentsId: vi.fn(() => 101)
|
|
} as unknown as AgentBrowserBridge
|
|
const commands = new RuntimeBrowserCommands(
|
|
createHost({
|
|
getAgentBrowserBridge: () => bridge,
|
|
getAuthoritativeWindow: vi.fn(() => ({ webContents: { send } }) as never)
|
|
})
|
|
)
|
|
|
|
await expect(
|
|
commands.browserTabClose({ worktree: 'id:wt-1', page: 'page-target' })
|
|
).resolves.toEqual({ closed: true })
|
|
|
|
expect(send).not.toHaveBeenCalledWith('browser:activateView', expect.anything())
|
|
expect(waitForTabRegistrationMock).not.toHaveBeenCalled()
|
|
expect(waitForWorktreeTabRegistrationMock).not.toHaveBeenCalled()
|
|
expect(send).toHaveBeenCalledWith(
|
|
'browser:requestTabClose',
|
|
expect.objectContaining({ tabId: 'page-target', worktreeId: 'wt-1' })
|
|
)
|
|
})
|
|
|
|
it('lets the renderer close an acknowledged page whose guest never registered', async () => {
|
|
const { RuntimeBrowserCommands } = await import('./orca-runtime-browser')
|
|
const send = vi.fn((channel: string, data: { requestId?: string }) => {
|
|
if (channel !== 'browser:requestTabClose') {
|
|
return
|
|
}
|
|
const handler = ipcMainOnMock.mock.calls.find(
|
|
([eventName]) => eventName === 'browser:tabCloseReply'
|
|
)?.[1] as ((event: unknown, reply: { requestId: string }) => void) | undefined
|
|
handler?.({} as never, { requestId: data.requestId ?? '' })
|
|
})
|
|
const bridge = {
|
|
getRegisteredTabs: vi.fn(() => new Map()),
|
|
getActivePageId: vi.fn(() => null),
|
|
getActiveWebContentsId: vi.fn(() => null)
|
|
} as unknown as AgentBrowserBridge
|
|
const authoritativeWindow = { webContents: { send } } as never
|
|
const commands = new RuntimeBrowserCommands(
|
|
createHost({
|
|
getAgentBrowserBridge: () => bridge,
|
|
getAvailableAuthoritativeWindow: vi.fn(() => authoritativeWindow),
|
|
getAuthoritativeWindow: vi.fn(() => authoritativeWindow)
|
|
})
|
|
)
|
|
|
|
await expect(
|
|
commands.browserTabClose({ worktree: 'id:wt-1', page: 'page-canonical' })
|
|
).resolves.toEqual({ closed: true })
|
|
|
|
expect(waitForTabRegistrationMock).not.toHaveBeenCalled()
|
|
expect(send).toHaveBeenCalledWith(
|
|
'browser:requestTabClose',
|
|
expect.objectContaining({ tabId: 'page-canonical', worktreeId: 'wt-1' })
|
|
)
|
|
})
|
|
|
|
it('fans one page screencast out to multiple subscribers', async () => {
|
|
const { RuntimeBrowserCommands } = await import('./orca-runtime-browser')
|
|
webContentsFromIdMock.mockReturnValue({ isDestroyed: () => false })
|
|
const done = deferred<void>()
|
|
const stop = vi.fn(() => done.resolve())
|
|
const updateViewport = vi.fn(async () => {})
|
|
startBrowserScreencastMock.mockResolvedValue({ stop, done: done.promise, updateViewport })
|
|
|
|
const commands = new RuntimeBrowserCommands(createHost())
|
|
const firstSend = vi.fn(() => false)
|
|
const secondSend = vi.fn(() => true)
|
|
const first = await commands.browserScreencast(
|
|
{
|
|
worktree: 'id:wt-1',
|
|
page: 'page-1',
|
|
format: 'jpeg',
|
|
viewportWidth: 1200,
|
|
viewportHeight: 800
|
|
},
|
|
{ sendBinary: firstSend }
|
|
)
|
|
const second = await commands.browserScreencast(
|
|
{
|
|
worktree: 'id:wt-1',
|
|
page: 'page-1',
|
|
format: 'jpeg',
|
|
viewportWidth: 800,
|
|
viewportHeight: 600
|
|
},
|
|
{ sendBinary: secondSend }
|
|
)
|
|
|
|
expect(startBrowserScreencastMock).toHaveBeenCalledOnce()
|
|
expect(first.subscriptionId).not.toBe(second.subscriptionId)
|
|
const frame = new Uint8Array([1, 2, 3])
|
|
expect(startBrowserScreencastMock.mock.calls[0][1].onFrame(frame)).toBe(true)
|
|
expect(firstSend).toHaveBeenCalledWith(frame)
|
|
expect(secondSend).toHaveBeenCalledWith(frame)
|
|
expect(updateViewport).toHaveBeenLastCalledWith(
|
|
expect.objectContaining({ viewportWidth: 800, viewportHeight: 600 })
|
|
)
|
|
second.session.stop()
|
|
await second.session.done
|
|
expect(stop).not.toHaveBeenCalled()
|
|
first.session.stop()
|
|
await first.session.done
|
|
expect(stop).toHaveBeenCalledOnce()
|
|
}, 10_000)
|
|
|
|
it('admits screencast frames through the paired-runtime size guard', async () => {
|
|
const { RuntimeBrowserCommands } = await import('./orca-runtime-browser')
|
|
webContentsFromIdMock.mockReturnValue({ isDestroyed: () => false })
|
|
const done = deferred<void>()
|
|
startBrowserScreencastMock.mockResolvedValue({
|
|
stop: vi.fn(() => done.resolve()),
|
|
done: done.promise,
|
|
updateViewport: vi.fn(async () => {})
|
|
})
|
|
const sendBinary = vi.fn(() => true)
|
|
|
|
const commands = new RuntimeBrowserCommands(createHost())
|
|
const started = await commands.browserScreencast(
|
|
{ worktree: 'id:wt-1', page: 'page-1', format: 'jpeg' },
|
|
{ sendBinary }
|
|
)
|
|
const { onFrame } = startBrowserScreencastMock.mock.calls[0][1]
|
|
|
|
expect(onFrame(new Uint8Array(REMOTE_RUNTIME_MAX_OUTBOUND_BINARY_FRAME_BYTES + 1))).toBe(true)
|
|
expect(sendBinary).not.toHaveBeenCalled()
|
|
expect(onFrame(new Uint8Array(64))).toBe(true)
|
|
expect(sendBinary).toHaveBeenCalledTimes(1)
|
|
|
|
started.session.stop()
|
|
await started.session.done
|
|
})
|
|
})
|
|
|
|
describe('RuntimeBrowserCommands headless offscreen routing', () => {
|
|
beforeEach(() => {
|
|
ipcMainOnMock.mockReset()
|
|
webContentsFromIdMock.mockReset()
|
|
waitForTabRegistrationMock.mockReset()
|
|
waitForTabRegistrationMock.mockResolvedValue(undefined)
|
|
waitForWorktreeTabRegistrationMock.mockReset()
|
|
waitForWorktreeTabRegistrationMock.mockResolvedValue(undefined)
|
|
})
|
|
|
|
it('routes tab creation to the offscreen backend when no renderer window exists', async () => {
|
|
const { RuntimeBrowserCommands } = await import('./orca-runtime-browser')
|
|
const createTab = vi.fn(async () => ({ browserPageId: 'page-offscreen' }))
|
|
const setActiveTab = vi.fn()
|
|
const bridge = {
|
|
getRegisteredTabs: vi.fn(() => new Map([['page-offscreen', 202]])),
|
|
setActiveTab
|
|
} as unknown as AgentBrowserBridge
|
|
const commands = new RuntimeBrowserCommands(
|
|
createHost({
|
|
getAgentBrowserBridge: () => bridge,
|
|
getAvailableAuthoritativeWindow: vi.fn(() => null),
|
|
getOffscreenBrowserBackend: vi.fn(() => ({ createTab, closeTab: vi.fn() }))
|
|
})
|
|
)
|
|
|
|
await expect(
|
|
commands.browserTabCreate({ worktree: 'id:wt-1', url: 'https://example.com' })
|
|
).resolves.toEqual({ browserPageId: 'page-offscreen' })
|
|
|
|
expect(createTab).toHaveBeenCalledWith({
|
|
url: 'https://example.com',
|
|
worktreeId: 'wt-1',
|
|
profileId: undefined
|
|
})
|
|
// No renderer round-trip in headless mode.
|
|
expect(waitForTabRegistrationMock).not.toHaveBeenCalled()
|
|
expect(setActiveTab).toHaveBeenCalledWith(202, 'wt-1')
|
|
})
|
|
|
|
it('publishes a headless session snapshot after explicit navigation', async () => {
|
|
const { RuntimeBrowserCommands } = await import('./orca-runtime-browser')
|
|
webContentsFromIdMock.mockReturnValue({ isDestroyed: () => false })
|
|
const notifyHeadlessBrowserSessionTabsChanged = vi.fn()
|
|
const bridge = {
|
|
getRegisteredTabs: vi.fn(() => new Map([['page-offscreen', 202]])),
|
|
getActivePageId: vi.fn(() => 'page-offscreen'),
|
|
goto: vi.fn(async () => ({ title: 'Loaded', url: 'https://example.com/loaded' }))
|
|
} as unknown as AgentBrowserBridge
|
|
const commands = new RuntimeBrowserCommands(
|
|
createHost({
|
|
getAgentBrowserBridge: () => bridge,
|
|
getAvailableAuthoritativeWindow: vi.fn(() => null),
|
|
notifyHeadlessBrowserSessionTabsChanged
|
|
})
|
|
)
|
|
|
|
await expect(
|
|
commands.browserGoto({
|
|
worktree: 'id:wt-1',
|
|
page: 'page-offscreen',
|
|
url: 'https://example.com/loaded'
|
|
})
|
|
).resolves.toEqual({ title: 'Loaded', url: 'https://example.com/loaded' })
|
|
|
|
expect(notifyHeadlessBrowserSessionTabsChanged).toHaveBeenCalledWith('wt-1')
|
|
})
|
|
|
|
it('rejects tab creation when neither a renderer nor an offscreen backend is available', async () => {
|
|
const { RuntimeBrowserCommands } = await import('./orca-runtime-browser')
|
|
const commands = new RuntimeBrowserCommands(
|
|
createHost({
|
|
getAvailableAuthoritativeWindow: vi.fn(() => null),
|
|
getOffscreenBrowserBackend: vi.fn(() => null)
|
|
})
|
|
)
|
|
|
|
await expect(commands.browserTabCreate({ url: 'about:blank' })).rejects.toThrow(
|
|
/does not support browser panes/
|
|
)
|
|
})
|
|
})
|