From 66053c049c65c525786ab42c8f5c59f31ae1c69b Mon Sep 17 00:00:00 2001 From: "microsoft-playwright-automation[bot]" <203992400+microsoft-playwright-automation[bot]@users.noreply.github.com> Date: Thu, 5 Nov 2026 14:45:40 -0800 Subject: [PATCH 1/2] fix(cli): report command failures in text and JSON modes The entry point discarded the promise from program(), so argument parsing, startup and daemon validation failures surfaced as unhandled rejections with a stack trace, and `--json` produced no output. Report them as a message on stderr, or `{ isError: true, error }` on stdout in JSON mode, with exit code 1. The output mode comes from the parsed arguments, or from the `--json` flag when parsing itself fails. Fixes: https://github.com/microsoft/playwright-cli/issues/502 --- README.md | 3 +++ playwright-cli.js | 14 +++++++++++++- tests/integration.spec.ts | 21 ++++++++++++++++++++- 3 files changed, 36 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ed86206..d8070e5 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,9 @@ npm install -g @playwright/cli@latest playwright-cli --help ``` +Argument parsing and startup errors exit with code 1 and print an error message. With +`--json`, these errors are written to stdout as `{ "isError": true, "error": "..." }`. + ### Installing skills Claude Code, GitHub Copilot and others will use the locally installed skills. diff --git a/playwright-cli.js b/playwright-cli.js index 8e9d23f..c4597f3 100755 --- a/playwright-cli.js +++ b/playwright-cli.js @@ -31,11 +31,23 @@ const packageJson = require('./package.json'); const ONE_DAY_MS = 24 * 60 * 60 * 1000; const UPDATE_CHECK_COMMANDS = new Set(['open', 'attach', 'list', 'install', 'install-browser']); -main(); +// Parsing can fail before the output mode is known, so start from the flag. +let json = process.argv.slice(2).includes('--json'); + +main().catch(error => { + // Report the failure the way program() reports its own errors. + const message = error instanceof Error ? error.message : String(error); + if (json) + console.log(JSON.stringify({ isError: true, error: message }, null, 2)); + else + console.error(message); + process.exit(1); +}); async function main() { // Parse once and inspect the arguments before program() runs, as it may mutate them. const args = parseArgs(process.argv.slice(2)); + json = !!args.json; await checkForUpdates(args).catch(() => {}); await program({ args, embedderVersion: packageJson.version }); } diff --git a/tests/integration.spec.ts b/tests/integration.spec.ts index 72e5831..94d4298 100644 --- a/tests/integration.spec.ts +++ b/tests/integration.spec.ts @@ -107,12 +107,31 @@ async function runCli(args: string[], env: Record = {}): Promise }); } -test('open data URL', async ({}) => { +test('open data URL', { annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright-cli/issues/502' } }, async ({}) => { + const argumentError = "boolean option '--headed' should not be passed with '=value', use '--headed' or '--no-headed' instead"; + expect(await runCli(['open', '--headed=false'])).toEqual({ + output: '', + error: argumentError, + exitCode: 1, + }); + + expect(await runCli(['--json', 'open', '--headed=false'])).toEqual({ + output: JSON.stringify({ isError: true, error: argumentError }, null, 2), + error: '', + exitCode: 1, + }); + expect(await runCli(['open', 'data:text/html,hello', '--persistent'])).toEqual(expect.objectContaining({ output: expect.stringContaining('hello'), exitCode: 0, })); + expect(await runCli(['--json', 'screenshot', '--filename'])).toEqual({ + output: JSON.stringify({ isError: true, error: "error: '--filename' option: expected string, received boolean" }, null, 2), + error: '', + exitCode: 1, + }); + expect(await runCli(['delete-data'])).toEqual(expect.objectContaining({ output: expect.stringContaining('Deleted user data for'), exitCode: 0, From cafcea98b24b79a54fe7ec7e2a3c6566113caa11 Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Thu, 5 Nov 2026 14:59:41 -0800 Subject: [PATCH 2/2] fix(cli): keep the output mode local to main() --- playwright-cli.js | 36 +++++++++++++++++++----------------- 1 file changed, 19 insertions(+), 17 deletions(-) diff --git a/playwright-cli.js b/playwright-cli.js index c4597f3..28fa1f8 100755 --- a/playwright-cli.js +++ b/playwright-cli.js @@ -31,25 +31,27 @@ const packageJson = require('./package.json'); const ONE_DAY_MS = 24 * 60 * 60 * 1000; const UPDATE_CHECK_COMMANDS = new Set(['open', 'attach', 'list', 'install', 'install-browser']); -// Parsing can fail before the output mode is known, so start from the flag. -let json = process.argv.slice(2).includes('--json'); - -main().catch(error => { - // Report the failure the way program() reports its own errors. - const message = error instanceof Error ? error.message : String(error); - if (json) - console.log(JSON.stringify({ isError: true, error: message }, null, 2)); - else - console.error(message); - process.exit(1); -}); +main(); async function main() { - // Parse once and inspect the arguments before program() runs, as it may mutate them. - const args = parseArgs(process.argv.slice(2)); - json = !!args.json; - await checkForUpdates(args).catch(() => {}); - await program({ args, embedderVersion: packageJson.version }); + const argv = process.argv.slice(2); + // Parsing can fail before the output mode is known, so start from the flag. + let json = argv.includes('--json'); + try { + // Parse once and inspect the arguments before program() runs, as it may mutate them. + const args = parseArgs(argv); + json = !!args.json; + await checkForUpdates(args).catch(() => {}); + await program({ args, embedderVersion: packageJson.version }); + } catch (error) { + // Report the failure the way program() reports its own errors. + const message = error instanceof Error ? error.message : String(error); + if (json) + console.log(JSON.stringify({ isError: true, error: message }, null, 2)); + else + console.error(message); + process.exit(1); + } } /**