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
- Target a browser/DOM runtime that implements MutationObserver (all evergreen browsers do).
- If using jsdom/happy-dom in tests, enable their MutationObserver implementation.
- Avoid text selectors ('text/', '::p-text') in environments known to lack MutationObserver.
- 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
- Target evergreen browsers that ship MutationObserver.
- Enable MutationObserver in jsdom/happy-dom test configs.
- Feature-detect before relying on text selectors.
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
- Not in WSL or unsupported version of WSL.
- Error: failed to find element matching selector "${selector}
- Element is not a(n) `${tagName}` element
- Node is either not clickable or not an Element
- Element is not a <select> element.
AI-assisted analysis of puppeteer/puppeteer@d484e21c17 (2026-08-12).
Data as JSON: /api/errors/5d126aef612be00f.
Report an issue: GitHub.