fix(cli): avoid defaulting to isolated when userDataDir is provided (#1258)

## Summary

Fix `chrome-devtools start` so it no longer implicitly enables
`isolated` when `--userDataDir` is provided.

Previously, the CLI wrapper always defaulted `isolated` to `true` for
`start`, which caused `userDataDir` and `isolated` to conflict even when
the user only specified `--userDataDir`. This made it impossible to
start the CLI daemon against a persistent browser profile.

   ## Changes

- Update `chrome-devtools start` default handling in
`src/bin/chrome-devtools.ts`
     - only default `isolated=true` when `userDataDir` is not set
- Clarify the `isolated` CLI description to document the conditional
default
   - Update `docs/cli.md` to reflect that:
     - `headless` is enabled by default
- `isolated` is enabled by default unless `--userDataDir` is provided
   - Fix a small error message typo

   ## Why

   This matches the intended semantics of the flags:

   - `--isolated` means use a temporary user data dir
   - `--userDataDir` means use a persistent, explicit user data dir

If the user passes `--userDataDir`, the CLI should not also implicitly
enable `isolated`.

   ## Testing

   - Ran:
     - `npm test -- tests/cli.test.ts`
     - `npm test -- tests/e2e/chrome-devtools.test.ts`

Added an e2e regression test in `tests/e2e/chrome-devtools.test.ts` to
verify that:

   - `chrome-devtools start --userDataDir <temp dir>` succeeds
- the CLI no longer fails with `Arguments userDataDir and isolated are
mutually exclusive`
   - the daemon starts successfully when `userDataDir` is provided
This commit is contained in:
pidofme authored and GitHub committed 2026-04-01 05:51:40 +00:00
1 parent 4637ab9389
commit 73e1e24b26
3 files changed
+30 -3

No files matched your search

+1 -1
View File
@@ -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
+4 -2
View File
@@ -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) {
+25
View File
@@ -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();