A Puppeteer waitForSelector timeout means the expected selector was not observed in the document context Puppeteer searched before its deadline. In Kubernetes, first establish whether the page reached the expected URL and DOM, then check selector visibility and frame scope, browser startup time, and Pod restarts. Increase the timeout only when evidence shows the page is correct but slower than the current limit.
What a waitForSelector timeout tells you—and what it does not
Puppeteer waits for a selector to appear in the DOM and throws if it does not appear before the configured deadline. The Puppeteer API documentation, as crawled in 2026, gives 30,000 milliseconds as the default timeout; timeout: 0 disables the limit, and page defaults can be changed.
The error does not by itself prove that Kubernetes killed Chromium, that the website is down, or that the page needs more time. It means only that the requested state was not observed in the document context searched. A wrong route, authentication or error page, misspelled selector, hidden element, iframe, or restarted browser can produce the same symptom. Raising the deadline addresses none of those causes.
Start by capturing the failure context
Collect enough evidence from the same deployment and build that produced the timeout. Log the URL after navigation, the exact selector string and options, elapsed time, page title, current URL, and a short HTML excerpt. Also record the Pod name, restart count, termination reason, and probe events.
Quick wins for a faster PC:
Clear out junk files and repair common Windows errorsFree Scan →Fix the driver behind crashes, sound loss and screen glitchesFind Drivers →Repair Windows errors before they cause bigger problemsFix Now →#1 Best Overall
Instrument the Puppeteer operation
This CommonJS example records navigation and selector failures and saves page evidence when the wait times out. Replace the example URL and selector with the values from your job. It uses a bounded timeout so a deterministic failure does not leave a worker waiting indefinitely.
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage();
const targetUrl = 'https://example.com';
const selector = '[data-testid="app-ready"]';
const startedAt = Date.now();
page.on('console', message => {
console.log('page console:', message.type(), message.text());
});
page.on('requestfailed', request => {
console.error('request failed:', request.url(), request.failure()?.errorText);
});
try {
const response = await page.goto(targetUrl, {
waitUntil: 'domcontentloaded',
timeout: 45000
});
console.log(JSON.stringify({
requestedUrl: targetUrl,
responseStatus: response?.status(),
currentUrl: page.url(),
title: await page.title()
}));
await page.waitForSelector(selector, { timeout: 60000 });
console.log('selector appeared after', Date.now() - startedAt, 'ms');
} catch (error) {
console.error('Puppeteer failure:', error.message);
console.error('elapsedMs:', Date.now() - startedAt);
console.error('currentUrl:', page.url());
console.error('title:', await page.title().catch(() => 'unavailable'));
console.error('html excerpt:', (await page.content()).slice(0, 4000));
await page.screenshot({ path: '/tmp/puppeteer-failure.png' }).catch(() => {});
throw error;
}
} finally {
await browser.close();
}
})().catch(error => {
console.error(error);
process.exitCode = 1;
});
The sample sets a separate navigation timeout because goto and waitForSelector are separate waits. Puppeteer’s default waitUntil for navigation is load; here domcontentloaded is used so the code can test the actual application selector rather than treating every late resource as a prerequisite.
Inspect the Pod and its events
Use Kubernetes’ own diagnostic path to connect the application error to its container lifecycle:
kubectl describe pod <pod-name> -n <namespace>
kubectl logs <pod-name> -n <namespace> -c <container-name>
kubectl logs <pod-name> -n <namespace> -c <container-name> --previous
Check the Pod’s events, restart count, termination reason, and timestamps against the Puppeteer log. The Kubernetes troubleshooting guide covers checks for Pods, Services, termination, init containers, and running containers. A browser or page lost during a container restart cannot finish its earlier selector wait.
What’s actually slowing this PC down?
Pick the symptom - the matching free tool is one click away.
Verify the selector against the page Puppeteer actually loaded
Compare page.url(), title, response status, screenshot, and HTML excerpt with what the successful run should show. A redirect to a login page, an application error route, different production markup, or a failed navigation can leave the expected element absent. In those cases, fix routing, authentication, deployment, or request handling; a longer timeout only delays the same failure.
Rank #2
Check spelling, case, and visibility
Test the selector against the rendered DOM in the same build and environment. Confirm its spelling, capitalization, and attributes. If you passed visible: true, Puppeteer is waiting for the element to be visible, not merely present. An element that exists but is hidden—for example, by display: none—will not satisfy that visibility condition until the page makes it visible.
Use the least restrictive condition that matches the job’s real requirement. If the next operation needs a visible button, use a visibility check; if it only needs a DOM node to exist, do not accidentally require visibility. Avoid waiting on a selector that can never occur in the error or unauthenticated state.
Check iframe and shadow-root boundaries
A selector evaluated on the main page cannot see DOM inside an iframe. Enumerate page.frames(), identify the frame whose URL or content should contain the target, and query through that frame:
Free tools Windows power users keep installed
One-click scans. No signup required.
for (const frame of page.frames()) {
console.log('frame URL:', frame.url());
}
const targetFrame = page.frames().find(frame => frame.url().includes('/embedded/'));
if (!targetFrame) {
throw new Error('Expected iframe was not found');
}
await targetFrame.waitForSelector('[data-testid="ready"]', { timeout: 30000 });
Adapt the frame URL test to the page; do not assume the first frame is the target. Shadow-root content also requires the appropriate selector strategy for that DOM rather than assuming a normal page-level selector can reach it.
Choose a readiness signal that means the page is usable
Navigation completion and application readiness are different events. A Puppeteer navigation can reach its chosen lifecycle point while client-side code is still rendering the component your job needs. Prefer a stable application selector or a specific API response tied to the required state.
Do not treat networkidle as a universal “page ready” signal. Analytics, polling, streaming, or other ongoing requests can keep the network active even after the useful interface is ready. Conversely, network quiet does not guarantee that the particular element or data your task needs has rendered. Puppeteer’s waiting guidance identifies a specific selector or response as usually more reliable.
Set the navigation timeout separately when navigation itself is slow. Keep it distinct from the selector timeout in logs and configuration so you can tell whether the job failed to reach the page or reached it but did not see the expected UI. A longer timeout can be appropriate when repeated measurements show that a correct page routinely renders the target after the current deadline; choose a bounded value based on those measurements.
Separate Chromium startup from page rendering
Measure time from worker process start to browser launch, first navigation, and selector appearance. A cold browser startup can consume much of a job’s time or make the Pod look unhealthy before Puppeteer reaches the page. Compare the measurements from successful and failing runs instead of applying one large timeout to every phase.
Kubernetes startup probes defer liveness and readiness checks until startup succeeds. The Kubernetes documentation states that when a startup probe is configured, liveness and readiness probes do not run until it succeeds. Readiness failures remove a Pod from Service endpoints; repeated liveness failures can restart the container.
Use a startup probe sized from observed startup time
The following example is a pattern, not a universal setting. The endpoint should report whether the browser worker itself has initialized; tune the values from measured startup behavior and the consequences of a slow or failed start.
Rank #4
startupProbe:
httpGet:
path: /health/startup
port: 8080
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 24
readinessProbe:
httpGet:
path: /health/ready
port: 8080
periodSeconds: 5
timeoutSeconds: 2
failureThreshold: 3
Keep readiness focused on whether the worker can accept jobs, rather than whether a particular remote website has rendered a selector. A website-specific wait can take much longer or fail for reasons unrelated to whether the worker should receive traffic.
Do not trust undersized probe defaults for a browser worker
Kubernetes’ probe configuration documentation, revised in 2025, lists defaults of 1 second for timeoutSeconds, 10 seconds for periodSeconds, and 3 consecutive failures for failureThreshold. Those defaults are easy to undersize for Chromium startup or a slow health endpoint. Set explicit values and check the effective Pod configuration rather than assuming a generous health-check window.
Correlate timeouts with resource pressure and restarts
Compare timeout timestamps with OOM kills, CPU throttling, eviction, node pressure, probe failures, and container restarts. If the browser becomes unresponsive or is terminated, selector logic may be sound while the process cannot complete its work. Use the Pod description, events, and current or previous container logs to establish whether the lifecycle changed during the wait.
- If the process restarted, determine why before retrying: the browser and page state from the prior process are gone.
- If the process remained alive but rendering slowed, compare startup and selector timings with CPU and memory pressure before adjusting job deadlines.
- If browser startup is slow but stable, size the startup probe around measured initialization rather than letting liveness checks interrupt it.
- If the page loaded quickly and the selector remained absent, return to route, selector, visibility, and DOM-scope checks.
Increase the timeout only after diagnosis
For a one-off wait, set the timeout on that wait. For waits that share a measured budget, Puppeteer page defaults can be changed. Setting timeout: 0 disables the limit, but it can turn an ordinary selector bug or unavailable page into a job that never completes; use it only when the surrounding system has another reliable cancellation mechanism.
Keep timeout values bounded and phase-specific: browser launch, navigation, and selector appearance answer different questions. Record actual elapsed times and choose limits that accommodate observed healthy runs while still letting failed jobs return actionable errors. Do not increase every limit just because the workload runs in Kubernetes.
The Tool Desk
Outbyte PC Repair FREERepair Windows errors before they cause bigger problemsFix Now →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Make the next failure actionable
On failure, retain the screenshot, current URL, HTML excerpt, console errors, failed requests, frame URLs, selector and options, elapsed time, and Pod identity. This lets you distinguish a DOM mismatch from an inaccessible frame, failed request, delayed render, or container restart without reproducing the exact workload manually.
Retry only an idempotent operation after confirming that the browser and page are alive. A retry may help with transient infrastructure trouble, but it cannot repair a deterministic selector mismatch, permanent auth redirect, or wrong frame scope. Preserve the first failure’s evidence before retrying so a later success does not erase the cause.
Troubleshooting by symptom
| Symptom | Likely explanation | Next check |
|---|---|---|
| Selector is absent and current URL differs from the target | Redirect, failed navigation, or unexpected route | Inspect navigation response, URL, title, and HTML excerpt; fix route or authentication. |
Element appears in HTML but the wait with visible: true times out |
The node exists but remains hidden | Check its rendered visibility and wait for the state the next action actually requires. |
| Element is visible in the browser but Puppeteer cannot find it | It may be in an iframe or shadow root | Inspect page.frames() and query the correct DOM context. |
| Failures cluster around Pod restarts or failed probes | Browser startup, health-check timing, or resource pressure | Correlate timestamps with events, termination reasons, resource pressure, and probe configuration. |
| Navigation finishes, but a page-wide network-idle wait hangs | Polling, analytics, streaming, or other ongoing requests | Wait for the required selector or a specific response instead. |
| Timeout occurs consistently at the same deadline on an otherwise correct page | The configured selector budget may be too short | Measure healthy render time, then raise the bounded selector timeout without changing unrelated phases. |
Or skip the browser setup
If the job’s deliverable is a website screenshot rather than browser automation inside your own Kubernetes worker, ScreenshotNeo provides a screenshot API and MCP server. A single GET request can return an image or PDF; the example below saves a WebP screenshot. See the ScreenshotNeo documentation for API parameters.
curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
ScreenshotNeo accepts cookie or consent banners as a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and responses identify the page verdict and billing status. Its MCP server includes take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients. The free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots. This is an option for screenshot capture, not a fix for a worker that must run arbitrary Puppeteer interactions or debug its Kubernetes probes.
Do these 3 things before closing this tab:
1Repair Windows errors before they cause bigger problems2Scan for outdated or missing drivers - takes under a minute3Clear out junk files and repair common Windows errorsSign up free for 1,000 screenshots a month, with no card required.
Frequently Asked Questions
Will a successful Kubernetes readiness probe prove the page selector is present?
No. Readiness should indicate whether the worker can accept jobs; a remote page’s DOM state is a separate, job-specific condition.
Should I retry every waitForSelector timeout?
No. Retry only when the operation is safe to repeat and evidence points to a transient failure; a repeat will not fix a deterministic selector or frame-scope error.
Quick Recap
Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.




