microsoft/playwright · error · Error

Failed to launch ${this._name} because executable doesn't ex

Error message

Failed to launch ${this._name} because executable doesn't exist at ${executablePath}

What it means

Thrown by resolveExecutablePath when options.executablePath is set (truthy) but existsAsync reports the path does not exist on disk. Playwright checks the custom path before falling back to the registry so a wrong explicit path fails fast rather than producing a confusing spawn error.

Source

Thrown at packages/playwright-core/src/server/browserType.ts:351

  }

  async prepareUserDataDir(options: types.LaunchOptions, userDataDir: string): Promise<void> {
  }

  supportsPipeTransport(options: types.LaunchOptions): boolean {
    return true;
  }

  getExecutableName(options: types.LaunchOptions): string {
    return options.channel || this._name;
  }

  protected async resolveExecutablePath(options: types.LaunchOptions): Promise<string | undefined> {
    const { executablePath } = options;
    if (!executablePath)
      return undefined;
    if (!(await existsAsync(executablePath)))
      throw new Error(`Failed to launch ${this._name} because executable doesn't exist at ${executablePath}`);
    return executablePath;
  }

  abstract defaultArgs(options: types.LaunchOptions, isPersistent: boolean, userDataDir: string): Promise<string[]>;
  abstract connectToTransport(transport: ConnectionTransport, options: BrowserOptions, browserLogsCollector: RecentLogsCollector): Promise<Browser>;
  abstract amendEnvironment(env: NodeJS.ProcessEnv, userDataDir: string, isPersistent: boolean, options: types.LaunchOptions): NodeJS.ProcessEnv;
  abstract doRewriteStartupLog(logs: string): string;
  abstract attemptToGracefullyCloseBrowser(transport: ConnectionTransport): void;
}

function copyTestHooks(from: object, to: object) {
  for (const [key, value] of Object.entries(from)) {
    if (key.startsWith('__testHook'))
      (to as any)[key] = value;
  }
}

View on GitHub (pinned to c8fc3bf8d3)

Solutions

  1. Verify the path with fs.existsSync before launching; print the absolute path you are passing.
  2. Point executablePath at the real binary, or remove it to let Playwright use the installed/registry browser.
  3. If the binary lives elsewhere per environment, resolve it from a validated env var with a fallback to the registry.

Example fix

// before
const b = await chromium.launch({ executablePath: process.env.BROWSER_BIN });
// after
const p = process.env.BROWSER_BIN;
if (p && !fs.existsSync(p)) throw new Error(`BROWSER_BIN not found: ${p}`);
const b = await chromium.launch({ executablePath: p });
Defensive patterns

Strategy: validation

Validate before calling

import fs from 'fs';
function resolveExecutable(p?: string): string | undefined {
  if (!p) return undefined;
  if (!fs.existsSync(p)) throw new Error(`executablePath does not exist: ${p}`);
  return p;
}
// chromium.launch({ executablePath: resolveExecutable(process.env.BROWSER_BIN) })

Type guard

function executableExists(p?: string): boolean {
  return !p || fs.existsSync(p);
}

Prevention

When it happens

Trigger: chromium.launch({ executablePath: '/wrong/path/chrome' }) where the file is absent. Relative path that resolves against an unexpected cwd.

Common situations: Hardcoded path from another machine/container. Typo. Path computed from an env var that is unset. Cross-platform path separators. ARCH mismatch where the path exists for another OS.

Related errors


AI-assisted analysis of microsoft/playwright@c8fc3bf8d3 (2026-08-12). Data as JSON: /api/errors/99cc4682404fdbc5. Report an issue: GitHub.