Skip to content

Commit 1da04e0

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

4 files changed

Lines changed: 165 additions & 15 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: 27 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22,24 +22,41 @@ import {
2222
} from './utils.js';
2323

2424
/**
25-
* Waits for a file to be created and populated.
25+
* Waits for a file to be created and populated (removed = false) or removed (removed = true).
2626
*/
27-
function waitForFile(filePath: string, timeout = 5000) {
27+
function waitForFile(filePath: string, removed = false, timeout = 5000) {
2828
return new Promise<void>((resolve, reject) => {
29-
if (fs.existsSync(filePath) && fs.statSync(filePath).size > 0) {
29+
const check = () => {
30+
const exists = fs.existsSync(filePath);
31+
if (removed) {
32+
return !exists;
33+
}
34+
if (!exists) {
35+
return false;
36+
}
37+
try {
38+
return fs.statSync(filePath).size > 0;
39+
} catch {
40+
return false;
41+
}
42+
};
43+
44+
if (check()) {
3045
resolve();
3146
return;
3247
}
3348

3449
const timer = setTimeout(() => {
3550
fs.unwatchFile(filePath);
3651
reject(
37-
new Error(`Timeout: file ${filePath} not found within ${timeout}ms`),
52+
new Error(
53+
`Timeout: file ${filePath} ${removed ? 'not removed' : 'not found'} within ${timeout}ms`,
54+
),
3855
);
3956
}, timeout);
4057

41-
fs.watchFile(filePath, {interval: 500}, curr => {
42-
if (curr.size > 0) {
58+
fs.watchFile(filePath, {interval: 500}, () => {
59+
if (check()) {
4360
clearTimeout(timer);
4461
fs.unwatchFile(filePath); // Always clean up your listeners!
4562
resolve();
@@ -135,7 +152,11 @@ export async function stopDaemon() {
135152
return;
136153
}
137154

155+
const pidFilePath = getPidFilePath();
156+
138157
await sendCommand({method: 'stop'});
158+
159+
await waitForFile(pidFilePath, /*removed=*/ true);
139160
}
140161

141162
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: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
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+
spawnSync('node', [CLI_PATH, 'start', ...START_ARGS]);
33+
34+
result = spawnSync('node', [CLI_PATH, 'status']);
35+
assert.strictEqual(
36+
result.stdout.toString(),
37+
'chrome-devtools-mcp daemon is running.\n',
38+
);
39+
});
40+
41+
it('can start and stop the daemon', () => {
42+
let result = spawnSync('node', [CLI_PATH, 'status']);
43+
assert.strictEqual(
44+
result.stdout.toString(),
45+
'chrome-devtools-mcp daemon is not running.\n',
46+
);
47+
48+
const startResult = spawnSync('node', [CLI_PATH, 'start', ...START_ARGS]);
49+
assert.strictEqual(
50+
startResult.status,
51+
0,
52+
`start command failed: ${startResult.stderr.toString()}`,
53+
);
54+
55+
result = spawnSync('node', [CLI_PATH, 'status']);
56+
assert.strictEqual(
57+
result.stdout.toString(),
58+
'chrome-devtools-mcp daemon is running.\n',
59+
);
60+
61+
const stopResult = spawnSync('node', [CLI_PATH, 'stop']);
62+
assert.strictEqual(
63+
stopResult.status,
64+
0,
65+
`stop command failed: ${stopResult.stderr.toString()}`,
66+
);
67+
68+
result = spawnSync('node', [CLI_PATH, 'status']);
69+
assert.strictEqual(
70+
result.stdout.toString(),
71+
'chrome-devtools-mcp daemon is not running.\n',
72+
);
73+
});
74+
75+
it('can invoke list_pages', async () => {
76+
// Daemon should not be running.
77+
let result = spawnSync('node', [CLI_PATH, 'status']);
78+
assert.strictEqual(
79+
result.stdout.toString(),
80+
'chrome-devtools-mcp daemon is not running.\n',
81+
);
82+
83+
const startResult = spawnSync('node', [CLI_PATH, 'start', ...START_ARGS]);
84+
assert.strictEqual(
85+
startResult.status,
86+
0,
87+
`start command failed: ${startResult.stderr.toString()}`,
88+
);
89+
90+
const listPagesResult = spawnSync('node', [CLI_PATH, 'list_pages']);
91+
assert.strictEqual(
92+
listPagesResult.status,
93+
0,
94+
`list_pages command failed: ${listPagesResult.stderr.toString()}`,
95+
);
96+
assert(
97+
listPagesResult.stdout.toString().includes('about:blank'),
98+
'list_pages output is unexpected',
99+
);
100+
101+
// Daemon should now be running.
102+
result = spawnSync('node', [CLI_PATH, 'status']);
103+
assert.strictEqual(
104+
result.stdout.toString(),
105+
'chrome-devtools-mcp daemon is running.\n',
106+
);
107+
});
108+
109+
it('forwards disclaimers to stderr on start', () => {
110+
const result = spawnSync('node', [CLI_PATH, 'start', ...START_ARGS]);
111+
assert.strictEqual(
112+
result.status,
113+
0,
114+
`start command failed: ${result.stderr.toString()}`,
115+
);
116+
assert(
117+
result.stderr.toString().includes('chrome-devtools-mcp exposes content'),
118+
'Disclaimer not found in stderr on start',
119+
);
120+
});
121+
});

0 commit comments

Comments
 (0)