apify/crawlee · error
Selector '${selector}' not found.
Error message
Selector '${selector}' not found. What it means
JSDOMCrawler's waitForSelector is implemented with cheerio over the static body; it polls every 50ms until the timeout expires. If the selector still matches nothing when timeoutMs reaches 0, it throws 'Selector ... not found.' — it never re-fetches the page, so the selector must exist in the already-loaded HTML.
Source
Thrown at packages/jsdom-crawler/src/internals/jsdom-crawler.ts:433
return addRequests(urls, {
...options,
baseUrl,
strategy: options.strategy ?? EnqueueStrategy.SameHostname,
});
},
async waitForSelector(selector: string, timeoutMs = 5_000) {
const cheerio = await import('cheerio');
const $ = cheerio.load(crawlingContext.body);
if ($(selector).get().length === 0) {
if (timeoutMs) {
await sleep(50);
await this.waitForSelector(selector, Math.max(timeoutMs - 50, 0));
return;
}
throw new Error(`Selector '${selector}' not found.`);
}
},
async parseWithCheerio(selector?: string, _timeoutMs = 5_000) {
const cheerio = await import('cheerio');
const $ = cheerio.load(crawlingContext.body);
if (selector && $(selector).get().length === 0) {
throw new Error(`Selector '${selector}' not found.`);
}
return $;
},
};
}
}
/**
* Extracts URLs from a given Window object.View on GitHub (pinned to dbe57fb09c)
Solutions
- Verify the selector exists in the fetched HTML (log `context.body` or test with cheerio).
- Enable `runScripts: true` (or wait for window load) if the element is created by client-side JS.
- Increase timeoutMs, but remember the HTML is static — a longer wait only helps if scripts mutate the DOM.
- Fix selector typos and confirm the correct case-sensitive attribute syntax.
Example fix
// before
await context.waitForSelector('.dynamic-item', 0); // throws immediately if absent
// after
await context.waitForSelector('.dynamic-item', 5000); // poll up to 5s (JS must populate DOM) Defensive patterns
Strategy: try-catch
Validate before calling
const $ = (await import('cheerio')).load(context.body);
if ($(selector).get().length === 0) {
logger.warning(`Selector ${selector} not in served HTML; waitForSelector would exhaust timeout`);
} Type guard
null
Try / catch
try {
await context.waitForSelector('.item', 5000);
} catch (err) {
if ((err as Error).message.includes("not found")) {
logger.warning('Element never appeared in static HTML', { selector: '.item' });
return; // skip request or fallback parsing
}
throw err;
} Prevention
- Verify selectors against a saved copy of the HTML before adding them to production code.
- Remember the DOM is static — enable runScripts if JS must render the element.
- Use a positive timeout (not 0) when the element may appear via scripts.
- Detect bot-block/error pages (empty selectors) and treat them as request failures.
When it happens
Trigger: Calling `context.waitForSelector(selector)` with a selector absent from the parsed body, or with `timeoutMs = 0` for an element not present immediately; using selectors that only appear after client-side JS execution not reflected in the served HTML.
Common situations: Waiting for SPA-rendered elements while crawling server-rendered HTML; typos in selectors; elements injected by runScripts but the crawl raced the window load; passing timeout 0 expecting infinite wait.
Related errors
- Selector '${selector}' not found.
- Selector '${selector}' not found.
- The current SessionPool instance couldn't find a valid sessi
- Navigation timed out after ${this.#navigationTimeoutMillis /
- Selector '${selector}' not found.
AI-assisted analysis of apify/crawlee@dbe57fb09c (2026-08-30).
Data as JSON: /api/errors/44114f2f7b62e4d7.
Report an issue: GitHub.