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

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

To access content inside an iframe in PhantomJS, switch the WebPage object into that child frame, run page.evaluate() there, and return a JSON-serializable value such as text, an attribute, or HTML. When you are done, call page.switchToMainFrame() (or page.switchToParentFrame() for one level up). Querying before the switch examines the wrong document.

The frame context you are actually selecting

An iframe creates a child browsing context with its own document. PhantomJS keeps one frame active for page operations. page.evaluate() runs in that active context, so a selector such as document.querySelector('.total') only sees the document belonging to the currently selected frame.

The parent page and the child frame are different things:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • The parent document contains the <iframe> element itself. Its attributes (for example, src, name, width, or title) are read while the main frame is active.
  • The child browsing context contains the markup rendered inside the iframe. Switch into that context before querying its controls, text, or other elements.
  • window.frames[index] is a child-frame Window object, not an iframe DOM element. If you need the element node, query the parent document for iframe; if you need the child document, use PhantomJS frame switching.

PhantomJS exposes both named and positional switching. The available names and count are relative to whichever frame is active, which matters for nested iframes.

Need Use Important detail
Enter a named child frame page.switchToFrame('frameName') Returns a Boolean indicating whether the switch succeeded.
Enter a child by position page.switchToFrame(0) Position is evaluated among the active frame’s children; it can change as a page loads.
Return to the top document page.switchToMainFrame() Resets the active context to the page’s main frame.
Move up one level page.switchToParentFrame() Useful when walking out of nested frames.
Inspect the active frame’s children page.framesName and page.framesCount These describe child frames of the current context.

A minimal, runnable PhantomJS example

This script opens a page, enters a frame named checkout, reads an element inside it, and returns to the main document. The names and selector are illustrative; replace them with values from the page you are automating.

var page = require('webpage').create();

page.open('https://example.com', function (status) {
  if (status !== 'success') {
    console.error('Unable to load the page');
    phantom.exit(1);
    return;
  }

  var switched = page.switchToFrame('checkout');
  if (!switched) {
    console.error('Frame not found');
    phantom.exit(1);
    return;
  }

  var text = page.evaluate(function () {
    var node = document.querySelector('.total');
    return node ? node.textContent : null;
  });

  console.log(text);
  page.switchToMainFrame();
  phantom.exit();
});

Run it with the PhantomJS executable and your saved script, for example phantomjs read-frame.js. The callback runs after the page-open operation reports success; it does not prove that every dynamically inserted frame element is already present. For pages that build frames with script, wait for the relevant condition before switching.

Discovering an unnamed or changing frame

If the iframe has no useful name, inspect the active frame before choosing a position. The following diagnostic prints the child-frame names and count from the main document:

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

page.open('https://example.com', function (status) {
  if (status !== 'success') {
    console.error('Open failed');
    phantom.exit(1);
    return;
  }

  console.log('Child frame count: ' + page.framesCount);
  console.log('Child frame names: ' + JSON.stringify(page.framesName));

  var frameIndex = 0; // Choose after inspecting the output.
  if (!page.switchToFrame(frameIndex)) {
    console.error('No frame at position ' + frameIndex);
    phantom.exit(1);
    return;
  }

  var heading = page.evaluate(function () {
    var h1 = document.querySelector('h1');
    return h1 ? h1.textContent : null;
  });
  console.log('Heading: ' + heading);

  page.switchToMainFrame();
  phantom.exit();
});

Frame positions are not permanent identifiers. A page can add or remove child frames as scripts run, so log framesName and framesCount in the context where you will switch, check the Boolean returned by switchToFrame, and fail with a useful message instead of querying an assumed index.

Reading the iframe element in the parent document

Sometimes the target is not content inside the frame. You may need the iframe tag’s attributes, such as its source URL or a data attribute. Do that while the main frame is active:

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
var iframeInfo = page.evaluate(function () {
  var iframe = document.querySelector('iframe[data-role="payment"]');
  if (!iframe) {
    return null;
  }
  return {
    src: iframe.getAttribute('src'),
    name: iframe.getAttribute('name'),
    title: iframe.getAttribute('title')
  };
});

console.log(JSON.stringify(iframeInfo));

Do not use window.frames[0] when you need that tag. It represents the frame’s window (equivalent to the iframe’s contentWindow), not the DOM element that carries those attributes. After collecting parent attributes, switch into the child frame if you also need its document.

Returning values safely from page.evaluate()

The function passed to evaluate executes in the page context. Its arguments and return value must be JSON-serializable. DOM nodes, functions, and closures cannot cross the PhantomJS bridge as live objects.

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

Return the specific data your script needs:

var details = page.evaluate(function () {
  var button = document.querySelector('button[type="submit"]');
  if (!button) {
    return { found: false };
  }
  return {
    found: true,
    label: button.textContent,
    disabled: button.disabled,
    html: button.outerHTML
  };
});

console.log(JSON.stringify(details));

Typical serializable results include textContent, getAttribute('href'), outerHTML, numbers, booleans, strings, arrays, and plain objects containing those types. Returning button itself will not give the calling script a usable element handle; return its fields instead.

Walking nested frames

Frame discovery is relative to the active context. For a frame inside another frame, enter the outer frame first, inspect its children, then enter the inner frame. Reset to the main frame when finished:

function printFrameState(label) {
  console.log(label + ' count=' + page.framesCount +
              ' names=' + JSON.stringify(page.framesName));
}

var page = require('webpage').create();
page.open('https://example.com', function (status) {
  if (status !== 'success') {
    phantom.exit(1);
    return;
  }

  printFrameState('main');
  if (!page.switchToFrame('outer')) {
    console.error('Outer frame not found');
    phantom.exit(1);
    return;
  }

  printFrameState('outer');
  if (!page.switchToFrame('inner')) {
    console.error('Inner frame not found');
    page.switchToMainFrame();
    phantom.exit(1);
    return;
  }

  var value = page.evaluate(function () {
    var node = document.querySelector('[data-value]');
    return node ? node.getAttribute('data-value') : null;
  });
  console.log(value);

  page.switchToParentFrame();
  page.switchToMainFrame();
  phantom.exit();
});

If a nested frame has no name, use its position after printing the child list for the current parent. Calling framesName while still in the main frame tells you nothing about grandchildren below an outer frame.

Rank #3
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

Timing: wait for the frame and its content

A successful page load and a present iframe are separate conditions on script-heavy sites. The documented APIs establish how to select and inspect frames, but they do not provide one universal delay that works for every application. Use the page’s load/event behavior or a site-specific condition, then inspect the frame list and switch.

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

A polling loop can check for a named frame without assuming a fixed sleep:

var page = require('webpage').create();
var attempts = 0;
var maxAttempts = 40;

page.open('https://example.com', function (status) {
  if (status !== 'success') {
    console.error('Open failed');
    phantom.exit(1);
    return;
  }

  function findFrame() {
    attempts += 1;
    if (page.framesName.indexOf('checkout') !== -1) {
      if (!page.switchToFrame('checkout')) {
        console.error('Frame appeared but could not be selected');
        phantom.exit(1);
        return;
      }
      var result = page.evaluate(function () {
        var node = document.querySelector('.total');
        return node ? node.textContent : null;
      });
      console.log(result);
      page.switchToMainFrame();
      phantom.exit();
      return;
    }

    if (attempts >= maxAttempts) {
      console.error('Timed out waiting for checkout frame');
      phantom.exit(1);
      return;
    }
    window.setTimeout(findFrame, 250);
  }

  findFrame();
});

Once inside the frame, you may need a second site-specific check for the element itself. A frame can exist before its application has rendered the control you want.

Using frameContent

page.frameContent exposes the content string for the currently active frame, whether that is the main frame or a child. It is useful when you need a snapshot of the active document’s markup rather than a live DOM element:

if (page.switchToFrame('checkout')) {
  var markup = page.frameContent;
  console.log(markup);
  page.switchToMainFrame();
}

Treat this as a string. It does not provide a node handle, and it does not change the need to select the correct frame before reading it.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Common failures and precise fixes

“Frame not found”

Cause: the name or position is wrong, or the frame has not been created yet. Fix: print framesName and framesCount in the current context, wait for the page’s frame-creation condition, and check the Boolean returned by switchToFrame.

The selector returns null

Cause: evaluation is still running in the parent (or another child) frame, or the element has not rendered. Fix: switch first, then evaluate; if the switch succeeds, wait for the element using a condition tied to that page.

An element returned from evaluate is unusable

Cause: DOM nodes are not JSON-serializable bridge values. Fix: return text, attributes, outerHTML, or a plain object of fields.

window.frames[0] is missing the attributes you expected

Cause: that expression is a frame window, not the parent document’s iframe element. Fix: query document.querySelector('iframe') in the parent for tag attributes, then switch for child content.

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

A nested-frame lookup works intermittently

Cause: child-frame lists are relative to the active parent and can change while scripts run. Fix: enter each level in sequence, inspect names/counts again after every switch, avoid hard-coded indexes when a stable name is available, and reset with switchToParentFrame or switchToMainFrame.

The page opens but content is blank

Cause: the target application may load asynchronously or fail under the page’s runtime conditions. Fix: distinguish page-open success from application readiness, capture diagnostic frame names and markup, and use a condition based on the site’s actual rendered state rather than an arbitrary delay. The documented frame APIs describe selection behavior, not compatibility with every current website.

Or skip the browser setup

If your actual goal is a clean image or PDF of a page rather than DOM-level interaction, ScreenshotNeo provides a website screenshot API and MCP server. One request can capture a URL without maintaining PhantomJS frame code:

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

See the ScreenshotNeo documentation for request options. The service can accept consent banners before capture and remove more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be disabled. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and whether the request was billed. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients.

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

There is a free allowance of 1,000 screenshots per month with no card. Paid plans start at $5 for 3,000 screenshots; every feature is included on every plan. If that fits your use case, sign up for the free plan.

Operational checklist

  • Open the page and handle a non-success status before frame work.
  • Inspect framesName and framesCount in the context where the target is expected.
  • Switch by a stable name when possible; otherwise validate a positional index immediately.
  • Run every selector for child content inside page.evaluate() after switching.
  • Return serializable data, not DOM nodes or functions.
  • For nested frames, repeat discovery at each level.
  • Wait for the site’s actual frame and element readiness conditions.
  • Restore the main frame before another top-level query or before exiting.

Frequently Asked Questions

What does page.frameContent return?

It returns a string containing the markup for the currently active frame; it is not a live DOM element handle.

How do I select a frame with no name?

Inspect page.framesName and page.framesCount, choose the appropriate child position, and verify that page.switchToFrame(position) returns true.

Can PhantomJS return an iframe DOM node from evaluate()?

No. Return serializable fields such as text, attributes, outerHTML, or a plain object instead.

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

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.