diff --git a/packages/playwright-core/src/cli/program.ts b/packages/playwright-core/src/cli/program.ts index a85ed67c11c20..802fb19732d8e 100644 --- a/packages/playwright-core/src/cli/program.ts +++ b/packages/playwright-core/src/cli/program.ts @@ -31,7 +31,7 @@ import type { Page } from '../client/page'; import type { BrowserType } from '../client/browserType'; import type { BrowserContextOptions, LaunchOptions } from '../client/types'; import { spawn } from 'child_process'; -import { wrapInASCIIBox, isLikelyNpxGlobal, assert, gracefullyProcessExitDoNotHang } from '../utils'; +import { wrapInASCIIBox, isLikelyNpxGlobal, assert, gracefullyProcessExitDoNotHang, getPackageManagerExecCommand } from '../utils'; import type { Executable } from '../server'; import { registry, writeDockerVersion } from '../server'; @@ -680,8 +680,10 @@ function buildBasePlaywrightCLICommand(cliTargetLang: string | undefined): strin return `mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="...options.."`; case 'csharp': return `pwsh bin/Debug/netX/playwright.ps1`; - default: - return `npx playwright`; + default: { + const packageManagerCommand = getPackageManagerExecCommand(); + return `${packageManagerCommand} playwright`; + } } } diff --git a/packages/playwright-core/src/server/android/android.ts b/packages/playwright-core/src/server/android/android.ts index 456ea92311133..d753b395ae368 100644 --- a/packages/playwright-core/src/server/android/android.ts +++ b/packages/playwright-core/src/server/android/android.ts @@ -21,7 +21,7 @@ import os from 'os'; import path from 'path'; import type * as stream from 'stream'; import { wsReceiver, wsSender } from '../../utilsBundle'; -import { createGuid, makeWaitForNextTask, isUnderTest } from '../../utils'; +import { createGuid, makeWaitForNextTask, isUnderTest, getPackageManagerExecCommand } from '../../utils'; import { removeFolders } from '../../utils/fileUtils'; import type { BrowserOptions, BrowserProcess } from '../browser'; import type { BrowserContext } from '../browserContext'; @@ -186,10 +186,11 @@ export class AndroidDevice extends SdkObject { debug('pw:android')('Installing the new driver'); const executable = registry.findExecutable('android')!; + const packageManagerCommand = getPackageManagerExecCommand(); for (const file of ['android-driver.apk', 'android-driver-target.apk']) { const fullName = path.join(executable.directory!, file); if (!fs.existsSync(fullName)) - throw new Error('Please install Android driver apk using `npx playwright install android`'); + throw new Error(`Please install Android driver apk using '${packageManagerCommand} playwright install android'`); await this.installApk(await fs.promises.readFile(fullName)); } } else { diff --git a/packages/playwright-core/src/server/registry/index.ts b/packages/playwright-core/src/server/registry/index.ts index 22a21ce88f1db..1bfb02d7f49d7 100644 --- a/packages/playwright-core/src/server/registry/index.ts +++ b/packages/playwright-core/src/server/registry/index.ts @@ -23,7 +23,7 @@ import { lockfile } from '../../utilsBundle'; import { getLinuxDistributionInfo } from '../../utils/linuxUtils'; import { fetchData } from '../../utils/network'; import { getEmbedderName } from '../../utils/userAgent'; -import { getFromENV, getAsBooleanFromENV, calculateSha1, wrapInASCIIBox } from '../../utils'; +import { getFromENV, getAsBooleanFromENV, calculateSha1, wrapInASCIIBox, getPackageManagerExecCommand } from '../../utils'; import { removeFolders, existsAsync, canAccessFile } from '../../utils/fileUtils'; import { hostPlatform } from '../../utils/hostPlatform'; import { spawnAsync } from '../../utils/spawnAsync'; @@ -1008,8 +1008,10 @@ export function buildPlaywrightCLICommand(sdkLanguage: string, parameters: strin return `mvn exec:java -e -D exec.mainClass=com.microsoft.playwright.CLI -D exec.args="${parameters}"`; case 'csharp': return `pwsh bin/Debug/netX/playwright.ps1 ${parameters}`; - default: - return `npx playwright ${parameters}`; + default: { + const packageManagerCommand = getPackageManagerExecCommand(); + return `${packageManagerCommand} playwright ${parameters}`; + } } } diff --git a/packages/playwright-core/src/utils/env.ts b/packages/playwright-core/src/utils/env.ts index 27f10f384b126..3a13296d8f774 100644 --- a/packages/playwright-core/src/utils/env.ts +++ b/packages/playwright-core/src/utils/env.ts @@ -34,3 +34,12 @@ export function getPackageManager() { return 'pnpm'; return 'npm'; } + +export function getPackageManagerExecCommand() { + const packageManager = getPackageManager(); + if (packageManager === 'yarn') + return 'yarn'; + if (packageManager === 'pnpm') + return 'pnpm exec'; + return 'npx'; +} diff --git a/packages/playwright-test/src/common/testType.ts b/packages/playwright-test/src/common/testType.ts index 3c51c8f1062ed..86e2174a3f84b 100644 --- a/packages/playwright-test/src/common/testType.ts +++ b/packages/playwright-test/src/common/testType.ts @@ -21,6 +21,7 @@ import { wrapFunctionWithLocation } from '../transform/transform'; import type { FixturesWithLocation } from './config'; import type { Fixtures, TestType } from '../../types/test'; import type { Location } from '../../types/testReporter'; +import { getPackageManagerExecCommand } from 'playwright-core/lib/utils'; const testTypeSymbol = Symbol('testType'); @@ -242,8 +243,9 @@ export class TestTypeImpl { function throwIfRunningInsideJest() { if (process.env.JEST_WORKER_ID) { + const packageManagerCommand = getPackageManagerExecCommand(); throw new Error( - `Playwright Test needs to be invoked via 'npx playwright test' and excluded from Jest test runs.\n` + + `Playwright Test needs to be invoked via '${packageManagerCommand} playwright test' and excluded from Jest test runs.\n` + `Creating one directory for Playwright tests and one for Jest is the recommended way of doing it.\n` + `See https://playwright.dev/docs/intro for more information about Playwright Test.`, ); diff --git a/packages/playwright-test/src/reporters/base.ts b/packages/playwright-test/src/reporters/base.ts index bfb5df663b733..12d1d0fe33954 100644 --- a/packages/playwright-test/src/reporters/base.ts +++ b/packages/playwright-test/src/reporters/base.ts @@ -18,7 +18,7 @@ import { colors, ms as milliseconds, parseStackTraceLine } from 'playwright-core import path from 'path'; import type { FullConfig, TestCase, Suite, TestResult, TestError, FullResult, TestStep, Location } from '../../types/testReporter'; import type { SuitePrivate } from '../../types/reporterPrivate'; -import { monotonicTime } from 'playwright-core/lib/utils'; +import { getPackageManagerExecCommand, monotonicTime } from 'playwright-core/lib/utils'; import type { ReporterV2 } from './reporterV2'; export type TestResultOutput = { chunk: string | Buffer, type: 'stdout' | 'stderr' }; export const kOutputSymbol = Symbol('output'); @@ -298,9 +298,10 @@ export function formatFailure(config: FullConfig, test: TestCase, options: {inde resultLines.push(colors.cyan(` ${relativePath}`)); // Make this extensible if (attachment.name === 'trace') { + const packageManagerCommand = getPackageManagerExecCommand(); resultLines.push(colors.cyan(` Usage:`)); resultLines.push(''); - resultLines.push(colors.cyan(` npx playwright show-trace ${relativePath}`)); + resultLines.push(colors.cyan(` ${packageManagerCommand} playwright show-trace ${relativePath}`)); resultLines.push(''); } } else { diff --git a/packages/playwright-test/src/reporters/html.ts b/packages/playwright-test/src/reporters/html.ts index ba78c372f9988..2ade2b105fe16 100644 --- a/packages/playwright-test/src/reporters/html.ts +++ b/packages/playwright-test/src/reporters/html.ts @@ -15,7 +15,7 @@ */ import { colors, open } from 'playwright-core/lib/utilsBundle'; -import { MultiMap } from 'playwright-core/lib/utils'; +import { MultiMap, getPackageManagerExecCommand } from 'playwright-core/lib/utils'; import fs from 'fs'; import path from 'path'; import type { TransformCallback } from 'stream'; @@ -128,11 +128,12 @@ class HtmlReporter extends EmptyReporter { if (shouldOpen) { await showHTMLReport(this._outputFolder, this._options.host, this._options.port, singleTestId); } else if (!FullConfigInternal.from(this.config)?.cliListOnly) { + const packageManagerCommand = getPackageManagerExecCommand(); const relativeReportPath = this._outputFolder === standaloneDefaultFolder() ? '' : ' ' + path.relative(process.cwd(), this._outputFolder); console.log(''); console.log('To open last HTML report run:'); console.log(colors.cyan(` - npx playwright show-report${relativeReportPath} + ${packageManagerCommand} playwright show-report${relativeReportPath} `)); } } diff --git a/packages/playwright-test/src/runner/watchMode.ts b/packages/playwright-test/src/runner/watchMode.ts index f730efcebb6ae..2f7f758fd740d 100644 --- a/packages/playwright-test/src/runner/watchMode.ts +++ b/packages/playwright-test/src/runner/watchMode.ts @@ -15,7 +15,7 @@ */ import readline from 'readline'; -import { createGuid, ManualPromise } from 'playwright-core/lib/utils'; +import { createGuid, getPackageManagerExecCommand, ManualPromise } from 'playwright-core/lib/utils'; import type { FullConfigInternal, FullProjectInternal } from '../common/config'; import { InternalReporter } from '../reporters/internalReporter'; import { createFileMatcher, createFileMatcherFromArguments } from '../util'; @@ -384,8 +384,9 @@ let showBrowserServer: PlaywrightServer | undefined; let seq = 0; function printConfiguration(config: FullConfigInternal, title?: string) { + const packageManagerCommand = getPackageManagerExecCommand(); const tokens: string[] = []; - tokens.push('npx playwright test'); + tokens.push(`${packageManagerCommand} playwright test`); tokens.push(...(config.cliProjectFilter || [])?.map(p => colors.blue(`--project ${p}`))); if (config.cliGrep) tokens.push(colors.red(`--grep ${config.cliGrep}`));