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.

Use Node Horseman’s .select(selector, value) to choose an AngularJS option, but do not call it as soon as the document loads. AngularJS may create <option> elements later through ngOptions. Wait for the actual option value, select the stable <select> element, then verify both the selected option and the page state that depends on the AngularJS model.

What the selection call actually does

Horseman is a chainable, promise-based Node API that drives PhantomJS. Its selection method takes a CSS selector for a <select> and the option’s value:

await horseman.select('#country', 'ca');

The second argument is not necessarily the text a person sees. For example, an option can display “Canada” while its HTML value is ca. Inspect the markup or read the selected option before deciding which string to pass.

On an AngularJS page, the select is normally connected to a scope property with ngModel. The ngOptions directive can generate options from an array or object, so the target option may not exist during the first page-load tick. Horseman can set the browser control, but the application still needs time to create the option and run its watchers.

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

Prerequisites and a minimal Horseman script

Install and expose PhantomJS

Horseman requires a working PhantomJS executable. Put PhantomJS on your system PATH, install it through a supported PhantomJS package, or provide its path through Horseman’s phantomPath option. If PhantomJS cannot start, no AngularJS selector code will run.

Basic script

const Horseman = require('node-horseman');

(async () => {
  const horseman = new Horseman({
    // phantomPath: '/absolute/path/to/phantomjs'
  });

  try {
    await horseman.open('https://example.com/form');
    await horseman.waitForSelector('select#country');
    await horseman.select('select#country', 'ca');

    const result = await horseman.evaluate(() => {
      const select = document.querySelector('select#country');
      const option = select && select.options[select.selectedIndex];
      return {
        value: select ? select.value : null,
        label: option ? option.textContent.trim() : null
      };
    });

    console.log(result);
  } finally {
    await horseman.close();
  }
})();

The finally block matters: .close() shuts down PhantomJS even when navigation, selection, or verification fails.

Reliable AngularJS option selection, step by step

1. Identify the real select and option value

Use a selector that is specific but resilient, such as select[name="country"] or an ID intended to be stable. Avoid selecting by a generated AngularJS class or a positional selector that changes when the list changes.

Then inspect the rendered HTML in a browser or through Horseman to determine the option’s value:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
const options = await horseman.evaluate(() => {
  const select = document.querySelector('select[name="country"]');
  return select ? Array.from(select.options).map(o => ({
    value: o.value,
    text: o.textContent.trim()
  })) : [];
});
console.log(options);

Because PhantomJS evaluation returns data across a process boundary, return plain JSON-serializable values. Do not return DOM nodes, closures, or functions.

2. Wait for the target option, not just the page shell

waitForSelector is useful for the select itself and has a documented default timeout of 5,000 milliseconds. It does not prove that an asynchronously generated option is present. A fixed sleep can be too short on a slow response and unnecessarily long on a fast one.

Poll for the exact value with a small helper. The loop below uses evaluate for the page-side check and a short Node delay between checks:

async function waitForOption(horseman, selectSelector, wantedValue, timeoutMs = 15000) {
  const started = Date.now();
  while (Date.now() - started < timeoutMs) {
    const present = await horseman.evaluate((selector, value) => {
      const select = document.querySelector(selector);
      return !!select && Array.from(select.options).some(o => o.value === value);
    }, selectSelector, wantedValue);

    if (present) return;
    await new Promise(resolve => setTimeout(resolve, 100));
  }
  throw new Error(`Option ${wantedValue} was not found in ${selectSelector}`);
}

This observes the condition that matters: the requested value is in the rendered option list. It is implementation guidance for this page, not a guarantee that every AngularJS directive or custom widget behaves identically.

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

3. Select by value

await horseman.select('select[name="country"]', 'ca');

Keep the value as a string unless the page’s markup clearly uses another representation. If two controls have similar names, scope the selector to the relevant form or container.

4. Verify the browser control

const selected = await horseman.evaluate((selector) => {
  const select = document.querySelector(selector);
  if (!select) return null;
  const option = select.options[select.selectedIndex];
  return {
    value: select.value,
    text: option ? option.textContent.trim() : null,
    index: select.selectedIndex
  };
}, 'select[name="country"]');

if (!selected || selected.value !== 'ca') {
  throw new Error(`Unexpected selection: ${JSON.stringify(selected)}`);
}

5. Verify the AngularJS result

A selected DOM value is only the first assertion. If the selection should load cities, enable a button, or update a summary, wait for and inspect that visible result. For example:

await horseman.waitForSelector('#city-list');
const cityCount = await horseman.evaluate(() =>
  document.querySelectorAll('#city-list option').length
);
if (cityCount === 0) throw new Error('Country selected, but no cities appeared');

This distinction is important with custom directives: the native control can show a value while application code has not completed its own update.

A complete example with an asynchronously populated ngOptions list

const Horseman = require('node-horseman');

async function waitForOption(h, selector, value, timeoutMs = 15000) {
  const start = Date.now();
  for (;;) {
    const exists = await h.evaluate((s, v) => {
      const el = document.querySelector(s);
      return !!el && Array.from(el.options).some(o => o.value === v);
    }, selector, value);
    if (exists) return;
    if (Date.now() - start >= timeoutMs) {
      throw new Error(`Timed out waiting for ${value}`);
    }
    await new Promise(r => setTimeout(r, 100));
  }
}

(async () => {
  const h = new Horseman({
    // phantomPath: process.env.PHANTOMJS_PATH
  });
  const select = 'select[name="country"]';
  try {
    await h.open('https://example.com/form');
    await h.waitForSelector(select);
    await waitForOption(h, select, 'ca');
    await h.select(select, 'ca');

    const state = await h.evaluate((s) => {
      const el = document.querySelector(s);
      const option = el && el.options[el.selectedIndex];
      return {
        value: el ? el.value : null,
        text: option ? option.textContent.trim() : null
      };
    }, select);
    if (state.value !== 'ca') throw new Error('Selection did not stick');

    await h.waitForSelector('#country-result');
    console.log(state);
  } finally {
    await h.close();
  }
})();

Replace the example URL and result selector with the application’s actual page. If the result appears only after an XHR, wait for a result-specific selector or condition rather than assuming that the select call itself waits for network activity.

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.

Common failures and fixes

“PhantomJS executable not found”

Cause: PhantomJS is not on PATH and no valid phantomPath was supplied. Install a supported package, verify the executable directly, or pass its absolute path.

“Element not found” or a timeout on the select

Cause: navigation has not finished, the selector is wrong, or the control is inside markup that has not been rendered. Confirm the URL, wait for the select’s actual selector, and inspect the page HTML from evaluate.

The select exists but the option does not

Cause: ngOptions is still processing data, the API request failed, or the value is different from the visible label. Poll for the exact option value, inspect all values, and check whether the page displays an application error.

Rank #4
AngularJS
  • Used Book in Good Condition

Selection succeeds but dependent content stays unchanged

Cause: the page uses custom event handling, a watcher has not run, or the selected value is not the model value expected by the directive. Verify the native selection, then wait for the dependent result. If necessary, use the page’s supported interaction path rather than trying to mutate AngularJS internals.

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

Evaluation returns an unusable object

Cause: PhantomJS can only transfer JSON-serializable arguments and return values from evaluate. Return strings, numbers, booleans, arrays, and plain objects; extract properties from DOM nodes inside the page context.

Intermittent failures on slow pages

Cause: a fixed delay or the default 5-second selector timeout is shorter than the page’s real data-loading time. Use a condition-based wait with an explicit upper bound, and fail with a diagnostic message when that bound is reached.

Selectors, waits, and verification: practical choices

Decision Prefer Why
Selector Stable ID, name, or semantic container Less sensitive to AngularJS-generated classes and option ordering
Readiness check Target option value exists Confirms the data needed by .select() is present
Delay Short polling interval plus timeout Adapts to variable load time instead of guessing a sleep
Assertion Selected value plus dependent visible state Distinguishes a native control change from a completed application update
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Runtime and maintenance considerations

Horseman’s browser engine is PhantomJS, so modern sites may expose compatibility problems unrelated to AngularJS. Keep the PhantomJS executable version and path explicit in deployment, and capture the page URL and selector in timeout errors so failures can be reproduced.

AngularJS support officially ended in January 2022. Existing applications can still be automated, but teams maintaining them should treat the framework and its browser assumptions as legacy dependencies. Do not infer that a successful native selection proves every custom AngularJS component will behave correctly; the application’s own visible result remains the useful acceptance check.

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

Or skip the browser setup

If your actual goal is a clean image or PDF of a page rather than interaction with its AngularJS model, ScreenshotNeo makes one HTTP request and returns a PNG, JPEG, WebP, or PDF. Its API accepts the page URL and handles the capture without installing PhantomJS:

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 parameters and response headers. 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 disabled. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers. ScreenshotNeo also provides an MCP server with take_screenshot, get_page_info, and capture_pdf tools for Claude, Cursor, and other MCP clients. The Free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Sign up free to try it.

Frequently Asked Questions

Can Horseman select an AngularJS option by its visible text?

Use the option’s HTML value with .select(). If you only know the label, first inspect the rendered options and map that label to its value.

Why does waiting for the select element not guarantee that selection will work?

AngularJS can render the select before ngOptions has inserted the requested option. Wait for that option’s value specifically.

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

What should a test assert after calling .select()?

Assert the native selected value, then assert the page-specific content or control that should change when AngularJS processes the model update.

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.