Skip to content

Commit 1c2f806

Browse files
committed
chore: fix arg forwarding
1 parent bedfd5d commit 1c2f806

4 files changed

Lines changed: 174 additions & 17 deletions

File tree

src/bin/chrome-devtools.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,14 @@ const y = yargs(argv)
4444
y.command(
4545
'start',
4646
'Start or restart chrome-devtools-mcp',
47-
y => y.help(false), // Disable help for start command to avoid parsing issues with passed args
47+
y =>
48+
y
49+
.help(false) // Disable help for start command to avoid parsing issues with passed args.
50+
.example(
51+
'$0 start --port 8080 --url http://localhost:8080',
52+
'Start the server on port 8080 with a specific URL',
53+
)
54+
.strict(false), // Don't validate arguments for start, as they are passed through to the daemon.
4855
async () => {
4956
if (isDaemonRunning()) {
5057
await stopDaemon();
@@ -54,13 +61,14 @@ y.command(
5461
const args = startIndex !== -1 ? process.argv.slice(startIndex + 1) : [];
5562
await startDaemon([...args, ...defaultArgs]);
5663
},
57-
);
64+
).strict(); // Re-enable strict validation for other commands; this is applied to the yargs instance itself
5865

5966
y.command('status', 'Checks if chrome-devtools-mcp is running', async () => {
6067
if (isDaemonRunning()) {
6168
console.log('chrome-devtools-mcp daemon is running.');
6269
} else {
63-
console.log('chrome-devtools-mcp daemon is not running');
70+
console.log('chrome-devtools-mcp daemon is not running.');
71+
process.exit(0);
6472
}
6573
});
6674

src/daemon/client.ts

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -21,27 +21,46 @@ import {
2121
isDaemonRunning,
2222
} from './utils.js';
2323

24+
const FILE_TIMEOUT = 10_000;
25+
2426
/**
25-
* Waits for a file to be created and populated.
27+
* Waits for a file to be created and populated (removed = false) or removed (removed = true).
2628
*/
27-
function waitForFile(filePath: string, timeout = 5000) {
29+
function waitForFile(filePath: string, removed = false) {
2830
return new Promise<void>((resolve, reject) => {
29-
if (fs.existsSync(filePath) && fs.statSync(filePath).size > 0) {
31+
const check = () => {
32+
const exists = fs.existsSync(filePath);
33+
if (removed) {
34+
return !exists;
35+
}
36+
if (!exists) {
37+
return false;
38+
}
39+
try {
40+
return fs.statSync(filePath).size > 0;
41+
} catch {
42+
return false;
43+
}
44+
};
45+
46+
if (check()) {
3047
resolve();
3148
return;
3249
}
3350

3451
const timer = setTimeout(() => {
3552
fs.unwatchFile(filePath);
3653
reject(
37-
new Error(`Timeout: file ${filePath} not found within ${timeout}ms`),
54+
new Error(
55+
`Timeout: file ${filePath} ${removed ? 'not removed' : 'not found'} within ${FILE_TIMEOUT}ms`,
56+
),
3857
);
39-
}, timeout);
58+
}, FILE_TIMEOUT);
4059

41-
fs.watchFile(filePath, {interval: 500}, curr => {
42-
if (curr.size > 0) {
60+
fs.watchFile(filePath, {interval: 500}, () => {
61+
if (check()) {
4362
clearTimeout(timer);
44-
fs.unwatchFile(filePath); // Always clean up your listeners!
63+
fs.unwatchFile(filePath);
4564
resolve();
4665
}
4766
});
@@ -135,7 +154,11 @@ export async function stopDaemon() {
135154
return;
136155
}
137156

157+
const pidFilePath = getPidFilePath();
158+
138159
await sendCommand({method: 'stop'});
160+
161+
await waitForFile(pidFilePath, /*removed=*/ true);
139162
}
140163

141164
export function handleResponse(

tests/daemon/client.test.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
*/
66

77
import assert from 'node:assert';
8-
import {describe, it, afterEach} from 'node:test';
8+
import {describe, it, afterEach, beforeEach} from 'node:test';
99

1010
import {
1111
handleResponse,
@@ -16,12 +16,12 @@ import {isDaemonRunning} from '../../src/daemon/utils.js';
1616

1717
describe('daemon client', () => {
1818
describe('start/stop', () => {
19+
beforeEach(async () => {
20+
await stopDaemon();
21+
});
22+
1923
afterEach(async () => {
20-
if (isDaemonRunning()) {
21-
await stopDaemon();
22-
// Wait a bit for the daemon to fully terminate and clean up its files.
23-
await new Promise(resolve => setTimeout(resolve, 1000));
24-
}
24+
await stopDaemon();
2525
});
2626

2727
it('should start and stop daemon', async () => {

tests/e2e/chrome-devtools.test.ts

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,126 @@
1+
/**
2+
* @license
3+
* Copyright 2026 Google LLC
4+
* SPDX-License-Identifier: Apache-2.0
5+
*/
6+
7+
import assert from 'node:assert';
8+
import {spawnSync} from 'node:child_process';
9+
import path from 'node:path';
10+
import {describe, it, afterEach, beforeEach} from 'node:test';
11+
12+
const CLI_PATH = path.resolve('build/src/bin/chrome-devtools.js');
13+
14+
describe('chrome-devtools', () => {
15+
const START_ARGS = ['--headless', '--isolated'];
16+
17+
beforeEach(() => {
18+
spawnSync('node', [CLI_PATH, 'stop']);
19+
});
20+
21+
afterEach(() => {
22+
spawnSync('node', [CLI_PATH, 'stop']);
23+
});
24+
25+
it('reports daemon status correctly', () => {
26+
let result = spawnSync('node', [CLI_PATH, 'status']);
27+
assert.strictEqual(
28+
result.stdout.toString(),
29+
'chrome-devtools-mcp daemon is not running.\n',
30+
);
31+
32+
const startResult = spawnSync('node', [CLI_PATH, 'start', ...START_ARGS]);
33+
assert.strictEqual(
34+
startResult.status,
35+
0,
36+
`start command failed: ${startResult.stderr.toString()}`,
37+
);
38+
39+
result = spawnSync('node', [CLI_PATH, 'status']);
40+
assert.strictEqual(
41+
result.stdout.toString(),
42+
'chrome-devtools-mcp daemon is running.\n',
43+
);
44+
});
45+
46+
it('can start and stop the daemon', () => {
47+
let result = spawnSync('node', [CLI_PATH, 'status']);
48+
assert.strictEqual(
49+
result.stdout.toString(),
50+
'chrome-devtools-mcp daemon is not running.\n',
51+
);
52+
53+
const startResult = spawnSync('node', [CLI_PATH, 'start', ...START_ARGS]);
54+
assert.strictEqual(
55+
startResult.status,
56+
0,
57+
`start command failed: ${startResult.stderr.toString()}`,
58+
);
59+
60+
result = spawnSync('node', [CLI_PATH, 'status']);
61+
assert.strictEqual(
62+
result.stdout.toString(),
63+
'chrome-devtools-mcp daemon is running.\n',
64+
);
65+
66+
const stopResult = spawnSync('node', [CLI_PATH, 'stop']);
67+
assert.strictEqual(
68+
stopResult.status,
69+
0,
70+
`stop command failed: ${stopResult.stderr.toString()}`,
71+
);
72+
73+
result = spawnSync('node', [CLI_PATH, 'status']);
74+
assert.strictEqual(
75+
result.stdout.toString(),
76+
'chrome-devtools-mcp daemon is not running.\n',
77+
);
78+
});
79+
80+
it('can invoke list_pages', async () => {
81+
// Daemon should not be running.
82+
let result = spawnSync('node', [CLI_PATH, 'status']);
83+
assert.strictEqual(
84+
result.stdout.toString(),
85+
'chrome-devtools-mcp daemon is not running.\n',
86+
);
87+
88+
const startResult = spawnSync('node', [CLI_PATH, 'start', ...START_ARGS]);
89+
assert.strictEqual(
90+
startResult.status,
91+
0,
92+
`start command failed: ${startResult.stderr.toString()}`,
93+
);
94+
95+
const listPagesResult = spawnSync('node', [CLI_PATH, 'list_pages']);
96+
assert.strictEqual(
97+
listPagesResult.status,
98+
0,
99+
`list_pages command failed: ${listPagesResult.stderr.toString()}`,
100+
);
101+
assert(
102+
listPagesResult.stdout.toString().includes('about:blank'),
103+
'list_pages output is unexpected',
104+
);
105+
106+
// Daemon should now be running.
107+
result = spawnSync('node', [CLI_PATH, 'status']);
108+
assert.strictEqual(
109+
result.stdout.toString(),
110+
'chrome-devtools-mcp daemon is running.\n',
111+
);
112+
});
113+
114+
it('forwards disclaimers to stderr on start', () => {
115+
const result = spawnSync('node', [CLI_PATH, 'start', ...START_ARGS]);
116+
assert.strictEqual(
117+
result.status,
118+
0,
119+
`start command failed: ${result.stderr.toString()}`,
120+
);
121+
assert(
122+
result.stderr.toString().includes('chrome-devtools-mcp exposes content'),
123+
'Disclaimer not found in stderr on start',
124+
);
125+
});
126+
});

0 commit comments

Comments
 (0)