mirror of
https://github.com/tiennm99/ccs.git
synced 2026-08-17 10:25:02 +00:00
feat(cli): add browser automation commands
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
import { afterEach, describe, expect, test, spyOn } from 'bun:test';
|
||||
|
||||
import * as browserUtils from '../../../src/utils/browser';
|
||||
import { handleBrowserCommand } from '../../../src/commands/browser-command';
|
||||
|
||||
function stripAnsi(input: string): string {
|
||||
return input.replace(/\u001b\[[0-9;]*m/g, '');
|
||||
}
|
||||
|
||||
async function renderLines(args: string[]): Promise<string> {
|
||||
const lines: string[] = [];
|
||||
await handleBrowserCommand(args, (line) => lines.push(line));
|
||||
return stripAnsi(lines.join('\n'));
|
||||
}
|
||||
|
||||
function currentPlatform(): 'darwin' | 'linux' | 'win32' {
|
||||
if (process.platform === 'darwin') return 'darwin';
|
||||
if (process.platform === 'win32') return 'win32';
|
||||
return 'linux';
|
||||
}
|
||||
|
||||
describe('browser command', () => {
|
||||
afterEach(() => {
|
||||
process.exitCode = 0;
|
||||
});
|
||||
|
||||
test('status renders both browser lanes from the shared status payload', async () => {
|
||||
const statusSpy = spyOn(browserUtils, 'getBrowserStatus').mockResolvedValue({
|
||||
claude: {
|
||||
enabled: true,
|
||||
source: 'config',
|
||||
overrideActive: false,
|
||||
state: 'ready',
|
||||
title: 'Claude Browser Attach is ready.',
|
||||
detail: 'CCS can reach the configured Chrome DevTools endpoint.',
|
||||
nextStep: 'Launch Claude.',
|
||||
effectiveUserDataDir: '/tmp/browser-profile',
|
||||
recommendedUserDataDir: '/tmp/browser-profile',
|
||||
devtoolsPort: 9222,
|
||||
managedMcpServerName: 'ccs-browser',
|
||||
managedMcpServerPath: '/tmp/ccs-browser-server.cjs',
|
||||
launchCommands: {
|
||||
darwin: 'open -na "Google Chrome" --args',
|
||||
linux: 'google-chrome --remote-debugging-port=9222',
|
||||
win32: 'chrome.exe --remote-debugging-port=9222',
|
||||
},
|
||||
runtimeEnv: {
|
||||
CCS_BROWSER_USER_DATA_DIR: '/tmp/browser-profile',
|
||||
CCS_BROWSER_DEVTOOLS_HOST: '127.0.0.1',
|
||||
CCS_BROWSER_DEVTOOLS_PORT: '9222',
|
||||
CCS_BROWSER_DEVTOOLS_HTTP_URL: 'http://127.0.0.1:9222',
|
||||
CCS_BROWSER_DEVTOOLS_WS_URL: 'ws://127.0.0.1/devtools/browser/test',
|
||||
},
|
||||
},
|
||||
codex: {
|
||||
enabled: true,
|
||||
state: 'enabled',
|
||||
title: 'Codex Browser Tools are enabled.',
|
||||
detail: 'CCS can inject the managed Playwright MCP overrides.',
|
||||
nextStep: 'Use a Codex-target launch.',
|
||||
serverName: 'ccs_browser',
|
||||
supportsConfigOverrides: true,
|
||||
binaryPath: '/usr/local/bin/codex',
|
||||
version: 'codex-cli 0.120.0',
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
const rendered = await renderLines(['status']);
|
||||
|
||||
expect(rendered.includes('ccs browser status')).toBe(true);
|
||||
expect(rendered.includes('Claude Browser Attach reuses a local Chrome session')).toBe(true);
|
||||
expect(rendered.includes('Codex Browser Tools inject managed Playwright MCP overrides')).toBe(
|
||||
true
|
||||
);
|
||||
expect(rendered.includes('Managed MCP: ccs-browser')).toBe(true);
|
||||
expect(rendered.includes('Managed server: ccs_browser')).toBe(true);
|
||||
expect(rendered.includes('DevTools endpoint: http://127.0.0.1:9222')).toBe(true);
|
||||
} finally {
|
||||
statusSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
test('doctor prints env override context and launch guidance when Claude attach is not ready', async () => {
|
||||
const launchCommands = {
|
||||
darwin: 'open -na "Google Chrome" --args --remote-debugging-port=9444',
|
||||
linux:
|
||||
'google-chrome --remote-debugging-port=9444 --user-data-dir="/tmp/browser-profile"',
|
||||
win32: 'chrome.exe --remote-debugging-port=9444 --user-data-dir="/tmp/browser-profile"',
|
||||
};
|
||||
const statusSpy = spyOn(browserUtils, 'getBrowserStatus').mockResolvedValue({
|
||||
claude: {
|
||||
enabled: true,
|
||||
source: 'CCS_BROWSER_PROFILE_DIR',
|
||||
overrideActive: true,
|
||||
state: 'browser_not_running',
|
||||
title: 'Claude Browser Attach could not find a running browser session.',
|
||||
detail: 'Chrome reuse metadata not found: /tmp/browser-profile/DevToolsActivePort',
|
||||
nextStep: 'Start Chrome with remote debugging.',
|
||||
effectiveUserDataDir: '/tmp/browser-profile',
|
||||
recommendedUserDataDir: '/tmp/browser-profile',
|
||||
devtoolsPort: 9444,
|
||||
managedMcpServerName: 'ccs-browser',
|
||||
managedMcpServerPath: '/tmp/ccs-browser-server.cjs',
|
||||
launchCommands,
|
||||
},
|
||||
codex: {
|
||||
enabled: true,
|
||||
state: 'unsupported_build',
|
||||
title: 'Codex Browser Tools need a Codex build with --config override support.',
|
||||
detail: 'Detected Codex at /usr/local/bin/codex, but it does not advertise --config overrides.',
|
||||
nextStep: 'Install or upgrade Codex, then rerun browser status/doctor.',
|
||||
serverName: 'ccs_browser',
|
||||
supportsConfigOverrides: false,
|
||||
binaryPath: '/usr/local/bin/codex',
|
||||
version: 'codex-cli 0.70.0',
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
const rendered = await renderLines(['doctor']);
|
||||
|
||||
expect(rendered.includes('Result: action required')).toBe(true);
|
||||
expect(rendered.includes('Source: CCS_BROWSER_PROFILE_DIR (env override active)')).toBe(
|
||||
true
|
||||
);
|
||||
expect(
|
||||
rendered.includes(
|
||||
`Launch command (${currentPlatform()}): ${launchCommands[currentPlatform()]}`
|
||||
)
|
||||
).toBe(true);
|
||||
expect(rendered.includes('Detected Codex at /usr/local/bin/codex')).toBe(true);
|
||||
expect(process.exitCode).toBe(1);
|
||||
} finally {
|
||||
statusSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
|
||||
test('doctor stays ready on Claude-only machines when Codex is not installed', async () => {
|
||||
const statusSpy = spyOn(browserUtils, 'getBrowserStatus').mockResolvedValue({
|
||||
claude: {
|
||||
enabled: false,
|
||||
source: 'config',
|
||||
overrideActive: false,
|
||||
state: 'disabled',
|
||||
title: 'Claude Browser Attach is disabled.',
|
||||
detail: 'CCS will not provision the managed browser MCP runtime for Claude launches until this lane is enabled.',
|
||||
nextStep:
|
||||
'Enable Claude Browser Attach in Settings > Browser or in ~/.ccs/config.yaml, then rerun `ccs browser doctor`.',
|
||||
effectiveUserDataDir: '/tmp/browser-profile',
|
||||
recommendedUserDataDir: '/tmp/browser-profile',
|
||||
devtoolsPort: 9222,
|
||||
managedMcpServerName: 'ccs-browser',
|
||||
managedMcpServerPath: '/tmp/ccs-browser-server.cjs',
|
||||
launchCommands: {
|
||||
darwin: 'open -na "Google Chrome" --args --remote-debugging-port=9222',
|
||||
linux:
|
||||
'google-chrome --remote-debugging-port=9222 --user-data-dir="/tmp/browser-profile"',
|
||||
win32:
|
||||
'chrome.exe --remote-debugging-port=9222 --user-data-dir="/tmp/browser-profile"',
|
||||
},
|
||||
},
|
||||
codex: {
|
||||
enabled: true,
|
||||
state: 'unsupported_build',
|
||||
title: 'Codex Browser Tools need a Codex build with --config override support.',
|
||||
detail: 'No Codex binary was detected, so CCS cannot confirm managed browser override support.',
|
||||
nextStep: 'Install or upgrade Codex, then rerun browser status/doctor.',
|
||||
serverName: 'ccs_browser',
|
||||
supportsConfigOverrides: false,
|
||||
binaryPath: null,
|
||||
},
|
||||
});
|
||||
|
||||
try {
|
||||
const rendered = await renderLines(['doctor']);
|
||||
|
||||
expect(rendered.includes('Result: partial')).toBe(true);
|
||||
expect(rendered.includes('run `ccs browser enable`')).toBe(false);
|
||||
expect(process.exitCode).toBe(0);
|
||||
} finally {
|
||||
statusSpy.mockRestore();
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user