microsoft/playwright · error · Error

fs is not available in the browser

Error message

fs is not available in the browser

What it means

Thrown by the browser-bundle stub of Node's `fs` module: any access to `fs`, `fs.promises.*`, `fs.createReadStream`, or `fs.createWriteStream` invokes `notAvailable()` which throws. The `promises` object is a Proxy whose every property getter throws, so even `fs.promises.readFile` triggers it.

Source

Thrown at packages/playwright-client/src/nodeStubs/fs.ts:18

/**
 * Copyright (c) Microsoft Corporation.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

function notAvailable(): never {
  throw new Error('fs is not available in the browser');
}

export const promises: any = new Proxy({}, { get: () => notAvailable });
export const createReadStream: any = notAvailable;
export const createWriteStream: any = notAvailable;

export default { promises, createReadStream, createWriteStream };

View on GitHub (pinned to c8fc3bf8d3)

Solutions

  1. Gate fs usage behind an environment check (`import.meta.env?.SSR`, `typeof window`) and only call it in Node.
  2. Move fs-dependent logic out of the browser bundle into a server/Node entry point.
  3. Configure the bundler to externalize `fs` for the Node target instead of aliasing the stub.
  4. Replace fs reads with the browser-friendly API (fetch, File API) where applicable.

Example fix

// before
import fs from 'fs';
const cfg = fs.readFileSync('./config.json', 'utf8');  // throws in browser bundle

// after
let cfg: string;
if (typeof window === 'undefined') {
  const fs = await import('fs');
  cfg = fs.readFileSync('./config.json', 'utf8');
} else {
  const res = await fetch('/config.json');
  cfg = await res.text();
}
Defensive patterns

Strategy: type-guard

Validate before calling

const isBrowserBundle = typeof window !== 'undefined' && (import.meta as any).env?.SSR === false;
function safeReadFileSync(p: string): string {
  if (isBrowserBundle) throw new Error('fs reads are disabled in the browser bundle');
  return require('fs').readFileSync(p, 'utf8');
}

Type guard

function fsAvailable(): boolean {
  return typeof window === 'undefined' && typeof process !== 'undefined' && !!process.versions?.node;
}

Try / catch

try {
  return fs.readFileSync(p, 'utf8');
} catch (e) {
  if (/fs is not available in the browser/.test(e.message)) {
    return await (await fetch(p)).text();
  }
  throw e;
}

Prevention

When it happens

Trigger: Code that imports/uses `fs` in a context resolved to `packages/playwright-client/src/nodeStubs/fs.ts` — i.e. the browser bundle of @playwright/client. Triggered by `import fs from 'fs'` then `fs.readFileSync(...)`, `fs.promises.readFile(...)`, or `fs.createReadStream(...)`.

Common situations: Pulling a Node-only utility into the browser client build; a dependency that lazily requires `fs`; bundler resolving `fs` to the stub instead of leaving it external; code shared between Node and browser that calls fs unconditionally.

Related errors


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