ScreenshotNeo

BlogHow-to

How to Detect HTML5 Validation in PhantomJS

Use PhantomJS feature detection to verify checkValidity(), ValidityState, constraint rules, and interactive form validation in the exact build you run.

By the ScreenshotNeo team1 October 20267 min read

Run feature detection inside PhantomJS with page.evaluate(). Check that a form control exposes checkValidity() and validity, then test a deliberately invalid control. This tells you whether the executable can perform the specific validation checks your application needs. It does not prove that PhantomJS displays browser validation bubbles, moves focus, or implements every HTML5 constraint correctly.

PhantomJS uses QtWebKit, and its documentation recommends feature detection because WebKit implementations can differ. It also cautions that detecting a feature does not guarantee complete support. See the PhantomJS supported standards documentation.

1. Run a basic validation probe

Create detect-validation.js:

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

page.open('about:blank', function (status) {
  if (status !== 'success') {
    console.error('Could not open test page: ' + status);
    phantom.exit(1);
    return;
  }

  var result = page.evaluate(function () {
    var input = document.createElement('input');
    input.required = true;
    input.value = '';

    var hasCheckValidity = typeof input.checkValidity === 'function';
    var hasValidity = !!input.validity;
    var invalidRequiredFieldIsRejected = hasCheckValidity
      ? input.checkValidity() === false
      : false;

    return {
      hasCheckValidity: hasCheckValidity,
      hasValidity: hasValidity,
      invalidRequiredFieldIsRejected: invalidRequiredFieldIsRejected
    };
  });

  console.log(JSON.stringify(result));
  phantom.exit(result.invalidRequiredFieldIsRejected ? 0 : 2);
});

Run it with the exact PhantomJS binary used by your application:

phantomjs --version
phantomjs detect-validation.js

A passing result resembles:

{"hasCheckValidity":true,"hasValidity":true,"invalidRequiredFieldIsRejected":true}

The three fields answer different questions:

Field What it proves What it does not prove
hasCheckValidity The method is exposed on the control. That every constraint or UI behavior works.
hasValidity A ValidityState-like object is exposed. That each state flag is accurate for your rules.
invalidRequiredFieldIsRejected An empty required control is rejected programmatically. That form submission shows an error or focuses the field.

2. Make sure JavaScript is enabled before page.open()

PhantomJS page settings apply during the initial page.open() call. JavaScript is enabled by default, but set it explicitly when diagnosing a controlled environment:

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

page.open('about:blank', function (status) {
  // Run page.evaluate() only after this callback fires.
});

Changing the setting after navigation can be too late for scripts that construct or modify the form. The PhantomJS page settings documentation describes this timing.

3. Test the constraints your form actually uses

A required-field check is only a smoke test. Add one case per constraint that matters to your application. Keep the values intentionally invalid so a false result is easy to interpret.

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

page.open('about:blank', function (status) {
  var results = page.evaluate(function () {
    function check(control) {
      return {
        checkValidity: typeof control.checkValidity === 'function'
          ? control.checkValidity()
          : null,
        valueMissing: control.validity ? !!control.validity.valueMissing : null,
        typeMismatch: control.validity ? !!control.validity.typeMismatch : null,
        patternMismatch: control.validity ? !!control.validity.patternMismatch : null,
        rangeUnderflow: control.validity ? !!control.validity.rangeUnderflow : null,
        rangeOverflow: control.validity ? !!control.validity.rangeOverflow : null
      };
    }

    var required = document.createElement('input');
    required.required = true;
    required.value = '';

    var email = document.createElement('input');
    email.type = 'email';
    email.value = 'not-an-email';

    var pattern = document.createElement('input');
    pattern.pattern = '^[A-Z]{3}$';
    pattern.value = 'abc';

    var number = document.createElement('input');
    number.type = 'number';
    number.min = '10';
    number.max = '20';
    number.value = '5';

    return {
      required: check(required),
      email: check(email),
      pattern: check(pattern),
      range: check(number)
    };
  });

  console.log(JSON.stringify(results, null, 2));
  phantom.exit();
});

Use the output as a compatibility record for that binary. Do not infer support from a WebKit version number alone. PhantomJS recommends testing the support level your page requires.

4. Understand programmatic versus interactive validation

checkValidity() is a programmatic query. It returns false when a constraint is violated, while validity exposes individual states. Interactive validation is separate: it normally occurs during form submission (unless the form uses novalidate) or when code calls reportValidity(). WebKit’s explanation of interactive validation distinguishes these behaviors; it was published on December 12, 2016, by Chris Dumez. See the WebKit interactive validation article.

If your application only needs to reject bad data in JavaScript, test checkValidity() and the relevant ValidityState flags. If it depends on a visible validation bubble, focus movement, or reportValidity(), create a separate behavior test in the deployed PhantomJS build. A passing API check is not evidence that the UI exists.

5. Test a real form and submission behavior

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

page.setContent(
  '<form id="f">' +
  '<input id="name" required>' +
  '<button type="submit">Send</button>' +
  '</form>',
  'http://validation.test/'
);

var result = page.evaluate(function () {
  var form = document.getElementById('f');
  var input = document.getElementById('name');
  var before = document.activeElement === input;
  var check = form.checkValidity();
  var report = typeof form.reportValidity === 'function'
    ? form.reportValidity()
    : null;

  return {
    formCheckValidity: check,
    reportValidityResult: report,
    activeElementIsInputBeforeCall: before,
    validationMessage: input.validationMessage || ''
  };
});

console.log(JSON.stringify(result));
phantom.exit();

setContent() is useful for an isolated probe. For an application test, use page.open() and wait until the page has created its form. The PhantomJS page.evaluate() documentation notes that the function runs in the webpage context and that arguments and return values should be simple serializable values.

6. Common errors and fixes

Symptom Likely cause Fix
checkValidity is not a function The control in that build does not expose the API, or the value is not a form control. Check typeof input.checkValidity, use an actual input/select/textarea, and record the PhantomJS version.
validity is null or undefined That executable exposes incomplete constraint-validation support. Treat it as unsupported and add a fallback validator or run the check in a maintained browser.
The invalid field returns true The constraint was not applied, the value is valid, or the control is disabled/read-only in a way that excludes it. Inspect the element properties inside page.evaluate(); deliberately set required, pattern, min, or max and an invalid value.
All checks fail after navigation JavaScript was disabled before page.open(), or the test ran before page scripts finished. Set page.settings.javascriptEnabled = true before opening and run the probe in the open callback.
API check passes but no browser bubble appears Programmatic validation and interactive UI are different features. Test submission and reportValidity() separately; do not rely on a screenshot alone.
JSON output is empty or serialization fails The evaluated function returned a DOM object or another non-serializable value. Return booleans, strings, numbers, arrays, and plain objects only.
Results differ between machines Different PhantomJS executables, patches, page settings, or loaded polyfills. Log the binary version, settings, URL, and test result; run the same probe in CI and production.

7. Performance and reliability

  • Use one page.evaluate() call to run a group of checks instead of navigating for every constraint.
  • Keep probes deterministic: create controls in memory, assign explicit values, and avoid network dependencies.
  • Run the probe against the exact PhantomJS binary shipped with the application. A result from another WebKit build is not a compatibility guarantee.
  • Record the version and the individual flags, rather than storing only a single “supported” boolean.
  • Use a fallback validation library or server-side validation when a required constraint is missing or inconsistent. Client-side checks never replace server-side validation.
  • When testing a live form, wait for the page’s own scripts to finish and ensure no polyfill changes the controls after your probe.

8. A repeatable compatibility checklist

  1. Print phantomjs --version and identify the executable path.
  2. Set page settings before page.open().
  3. Verify checkValidity() and validity separately.
  4. Exercise an invalid required control.
  5. Exercise every required rule: email, pattern, range, length, and custom validity where applicable.
  6. Test submission, novalidate, and reportValidity() only if your product depends on interactive behavior.
  7. Store the JSON result with the build or CI artifact.
  8. Re-run after upgrading PhantomJS, changing page settings, or changing validation polyfills.

Or skip the browser setup

If your goal is to capture a page after checking its form state, ScreenshotNeo provides a website screenshot API and MCP server. The DIY PhantomJS probe above remains the right way to detect HTML5 validation support; ScreenshotNeo handles the capture step when you do not want to maintain a browser setup.

See the ScreenshotNeo API documentation for all options. A one-call capture looks like this:

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

ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before the shot. 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 lets AI agents take screenshots. 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.

FAQ

Does PhantomJS support HTML5 validation?

Some PhantomJS builds expose parts of constraint validation. Detect the exact methods and behaviors you need in the executable you run; do not assume every build has complete support.

Is checking checkValidity enough?

No. It verifies one programmatic path. Test the individual constraints and interactive behavior separately when those behaviors matter.

Can I identify support from the WebKit version?

No. PhantomJS recommends feature detection because implementations can differ even when their underlying version labels look similar.

Should invalid client-side data be trusted?

No. Use the browser check for user feedback, then validate and enforce the data on the server.