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

To access an element inside an iframe with PhantomJS, switch the WebPage object into that frame, then run page.evaluate() to query the frame’s document. Return text, an attribute, HTML, or another JSON-serializable value—not the DOM node itself. Call page.switchToMainFrame() when you need to work on the top-level document again.

The basic pattern

PhantomJS evaluates JavaScript in the currently active browsing context. A selector such as document.querySelector('.total') therefore searches the main page until you switch into a child frame. The smallest useful script is:

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();
});

Replace checkout and .total with values from the page you are automating. switchToFrame() accepts either a frame name or a numeric position. It returns a Boolean, so check the result before querying.

Find the right frame

Inspect names and counts

If you do not know the frame name or its position, inspect the active context first:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
console.log('child frame count: ' + page.framesCount);
console.log('child frame names: ' + JSON.stringify(page.framesName));

framesCount and framesName describe the child frames of the context that is currently active. After entering a frame, inspect those properties again; they now describe that frame’s children rather than the original page’s children.

Select by name

A named frame is generally easier to read and less fragile than a position:

var ok = page.switchToFrame('paymentFrame');
if (!ok) {
  throw new Error('paymentFrame was not found');
}

var value = page.evaluate(function () {
  var input = document.querySelector('#card-number');
  return input ? input.getAttribute('value') : null;
});

The name is the frame’s browsing-context name, not a CSS selector. If the page has several frames, confirm the value in page.framesName before switching.

Select by position

When a frame is unnamed, use its zero-based position among the current context’s child frames:

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
var index = 0;
if (index >= page.framesCount) {
  throw new Error('No frame at position ' + index);
}

if (!page.switchToFrame(index)) {
  throw new Error('Could not enter frame ' + index);
}

Positions can change when a site adds, removes, or reorders frames. Enumerate the frames after the page has reached the state you intend to automate, and fail clearly if the expected index is unavailable.

Query and return data from the child document

Return primitives or plain objects

The function passed to page.evaluate() runs inside the selected frame. Its arguments and return value cross a JSON bridge, so return strings, numbers, booleans, null, arrays, or plain objects:

var details = page.evaluate(function () {
  var link = document.querySelector('a.receipt');
  var heading = document.querySelector('h1');

  return {
    title: heading ? heading.textContent.trim() : null,
    href: link ? link.getAttribute('href') : null,
    html: link ? link.outerHTML : null
  };
});

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

Do not return link, heading, or another DOM element directly. DOM nodes, functions, and closures are not serializable results for this API. Extract the fields you need while still inside evaluate().

Read the frame’s HTML

page.frameContent exposes the content string for the currently active frame. It is useful when you need the current markup as text, but it is not a live element handle:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
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
page.switchToFrame('checkout');
var markup = page.frameContent;
console.log(markup);
page.switchToMainFrame();

Use evaluate() for targeted selectors and computed values; use frameContent when the complete serialized content is the result you need.

Distinguish the iframe element from its document

There are two different objects developers often call “the iframe.” The parent document contains an actual <iframe> element. The child browsing context has its own document, where the embedded page’s controls and text live.

Inspect the iframe element in the parent

Stay in the parent context and query the element itself:

page.switchToMainFrame();
var frameAttributes = page.evaluate(function () {
  var frame = document.querySelector('iframe[data-role="checkout"]');
  return frame ? {
    id: frame.id,
    name: frame.name,
    src: frame.getAttribute('src'),
    title: frame.getAttribute('title')
  } : null;
});

This gives you attributes such as src and name. It does not query the embedded page’s internal DOM.

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

Inspect content inside the iframe

Switch first, then query:

if (!page.switchToFrame('checkout')) {
  throw new Error('checkout frame is unavailable');
}

var label = page.evaluate(function () {
  var node = document.querySelector('.shipping-address');
  return node ? node.textContent.trim() : null;
});

In browser terms, window.frames[0] is a child frame’s Window, equivalent to the iframe element’s contentWindow; it is not the iframe DOM element. Use a DOM query in the parent for the element, or PhantomJS’s frame-switching methods for the child document.

Handle nested iframes

Frame selection is relative to the current context. For a frame inside another frame, enter each level in order:

if (!page.switchToFrame('outer')) {
  throw new Error('Outer frame not found');
}

console.log('nested names: ' + JSON.stringify(page.framesName));

if (!page.switchToFrame('inner')) {
  throw new Error('Inner frame not found');
}

var result = page.evaluate(function () {
  var node = document.querySelector('.nested-value');
  return node ? node.textContent.trim() : null;
});

console.log(result);
page.switchToMainFrame();

You can instead call page.switchToParentFrame() once to move up one level, or reset all the way to the top with page.switchToMainFrame(). If the nested frame is unnamed, inspect framesCount and framesName after entering its parent and then choose the appropriate position.

Timing and dynamic pages

A frame can exist before its content is ready. The documented APIs explain how to switch and evaluate, but no fixed delay is reliable for every site. Open the page, wait for the site’s relevant load or page event, and only then enumerate frames and query their content. For script-generated frames, repeat the inspection after the page has created them.

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.

When a selector may appear later, use a site-specific condition rather than assuming that a short sleep is sufficient. Your condition should verify the frame is available and that the target element exists in the active frame. If the page replaces a frame during navigation, switch again and re-check the frame list instead of reusing an old positional assumption.

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

Common errors and fixes

Symptom Likely cause Fix
switchToFrame() returns false The name or position is wrong, or the frame has not been created yet. Inspect framesName and framesCount in the current context, wait for the page state that creates the frame, then try again.
The selector returns null The evaluation is still running in the parent or in the wrong nested frame. Switch into the intended frame first and verify the selector there.
A DOM object is empty or unusable after evaluate() A DOM node was returned across the JSON bridge. Return textContent, an attribute, outerHTML, or a plain object of fields.
The iframe’s URL or attributes are needed The script switched into the child and lost the parent element context. Return to the main or parent frame and query the <iframe> element itself.
The script works once, then targets the wrong frame Frame order changed after navigation or client-side rendering. Prefer a stable name; otherwise enumerate the current frame list immediately before switching.
Nested content cannot be found Only the outer frame was selected. Inspect child frames from the active outer frame, enter the inner frame, and then evaluate.
Content is blank or incomplete The frame’s document is still loading or is replaced by page script. Wait for a relevant load/event condition and query again; do not treat a universal fixed delay as guaranteed.

A repeatable debugging checklist

  1. Confirm page.open() completed successfully.
  2. Print framesCount and framesName in the current context.
  3. Switch by name where possible and check the Boolean return value.
  4. For unnamed frames, verify the zero-based position immediately before switching.
  5. Run a diagnostic evaluate() that returns document.title or a small text value.
  6. Only then run the production selector and return serializable data.
  7. Reset with switchToMainFrame() before operating on top-level elements.

What PhantomJS can and cannot guarantee

The frame APIs define how to inspect and switch browsing contexts, but they do not guarantee a particular site’s frame names, structure, loading sequence, or compatibility with every current website. Treat frame structure as page-specific and re-check it after navigation or client-side changes. The documentation reviewed here does not establish PhantomJS’s current maintenance or security-support status, so verify project status and runtime suitability before selecting it for a new production system.

Or skip the browser setup

If your actual goal is a clean image or PDF of a page rather than interactive DOM automation, ScreenshotNeo provides a website screenshot API and MCP server. A single request can capture a URL without managing PhantomJS frames:

cURL

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

Python

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)

Node.js

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

See the ScreenshotNeo documentation for request options. Before capture, it accepts cookie or consent banners and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each cleanup step can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and each response reports its page verdict and billing status in X-Page-Verdict and X-Billed headers. It also offers an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients.

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

The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account to try it.

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.