October DealsAmazon USOctober deal check: compare before you payAmazon US: current deals, useful picks and tech finds.Check DealsWindows FixRecommendedWindows errors stealing your time? Find the fix fastScan stability, cleanup and performance issues.Fix NowOctober DealsAmazon USDeal season is back - check today's better picksAmazon US: current deals, useful picks and tech finds.See Picks×
Skip to content
CI

How to Capture Screenshots in Karma Tests Running PhantomJS 2

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

To capture a page during a Karma test in PhantomJS 2, launch a custom PhantomJS browser with an options.onCallback handler, then call window.top.callPhantom({type: 'render', fname: 'path/file.png'}) from the test page. The handler runs in PhantomJS context and invokes page.render(); a bare callPhantom('render') does nothing unless that bridge is configured.

What you need before adding screenshots

This recipe assumes a Karma project that already runs tests with the PhantomJS browser. Install the Karma launcher plugin and make an artifact directory before the test command runs:

npm install --save-dev karma karma-phantomjs-launcher
mkdir -p .tmp/screenshots

The directory can be any workspace-relative path. In continuous integration, create it in the job itself and publish it as a build artifact after Karma exits. Use a path that the PhantomJS process can write to; a browser sandbox or a read-only working directory will otherwise produce a render failure.

Configure a custom PhantomJS launcher

In karma.conf.js, define a launcher based on PhantomJS. The important part is options.onCallback: it receives the object sent by the test and calls page.render with the requested filename.

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.
module.exports = function (config) {
  config.set({
    frameworks: ['jasmine'],
    files: [
      'test/**/*.spec.js'
    ],
    reporters: ['progress'],

    customLaunchers: {
      PhantomJSCustom: {
        base: 'PhantomJS',
        options: {
          onCallback: function (data) {
            if (data && data.type === 'render' && data.fname !== undefined) {
              page.render(data.fname);
            }
          }
        }
      }
    },

    browsers: ['PhantomJSCustom'],
    singleRun: true
  });
};

Keep the data guard. A callback can receive values from other page code, and attempting to render when type or fname is absent can overwrite an unintended file or throw an exception. If you want a fixed destination, replace data.fname with a known path; passing the filename from the test is more useful when several specs capture different states.

Add a screenshot helper to the test bundle

Karma test code runs inside the page. page.render is available only to the PhantomJS script, so the test must send a callback request through window.top.callPhantom. This helper safely does nothing when the same test bundle is opened in a normal browser.

var renderId = 0;

function takeScreenshot(file) {
  if (window.top.callPhantom === undefined) {
    return;
  }

  var options = {
    type: 'render',
    fname: file || '.tmp/screenshots/' + (renderId++) + '.png'
  };

  window.top.callPhantom(options);
}

Call it after the UI has reached the state you want to inspect. For example:

describe('checkout form', function () {
  it('shows the validation state', function () {
    document.querySelector('#email').value = '';
    document.querySelector('#submit').click();

    takeScreenshot('.tmp/screenshots/checkout-validation.png');

    expect(document.querySelector('.error').textContent)
      .toContain('Email');
  });
});

The filename is relative to the process working directory unless you provide an absolute path accepted by your CI environment. Keep names deterministic and include the suite or test identifier when tests can run in parallel; otherwise two workers can write the same file.

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

Why a callback object is required

window.top.callPhantom is a bridge from the page context to the PhantomJS script context. The launcher receives the object as data. Only the launcher knows the PhantomJS page object, so only its onCallback function can call page.render().

Consequently, window.top.callPhantom('render') is not a complete implementation. It sends a string, while the launcher above expects an object containing type: 'render' and a destination filename. If you prefer to generate names in the launcher, accept a render request without fname and substitute a counter there; passing the path from the helper keeps artifact naming close to the test that created it.

Control the captured area and file format

PhantomJS’s capture API renders the current page after it has loaded. Set the viewport before the page is opened when you need a reproducible layout:

page.viewportSize = {
  width: 1280,
  height: 900
};

When the whole viewport is not useful, assign a clipping rectangle before rendering:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
page.clipRect = {
  top: 0,
  left: 0,
  width: 640,
  height: 480
};
page.render('.tmp/screenshots/region.png');

page.render supports PNG, JPEG, GIF and PDF output. Match the extension and your artifact handling to the format you select. PNG is generally the simplest choice for test diffs; PDF is useful when the artifact is intended for review rather than pixel comparison. The custom launcher can pass any filename supported by PhantomJS:

takeScreenshot('.tmp/screenshots/state.jpg');
takeScreenshot('.tmp/screenshots/state.pdf');

If your suite needs several viewport sizes, set page.viewportSize in the PhantomJS setup for each run rather than changing it from page JavaScript. A viewport change after layout-sensitive assertions can invalidate the state you meant to capture.

Make screenshots dependable in CI

Create and collect the directory

Make .tmp/screenshots/ before Karma starts, and configure the CI system to archive that directory even when a test fails. A failing assertion should not discard the screenshot that explains the failure.

Avoid filename collisions

The counter in the helper is process-local. It prevents collisions within one browser process, but parallel Karma workers can still produce the same names. Prefix files with a suite name, shard number or test identifier when parallel execution is enabled.

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

Keep rendering after the state change

Invoke the helper after the event, route change or DOM update you want to inspect. If the application updates asynchronously, wait for the same condition your assertion uses before sending the callback. The callback request is not a synchronization primitive for pending application work.

Use workspace-relative paths

A relative path such as .tmp/screenshots/ makes artifact collection predictable. Absolute paths can work, but they must exist and be writable inside the CI agent; paths from a developer workstation will not exist in a clean build container.

Troubleshooting common failures

Nothing is written

Check that Karma is actually using PhantomJSCustom, not the plain PhantomJS launcher, and that the callback is nested under customLaunchers.PhantomJSCustom.options. Then verify that the test calls window.top.callPhantom with an object and that data.type is exactly 'render'.

callPhantom is undefined

This is expected when the bundle runs outside PhantomJS. The helper’s guard avoids an exception in Chrome or a local browser. In Karma, confirm that the PhantomJS launcher started successfully and that the helper is called from the page context, not from the Node.js Karma configuration.

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

The callback runs but the file is missing

Most often the destination directory was never created or is not writable. Create it before Karma starts, use a workspace-relative path, and inspect the CI process’s current working directory. Also check for a filename collision that caused a later render to replace an earlier artifact.

The screenshot captures the wrong area

Set page.viewportSize for the intended browser dimensions and use page.clipRect when only a region is needed. A clipping rectangle is measured in page coordinates; values outside the rendered page can produce an empty or truncated image.

The image is blank or stale

Render only after the page has reached the state under test. If a test triggers asynchronous rendering, wait for a selector, class or other observable condition before calling the helper. A callback request cannot make unfinished application work complete.

PDF or JPEG handling fails downstream

PhantomJS can produce PNG, JPEG, GIF and PDF, but your artifact viewer or comparison step may accept only some of them. Confirm the extension and configure the CI collector for that file type; use PNG when the pipeline assumes raster images.

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

Understand the maintenance trade-off

PhantomJS is headless command-line software commonly driven through a test runner; it is not itself a test framework. The PhantomJS project homepage states, “Important: PhantomJS development is suspended until further notice.” That makes this custom-launcher technique appropriate for maintaining an existing Karma/PhantomJS 2 suite, but a new system should evaluate a maintained browser runner against the same needs: screenshot API support, Karma integration, headless CI reliability, viewport and clipping controls, artifact paths, browser-engine currency and project maintenance.

Or skip the browser setup

If the goal is simply to capture a URL rather than render inside a Karma process, ScreenshotNeo provides a single HTTP request. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups and chat widgets; bot checks, blank pages, timeouts, failed loads and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP server exposes take_screenshot, get_page_info and capture_pdf to Claude, Cursor and other MCP clients.

See the parameter reference in the ScreenshotNeo documentation. The following calls are runnable as written after replacing YOUR_API_KEY:

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,
)
r.raise_for_status()
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}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));

ScreenshotNeo includes full-page capture with lazy images loaded, CSS-selector element capture, dark mode, device presets or custom viewports, retina scale, PDF paper and page-range controls, custom CSS and JavaScript, pre-capture clicks, selector or network-idle waits, request and resource blocking, headers, cookies, user agents, authorization, timezone and geolocation, transparent backgrounds, resizing, configurable-TTL caching, signed image links, asynchronous jobs with signed webhooks, bulk capture for up to 100 URLs per call, a usage API and an OpenAPI specification. Existing integrations can use the parameter names used by other screenshot APIs.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Plan Included screenshots Price
Free 1,000 per month $0; no card
Starter 3,000 $5
Growth 15,000 $15
Pro 60,000 $39
Scale 250,000 $99
Business 1,000,000 $249

Yearly billing provides two months free, and every feature is available on every plan. Create a free ScreenshotNeo account to get 1,000 screenshots each month without adding a card.

Frequently Asked Questions

Is PhantomJS itself a test framework?

No. PhantomJS is headless command-line software used to launch tests through a suitable runner such as Karma; the assertions and test lifecycle remain Karma’s responsibility.

Can ScreenshotNeo run my Karma suite?

No. ScreenshotNeo captures a supplied URL or PDF through its API or MCP tools. Use the custom launcher above when the screenshot must be produced inside a Karma test; use ScreenshotNeo when an external URL capture is sufficient.

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.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

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

Leave a Reply

Your email address will not be published. Required fields are marked *

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

Read next

Recommended PC Tool
Recommended PC Tool
Windows Errors? Fix Them Before They SpreadFree repair scan
Outdated Drivers Are Slowing You DownFree scan - exact matches

Two free Windows tools

One Free Minute Could Fix That PC

Before you go - each of these free tools takes about a minute and tackles what quietly slows a Windows PC down.

Special offer. View Outbyte info, uninstall instructions, EULA, and Privacy Policy.