puppeteer/puppeteer · error · Error

MutationObserver is not available in this environment.

Error message

MutationObserver is not available in this environment.

What it means

TextContent (the injected text selector engine) lazily builds a MutationObserver to invalidate its cached text when the DOM mutates. If globalThis.MutationObserver is undefined when getTextChangeObserver() runs, it throws — the engine cannot safely cache text content without a way to observe mutations.

Source

Thrown at packages/puppeteer-core/src/injected/TextContent.ts:80

  while (node) {
    textContentCache.delete(node);
    if (node instanceof ShadowRoot) {
      node = node.host;
    } else {
      node = node.parentNode;
    }
  }
};

/**
 * Erases the cache when the tree has mutated text.
 */
const observedNodes = new WeakSet<Node>();
let textChangeObserver: MutationObserver;
const getTextChangeObserver = () => {
  const MutationObserverImpl = globalThis.MutationObserver;
  if (!MutationObserverImpl) {
    throw new Error('MutationObserver is not available in this environment.');
  }
  if (!textChangeObserver) {
    textChangeObserver = new MutationObserverImpl(mutations => {
      for (const mutation of mutations) {
        eraseFromCache(mutation.target);
      }
    });
  }
  return textChangeObserver;
};

/**
 * Builds the text content of a node using some custom logic.
 *
 * @remarks
 * The primary reason this function exists is due to {@link ShadowRoot}s not having
 * text content.
 *

View on GitHub (pinned to d484e21c17)

Solutions

  1. Target a browser/DOM runtime that implements MutationObserver (all evergreen browsers do).
  2. If using jsdom/happy-dom in tests, enable their MutationObserver implementation.
  3. Avoid text selectors ('text/', '::p-text') in environments known to lack MutationObserver.
  4. Feature-detect globalThis.MutationObserver before relying on text selectors.

Example fix

// before
await page.$('::p-text(Sign in)'); // throws if MutationObserver missing

// after
if (!globalThis.MutationObserver) {
  // fall back to XPath or aria selectors that don't need the observer
  await page.$('//button[contains(., "Sign in")]');
}
Defensive patterns

Strategy: type-guard

Validate before calling

function supportsTextSelector(): boolean {
  return typeof globalThis.MutationObserver === 'function';
}
if (!supportsTextSelector()) {
  // avoid '::p-text(...)' selectors; use xpath/aria instead
}

Type guard

const hasMutationObserver = (): boolean =>
  typeof globalThis.MutationObserver === 'function';

Try / catch

try {
  await page.$('::p-text(Sign in)');
} catch (e) {
  if (e instanceof Error && e.message === 'MutationObserver is not available in this environment.') {
    await page.$('//button[contains(., "Sign in")]'); // xpath fallback
  } else throw e;
}

Prevention

When it happens

Trigger: Running the injected TextContent engine in a JS environment that lacks MutationObserver (very old browsers, minimal jsdom/happy-dom configurations, some workers). Triggered the first time a text selector ('::p-text(...)') is evaluated.

Common situations: Running Puppeteer's selector logic against a non-standard DOM (jsdom with MutationObserver disabled); old embedded browser views; headless shells stripped of the observer API.

Related errors


AI-assisted analysis of puppeteer/puppeteer@d484e21c17 (2026-08-12). Data as JSON: /api/errors/5d126aef612be00f. Report an issue: GitHub.