ScreenshotNeo

BlogHow-to

How to Wait for a Page to Finish Loading in PhantomJS Before a Screenshot

Capture a PhantomJS screenshot after navigation succeeds, and wait for a page-specific signal when content continues loading afterward.

By the ScreenshotNeo team4 October 20266 min read

To capture a PhantomJS page after its normal browser load finishes, call page.open(url, callback), check that the callback status is 'success', and call page.render() inside the callback. This callback runs through page.onLoadFinished. It does not guarantee that later client-side rendering, timers, or asynchronous API calls have finished.

Basic screenshot after page load

Save this as capture.js and run it with PhantomJS, passing the URL and output filename as arguments:

var page = require('webpage').create();
var system = require('system');
var url = system.args[1] || 'https://example.com';
var output = system.args[2] || 'example.png';

page.open(url, function(status) {
  if (status === 'success') {
    page.render(output);
    console.log('Screenshot saved to ' + output);
    phantom.exit(0);
  }

  console.error('Page navigation failed: ' + status);
  phantom.exit(1);
});

Run it with phantomjs capture.js https://example.com example.png. Rendering inside the callback ensures navigation has reached the load-finished event before the screenshot is taken. The explicit exit on both paths matters: PhantomJS scripts need phantom.exit() to terminate.

What “finished loading” means

The page.open callback reports the navigation status when the page load event finishes. A success status means the navigation completed; it is not a promise that every application task has settled. Single-page apps may render data after load, and pages can change later because of timers, delayed requests, animations, or lazy content.

Choose the readiness signal that matches what the screenshot must contain:

Approach Use it when Trade-off
page.open callback The normal document load is the desired capture point. Simple and directly tied to navigation, but does not cover later app work.
Wait for a specific DOM condition A known element appears or changes when the relevant content is ready. More precise; requires a page-specific condition and a timeout.
Wait for a fixed delay The page has no reliable readiness signal and a short settling period is acceptable. Can be too short on slow runs and unnecessarily long on fast ones.
page.loading and page.loadingProgress You need diagnostics about loading state or progress. Useful for observation; a progress value of 100 is not a substitute for an app-specific readiness condition.

Wait for content rendered after load

When the page updates after navigation, wait for a meaningful signal. The following example waits for a selector to exist, checks periodically, and exits with an error if the condition is not met before the deadline. It assumes the page exposes #report-ready only after the content needed for the screenshot is present.

var page = require('webpage').create();
var system = require('system');
var url = system.args[1] || 'https://example.com';
var output = system.args[2] || 'report.png';
var readySelector = system.args[3] || '#report-ready';
var timeoutMs = 15000;
var pollMs = 100;
var startedAt;
var timer;

function finish(code) {
  if (timer) {
    clearInterval(timer);
    timer = null;
  }
  phantom.exit(code);
}

page.open(url, function(status) {
  if (status !== 'success') {
    console.error('Page navigation failed: ' + status);
    finish(1);
    return;
  }

  startedAt = Date.now();
  timer = setInterval(function() {
    var ready = page.evaluate(function(selector) {
      return document.querySelector(selector) !== null;
    }, readySelector);

    if (ready) {
      page.render(output);
      console.log('Screenshot saved to ' + output);
      finish(0);
      return;
    }

    if (Date.now() - startedAt >= timeoutMs) {
      console.error('Timed out waiting for selector: ' + readySelector);
      finish(1);
    }
  }, pollMs);
});

Run it by providing a selector that represents actual readiness, for example phantomjs capture-ready.js https://example.com report.png '#report-ready'. If the content can be present before it is complete, wait for a stronger condition, such as a completion attribute or a non-empty value. Keep a finite timeout so a missing selector cannot leave the process running forever.

Use a fixed delay only when necessary

If there is no observable readiness condition, start a timer after successful navigation and render after the chosen delay. This is less reliable: a fixed delay may miss content on a slow page, while a long delay wastes time on a fast page. Keep the timeout bounded and tune the delay to the page’s behavior.

Configure loading behavior before navigation

PhantomJS page settings apply during the initial page.open() call, so set them before opening the URL. JavaScript and image loading are enabled by default. resourceTimeout is in milliseconds; when a resource exceeds it, PhantomJS stops trying to load that resource and calls page.onResourceTimeout.

var page = require('webpage').create();
page.settings.javascriptEnabled = true;
page.settings.loadImages = true;
page.settings.resourceTimeout = 10000;

page.onResourceTimeout = function(request) {
  console.error('Resource timed out: ' + request.url);
};

page.open('https://example.com', function(status) {
  if (status === 'success') {
    page.render('example.png');
  } else {
    console.error('Navigation failed: ' + status);
  }
  phantom.exit(status === 'success' ? 0 : 1);
});

A resource timeout controls an individual resource load; it does not define when arbitrary application work is complete. If the page depends on a slow script or API response, increase the resource timeout as appropriate and still wait for the content’s own readiness signal.

Common errors and fixes

Symptom Likely cause Fix
The screenshot is blank or incomplete. Rendering happened before the load callback, or the page fills in content afterward. Render only after successful navigation. For late content, wait for a specific DOM or application condition.
The script exits without saving an image. page.open returned 'fail', or the output path is not writable. Log the status, handle failure explicitly, and check the destination path and permissions.
The script never exits. An exit path is missing, or a readiness wait has no timeout. Call phantom.exit() on success and failure; add a finite timeout to polling or timers.
A resource stops loading too early. resourceTimeout is shorter than the resource’s response time. Raise the timeout in milliseconds and inspect onResourceTimeout to identify the slow resource.
The screenshot is missing images or scripts. Loading settings were changed, or resources failed or timed out. Confirm loadImages and javascriptEnabled are enabled, and inspect resource failures.
A selector wait times out even though the page looks ready. The selector does not match the actual DOM, appears in a different frame, or the page uses a different readiness marker. Verify the selector against the rendered DOM and wait for a condition available in the relevant page context.

Performance and reliability notes

  • Use the load callback alone when it matches the desired capture point; extra waiting increases runtime without improving that screenshot.
  • Prefer condition-based waits over generous fixed delays. Poll at a reasonable interval and enforce a maximum wait.
  • Only wait for resources and content needed in the image. Raising every timeout can make failures take longer to report.
  • Keep failure handling explicit. A failed navigation or readiness timeout should produce a nonzero exit status so calling scripts can detect it.
  • PhantomJS is a legacy tool. Its documented events and settings define the behavior described here; this guide makes no claim about current browser compatibility or performance.

Or skip the browser setup

ScreenshotNeo is a website screenshot API: send one GET request with a URL and receive an image or PDF. See the API documentation for the request options.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
  • Cookie banners are accepted and removed before capture, along with known consent platforms, newsletter popups, and chat widgets; each step can be turned off.
  • Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed. Response headers report the page verdict and billing status.
  • An MCP server gives AI agents tools to take screenshots, get page information, and capture PDFs.
  • The free plan includes 1,000 screenshots a month with no card. Paid plans start at $5 for 3,000 screenshots.

Sign up free for 1,000 screenshots a month, with no card required.

FAQ

Does page.open wait for every network request?

Its callback is tied to the page load-finished event. Do not treat it as proof that all later asynchronous application work has stopped.

Should I wait until page.loadingProgress reaches 100?

Use it as a diagnostic signal. For content rendered after load, a page-specific readiness condition is more meaningful.

What is the best timeout?

Set resource and readiness timeouts based on the page and resources the screenshot needs. Always use a finite readiness deadline and report when it expires.