From 90e8fb2c5cfdd0212b0066a03612e5beebc76062 Mon Sep 17 00:00:00 2001 From: Jaromir Obr Date: Thu, 10 Sep 2026 14:44:46 +0200 Subject: [PATCH] fix: use bunx instead of npx to read Playwright versions under Bun `getPlaywrightBrowsers()` hardcoded `npx playwright install --dry-run`. A Bun-only environment has no `npx` on PATH, so `execSync` throws and the catch reports `Playwright not installed` on a working install. That is a false negative rather than a visible error, so it reads as a real misconfiguration. Pick the package runner from the runtime that is actually executing, via `process.versions.bun`, the same detection already used by `getRuntimeInfo()`. `bunx playwright install --dry-run` resolves the local `node_modules/.bin/playwright` and emits output identical to the `npx` form, so `parsePlaywrightBrowsers()` is unaffected and Node behaviour is unchanged. This is the only place CodeceptJS shells out to `npx`; every other occurrence under `lib/` is documentation text. It removes the need to symlink `npx` to `bun` in Bun images. Closes #5713 Co-Authored-By: Claude Opus 5 --- lib/command/info.js | 11 +++++++++-- test/unit/command/info_test.js | 28 +++++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 3 deletions(-) diff --git a/lib/command/info.js b/lib/command/info.js index 073c0c98e..cad6c8bd6 100644 --- a/lib/command/info.js +++ b/lib/command/info.js @@ -21,9 +21,16 @@ function parsePlaywrightBrowsers(output) { return versions.join(', ') } +// Bun has its own package runner and a Bun-only install has no `npx` on PATH at all, so the +// runner has to follow the runtime that is actually executing rather than what PATH happens to hold. +function getPackageRunner() { + if (process.versions.bun) return 'bunx' + return 'npx' +} + async function getPlaywrightBrowsers() { try { - const info = execSync('npx playwright install --dry-run').toString().trim() + const info = execSync(`${getPackageRunner()} playwright install --dry-run`).toString().trim() return parsePlaywrightBrowsers(info) } catch (err) { return 'Playwright not installed' @@ -85,7 +92,7 @@ export default async function (path) { output.print('***************************************') } -export { parsePlaywrightBrowsers, getRuntimeInfo } +export { parsePlaywrightBrowsers, getRuntimeInfo, getPackageRunner } export const getMachineInfo = async () => { const info = { diff --git a/test/unit/command/info_test.js b/test/unit/command/info_test.js index a6780881a..0a921e4fa 100644 --- a/test/unit/command/info_test.js +++ b/test/unit/command/info_test.js @@ -1,5 +1,5 @@ import { expect } from 'chai' -import { parsePlaywrightBrowsers, getRuntimeInfo } from '../../../lib/command/info.js' +import { parsePlaywrightBrowsers, getRuntimeInfo, getPackageRunner } from '../../../lib/command/info.js' describe('info command', () => { describe('getRuntimeInfo', () => { @@ -42,6 +42,32 @@ describe('info command', () => { }) }) + describe('getPackageRunner', () => { + let originalBunVersion + + beforeEach(() => { + originalBunVersion = process.versions.bun + }) + + afterEach(() => { + if (originalBunVersion === undefined) { + delete process.versions.bun + } else { + process.versions.bun = originalBunVersion + } + }) + + it('should use bunx when running under Bun', () => { + process.versions.bun = '1.4.2' + expect(getPackageRunner()).to.equal('bunx') + }) + + it('should use npx when not running under Bun', () => { + delete process.versions.bun + expect(getPackageRunner()).to.equal('npx') + }) + }) + describe('parsePlaywrightBrowsers', () => { describe('old format (Playwright < 1.58)', () => { const oldFormatOutput = `browser: chromium version 140.0.7339.186