Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

If PhantomJS reaches page.open‘s callback but the content is still missing, the page may have started an AJAX request that has not finished yet. Check that navigation succeeded, load jQuery before using it if necessary, wait for a signal that the expected data has appeared, and only then read the DOM with page.evaluate. Neither navigation completion nor jQuery’s document.ready guarantees that later AJAX-rendered content is ready.

Why PhantomJS can reach document.ready before AJAX content appears

There are several distinct events that are easy to mistake for one another:

  • page.open callback: PhantomJS reports whether navigation completed with status success or fail. A successful status does not establish that every script-initiated request has finished.
  • jQuery $(document).ready(...): this is a DOM-readiness milestone. It is not a signal that an application’s later AJAX request has returned or that its response has been inserted into the page.
  • Application data ready: this is the point your scraper needs. Identify it using a result element, a loading indicator disappearing, an expected item count, or an application flag.

So if $('#results').text() is empty inside document.ready, the issue may be timing rather than a broken selector or a failure to load jQuery. Wait for an application-specific completion condition instead of treating DOM ready as the finish line.

Use this sequence to wait for the rendered result

  1. Open the page and check the status. Continue only if the callback status is success; log the URL and diagnose navigation failures otherwise.
  2. Ensure jQuery is available. If the page already includes it, use the page’s copy. If not, inject it with page.includeJs and put code that depends on it inside that method’s callback.
  3. Wait for a completion signal. Prefer a selector, flag, or count tied to the data you need. A fixed delay is only a fallback when no meaningful signal exists.
  4. Read simple values with page.evaluate. Return text, booleans, numbers, arrays, or plain objects, not DOM nodes or functions.
  5. Exit after the asynchronous work finishes. Calling phantom.exit() early can stop the script before an injected library or the page’s data-loading work has completed.

Runnable PhantomJS example: poll for a result element

This example assumes the target application adds #results-loaded when its data is ready and puts the result in #results. Replace both selectors and the timeout with values appropriate to the page. It does not require jQuery to read the DOM; the optional injection is included to show where jQuery-dependent work belongs when the page does not provide it.

Free tools Windows power users keep installed

One-click scans. No signup required.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
var page = require('webpage').create();

page.onError = function (msg, trace) {
  console.log('page error: ' + msg);
};
page.onResourceError = function (resourceError) {
  console.log('resource error: ' + resourceError.url + ' :: ' + resourceError.errorString);
};

var url = 'https://example.test';
page.open(url, function (status) {
  console.log('opened URL: ' + url);
  if (status !== 'success') {
    console.log('open failed: ' + status);
    phantom.exit();
    return;
  }

  // Skip this call if the target page already supplies jQuery.
  page.includeJs('https://ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js', function () {
    var deadline = Date.now() + 10000;

    function poll() {
      var ready = page.evaluate(function () {
        return !!document.querySelector('#results-loaded');
      });

      if (ready || Date.now() >= deadline) {
        var result = page.evaluate(function () {
          var node = document.querySelector('#results');
          return node ? node.textContent : '';
        });
        console.log(ready ? result : 'Timed out waiting for results; current text: ' + result);
        phantom.exit();
        return;
      }

      setTimeout(poll, 100);
    }

    poll();
  });
});

PhantomJS documents the success/fail callback status for page.open in its WebPage API. Its evaluate documentation explains that values crossing the page boundary must be serializable: return a string or other simple data rather than a DOM node. The automation guide warns that phantom.exit() belongs inside the includeJs callback when using that method, or the process can exit before the library loads.

Choose a synchronization signal that matches the page

Result selector appears

Use this when the application adds a container or status element only after rendering data. Make the selector specific enough to distinguish a completed result from an empty placeholder.

Loading marker disappears

If the page shows a spinner or loading message, wait until it is removed or hidden. Confirm that this marker reliably means the response has been processed; some interfaces hide it before the final content is painted.

Count or page flag changes

For a list, wait until the number of rows reaches a known threshold or an application-owned flag changes. The condition should represent the data you need rather than a generic page event.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Rank #2
Sale
HTML and CSS: Design and Build Websites
  • HTML CSS Design and Build Web Sites
  • Comes with secure packaging
  • It can be a gift option

Fixed delay as a last resort

A delay such as setTimeout is simple but has no knowledge of the network or rendering. If it is too short, the result is empty; if too long, every run wastes time. Use a finite deadline and report a timeout distinctly from a successful capture rather than silently treating an empty result as valid.

When jQuery injection is involved

page.includeJs is useful only if the page lacks the jQuery library you intend to use. Its callback marks when the injected script has loaded, so dependent calls should be nested inside it. If you call jQuery code immediately after requesting injection, it may run before $ exists. If the site’s own jQuery is already loaded, injecting another copy may be unnecessary and can complicate debugging.

Even after jQuery is present, $(document).ready(...) still does not wait for an arbitrary AJAX callback. If you control the page, the most reliable signal is one set by the application’s successful data handler—for example, a class, attribute, or JavaScript flag updated after it inserts the response. If you do not control the page, observe the rendered DOM for a stable result-specific condition.

Diagnose an empty result systematically

Navigation status is not success

Log the page.open status and the URL actually opened. On fail, waiting longer for a selector will not repair the navigation. Check the URL, redirects, connectivity, and resource errors.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Page JavaScript threw an exception

Attach page.onError before navigation and log its message. A script exception may prevent the handler that fetches or inserts data from running.

An API request or script failed

Log resource request, response, and error events while diagnosing. This helps separate a synchronization problem from a failed API call, missing script, certificate issue, or interrupted transfer. A DOM-ready event cannot compensate for a request that never returned successfully.

The queried element is elsewhere

Verify that the selector exists in the live page and that the content is not inside an iframe or shadow DOM. A selector in the top-level document cannot automatically locate content in a different browsing context, and older PhantomJS engines may not support modern page features as expected.

The evaluate return value is unsupported

Inside page.evaluate, extract a value such as node.textContent or a plain object of fields. PhantomJS cannot transfer a live DOM node, closure, or function to the outer script; convert the information in the page context before returning it.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Rank #4
Sale
Web Design with HTML, CSS, JavaScript and jQuery Set
  • Brand: Wiley
  • Set of 2 Volumes
  • A handy two-book set that uniquely combines related technologies Highly visual format and accessible language makes these books highly effective learning tools Perfect for beginning web designers and front-end developers

The page still appears to be loading

For diagnosis, inspect page.loading and page.loadingProgress. PhantomJS guidance describes a progress value of 100 as fully loaded, but generic load progress is still not equivalent to your application’s AJAX-complete condition.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Reliability, runtime, and cost considerations

Use a specific condition plus a deadline rather than an unlimited poll. The polling interval controls how often the page context is queried; shorter intervals can detect readiness sooner but do more polling, while longer intervals may add delay after the page is ready. Select an interval appropriate to your workload and tolerate the fact that the page may never satisfy the condition.

For repeated captures, log the final status, timeout outcome, and relevant resource errors. This makes an empty result distinguishable from an unsuccessful navigation or a page-side failure. A successful screenshot or scrape run should mean the expected content condition was met, not merely that a timer expired.

PhantomJS is a legacy headless-browser option, so modern site behavior may expose engine-compatibility problems that waiting cannot fix. If the page depends on browser capabilities PhantomJS does not handle, validate whether the target can render in that engine before investing in more elaborate polling logic.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Or skip the browser setup

If your goal is a clean website screenshot rather than extracting text from the DOM, ScreenshotNeo provides a one-request screenshot API and an MCP server for AI agents. Its screenshot request can return PNG, JPEG, WebP, or PDF; it is not a replacement for a PhantomJS script that needs to inspect application data.

For a screenshot of a page, the cURL request below uses the documented API endpoint. See the ScreenshotNeo documentation for available request options.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://example.test -o shot.webp

ScreenshotNeo removes supported cookie and consent banners, newsletter popups, and chat widgets before capture; those steps can be turned off. Bot checks, blank pages, failed loads, timeouts, and cache hits are not billed, and response headers report the page verdict and billing status. AI agents can use its MCP server tools for screenshots, page information, and PDF capture. The free plan includes 1,000 shots per month with no card; paid plans start at $5 for 3,000 shots.

Sign up for ScreenshotNeo’s free plan to try 1,000 screenshots a month without a card.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Frequently Asked Questions

Does PhantomJS document.ready wait for AJAX?

No. It indicates DOM readiness, not completion of a later AJAX request. Wait for a condition tied to the rendered data.

Can I return a DOM element from page.evaluate?

No. Return serializable values such as text, numbers, booleans, arrays, or plain objects.

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.