diff --git a/docs/cli.md b/docs/cli.md index 7606e5677..c3025b3c0 100644 --- a/docs/cli.md +++ b/docs/cli.md @@ -17,7 +17,7 @@ The CLI acts as a client to a background `chrome-devtools-mcp` daemon (uses Unix - **Automatic Start**: The first time you call a tool (e.g., `list_pages`), the CLI automatically starts the MCP server and the browser in the background if they aren't already running. - **Persistence**: The same background instance is reused for subsequent commands, preserving the browser state (open pages, cookies, etc.). -- **Manual Control**: You can explicitly manage the background process using `start`, `stop`, and `status`. The `start` command forwards all subsequent arguments to the underlying MCP server (e.g., `--headless`, `--userDataDir`) but not all args are supported. Run `chrome-devtools start --help` for supported args. Headless and isolated are enabled by default. +- **Manual Control**: You can explicitly manage the background process using `start`, `stop`, and `status`. The `start` command forwards all subsequent arguments to the underlying MCP server (e.g., `--headless`, `--userDataDir`) but not all args are supported. Run `chrome-devtools start --help` for supported args. Headless is enabled by default. Isolated is enabled by default unless `--userDataDir` is provided. ```sh # Check if the daemon is running diff --git a/src/bin/chrome-devtools.ts b/src/bin/chrome-devtools.ts index b30f136dd..f417c8373 100644 --- a/src/bin/chrome-devtools.ts +++ b/src/bin/chrome-devtools.ts @@ -59,9 +59,11 @@ if (!('default' in cliOptions.headless)) { throw new Error('headless cli option unexpectedly does not have a default'); } if ('default' in cliOptions.isolated) { - throw new Error('headless cli option unexpectedly does not have a default'); + throw new Error('isolated cli option unexpectedly has a default'); } startCliOptions.headless!.default = true; +startCliOptions.isolated!.description = + 'If specified, creates a temporary user-data-dir that is automatically cleaned up after the browser is closed. Defaults to true unless userDataDir is provided.'; const y = yargs(hideBin(process.argv)) .scriptName('chrome-devtools') @@ -92,7 +94,7 @@ y.command( await stopDaemon(); } // Defaults but we do not want to affect the yargs conflict resolution. - if (argv.isolated === undefined) { + if (argv.isolated === undefined && argv.userDataDir === undefined) { argv.isolated = true; } if (argv.headless === undefined) { diff --git a/tests/e2e/chrome-devtools.test.ts b/tests/e2e/chrome-devtools.test.ts index 4f5ac1a4e..c042cd72d 100644 --- a/tests/e2e/chrome-devtools.test.ts +++ b/tests/e2e/chrome-devtools.test.ts @@ -6,6 +6,8 @@ import assert from 'node:assert'; import {spawn} from 'node:child_process'; +import fs from 'node:fs'; +import os from 'node:os'; import path from 'node:path'; import {describe, it, afterEach, beforeEach} from 'node:test'; @@ -93,6 +95,29 @@ describe('chrome-devtools', () => { await assertDaemonIsNotRunning(); }); + it('can start the daemon with userDataDir', async () => { + const userDataDir = path.join( + os.tmpdir(), + `chrome-devtools-test-${crypto.randomUUID()}`, + ); + fs.mkdirSync(userDataDir, {recursive: true}); + + const startResult = await runCli(['start', '--userDataDir', userDataDir]); + assert.strictEqual( + startResult.status, + 0, + `start command failed: ${startResult.stderr}`, + ); + assert.ok( + !startResult.stderr.includes( + 'Arguments userDataDir and isolated are mutually exclusive', + ), + `unexpected conflict error: ${startResult.stderr}`, + ); + + await assertDaemonIsRunning(); + }); + it('can invoke list_pages', async () => { await assertDaemonIsNotRunning();