diff --git a/core/includes/common.inc b/core/includes/common.inc
index 03af03b..7bb70a2 100644
--- a/core/includes/common.inc
+++ b/core/includes/common.inc
@@ -568,7 +568,7 @@ function drupal_js_defaults($data = NULL) {
  * @param $elements
  *   A renderable array element having a #states property as described above.
  *
- * @see form_example_states_form()
+ * @see \Drupal\form_test\Form\JavascriptStatesForm
  */
 function drupal_process_states(&$elements) {
   $elements['#attached']['library'][] = 'core/drupal.states';
diff --git a/core/modules/system/tests/modules/form_test/form_test.routing.yml b/core/modules/system/tests/modules/form_test/form_test.routing.yml
index d8c5833..22b1221 100644
--- a/core/modules/system/tests/modules/form_test/form_test.routing.yml
+++ b/core/modules/system/tests/modules/form_test/form_test.routing.yml
@@ -480,3 +480,10 @@ form_test.get_form:
     _form: '\Drupal\form_test\Form\FormTestGetForm'
   requirements:
     _access: 'TRUE'
+
+form_test.javascript_states_form:
+  path: '/form-test/javascript-states-form'
+  defaults:
+    _form: '\Drupal\form_test\Form\JavascriptStatesForm'
+  requirements:
+    _access: 'TRUE'
diff --git a/core/modules/system/tests/modules/form_test/src/Form/JavascriptStatesForm.php b/core/modules/system/tests/modules/form_test/src/Form/JavascriptStatesForm.php
new file mode 100644
index 0000000..695ac3a
--- /dev/null
+++ b/core/modules/system/tests/modules/form_test/src/Form/JavascriptStatesForm.php
@@ -0,0 +1,273 @@
+<?php
+
+namespace Drupal\form_test\Form;
+
+use Drupal\Core\Form\FormBase;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Builds a simple form to test states.
+ *
+ * @see \Drupal\FunctionalJavascriptTests\Core\Form\JavascriptStatesTest
+ */
+class JavascriptStatesForm extends FormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'javascript_states_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    // Hide name field when the anonymous checkbox is checked.
+    $form['name'] = [
+      '#type' => 'textfield',
+      '#title' => $this->t('Name'),
+      '#states' => [
+        'invisible' => [
+          ':input[name="anonymous"]' => ['checked' => TRUE],
+        ],
+      ],
+    ];
+
+    // Uncheck anonymous field when the name field is filled.
+    $form['anonymous'] = [
+      '#type' => 'checkbox',
+      '#title' => $this->t('I prefer to remain anonymous'),
+      '#default_value' => '1',
+      '#states' => [
+        'unchecked' => [
+          ':input[name="name"]' => ['filled' => TRUE],
+        ],
+      ],
+    ];
+
+    $form['student_type'] = [
+      '#type' => 'radios',
+      '#options' => [
+        'high_school' => $this->t('High School'),
+        'undergraduate' => $this->t('Undergraduate'),
+        'graduate' => $this->t('Graduate'),
+      ],
+      '#title' => $this->t('What type of student are you?'),
+    ];
+    $form['high_school'] = [
+      '#type' => 'fieldset',
+      '#title' => $this->t('High School Information'),
+      // This #states rule says that the "high school" fieldset should only
+      // be shown if the "student_type" form element is set to "High School".
+      '#states' => [
+        'visible' => [
+          ':input[name="student_type"]' => ['value' => 'high_school'],
+        ],
+      ],
+    ];
+
+    // High school information.
+    $form['high_school']['tests_taken'] = [
+      '#type' => 'checkboxes',
+      '#options' => array_combine(['SAT', 'ACT'], [t('SAT'), t('ACT')]),
+      '#title' => $this->t('What standardized tests did you take?'),
+      // This #states rule says that this checkboxes array will be visible only
+      // when $form['student_type'] is set to t('High School').
+      // It uses the jQuery selector :input[name=student_type] to choose the
+      // element which triggers the behavior, and then defines the "High School"
+      // value as the one that triggers visibility.
+      '#states' => [
+        // Action to take.
+        'visible' => [
+          ':input[name="student_type"]' => ['value' => 'high_school'],
+        ],
+      ],
+    ];
+
+    $form['high_school']['sat_score'] = [
+      '#type' => 'textfield',
+      '#title' => $this->t('Your SAT score:'),
+      '#size' => 4,
+
+      // This #states rule limits visibility to when the $form['tests_taken']
+      // 'SAT' checkbox is checked."
+      '#states' => [
+        // Action to take.
+        'visible' => [
+          ':input[name="tests_taken[SAT]"]' => ['checked' => TRUE],
+        ],
+      ],
+    ];
+    $form['high_school']['act_score'] = [
+      '#type' => 'textfield',
+      '#title' => $this->t('Your ACT score:'),
+      '#size' => 4,
+
+      // Set this element visible if the ACT checkbox above is checked.
+      '#states' => [
+        // Action to take.
+        'visible' => [
+          ':input[name="tests_taken[ACT]"]' => ['checked' => TRUE],
+        ],
+      ],
+    ];
+
+    // Undergrad information.
+    $form['undergraduate'] = [
+      '#type' => 'fieldset',
+      '#title' => $this->t('Undergraduate Information'),
+      // This #states rule says that the "undergraduate" fieldset should only
+      // be shown if the "student_type" form element is set to "Undergraduate".
+      '#states' => [
+        'visible' => [
+          ':input[name="student_type"]' => ['value' => 'undergraduate'],
+        ],
+      ],
+    ];
+
+    $form['undergraduate']['how_many_years'] = [
+      '#type' => 'select',
+      '#title' => $this->t('How many years have you completed?'),
+      // The options here are integers, but since all the action here happens
+      // using the DOM on the client, we will have to use strings to work with
+      // them.
+      '#options' => [
+        1 => $this->t('One'),
+        2 => $this->t('Two'),
+        3 => $this->t('Three'),
+        4 => $this->t('Four'),
+        5 => $this->t('Lots'),
+      ],
+    ];
+
+    $form['undergraduate']['comment'] = [
+      '#type' => 'item',
+      '#description' => $this->t("Wow, that's a long time."),
+      '#states' => [
+        'visible' => [
+          // Note that '5' must be used here instead of the integer 5.
+          // The information is coming from the DOM as a string.
+          ':input[name="how_many_years"]' => ['value' => '5'],
+        ],
+      ],
+    ];
+    $form['undergraduate']['school_name'] = [
+      '#type' => 'textfield',
+      '#title' => $this->t('Your college or university:'),
+    ];
+    $form['undergraduate']['school_country'] = [
+      '#type' => 'select',
+      '#options' => array_combine(['UK', 'Other'], [t('UK'), t('Other')]),
+      '#title' => $this->t('In what country is your college or university located?'),
+    ];
+    $form['undergraduate']['country_writein'] = [
+      '#type' => 'textfield',
+      '#size' => 20,
+      '#title' => $this->t('Please enter the name of the country where your college or university is located.'),
+
+      // Only show this field if school_country is set to 'Other'.
+      '#states' => [
+        // Action to take: Make visible.
+        'visible' => [
+          ':input[name="school_country"]' => ['value' => $this->t('Other')],
+        ],
+      ],
+    ];
+
+    $form['undergraduate']['thanks'] = [
+      '#type' => 'item',
+      '#description' => $this->t('Thanks for providing both your school and your country.'),
+      '#states' => [
+        // Here visibility requires that two separate conditions be true.
+        'visible' => [
+          ':input[name="school_country"]' => ['value' => $this->t('Other')],
+          ':input[name="country_writein"]' => ['filled' => TRUE],
+        ],
+      ],
+    ];
+    $form['undergraduate']['go_away'] = [
+      '#type' => 'submit',
+      '#value' => $this->t('Done with form'),
+      '#states' => [
+        // Here visibility requires that two separate conditions be true.
+        'visible' => [
+          ':input[name="school_country"]' => ['value' => $this->t('Other')],
+          ':input[name="country_writein"]' => ['filled' => TRUE],
+        ],
+      ],
+    ];
+
+    // Graduate student information.
+    $form['graduate'] = [
+      '#type' => 'fieldset',
+      '#title' => $this->t('Graduate School Information'),
+      // This #states rule says that the "graduate" fieldset should only
+      // be shown if the "student_type" form element is set to "Graduate".
+      '#states' => [
+        'visible' => [
+          ':input[name="student_type"]' => ['value' => 'graduate'],
+        ],
+      ],
+    ];
+    $form['graduate']['more_info'] = [
+      '#type' => 'textarea',
+      '#title' => $this->t('Please describe your graduate studies'),
+    ];
+
+    $form['graduate']['info_provide'] = [
+      '#type' => 'checkbox',
+      '#title' => $this->t('Check here if you have provided information above'),
+      '#disabled' => TRUE,
+      '#states' => [
+        // Mark this checkbox checked if the "more_info" textarea has something
+        // in it, if it's 'filled'.
+        'checked' => [
+          ':input[name="more_info"]' => ['filled' => TRUE],
+        ],
+      ],
+    ];
+
+    $form['average'] = [
+      '#type' => 'textfield',
+      '#title' => $this->t('Enter your average'),
+      // To trigger a state when the same controlling element can have more than
+      // one possible value, put all values in a higher-level array.
+      '#states' => [
+        'visible' => [
+          ':input[name="student_type"]' => [
+            ['value' => 'high_school'],
+            ['value' => 'undergraduate'],
+          ],
+        ],
+      ],
+    ];
+
+    $form['expand_more_info'] = [
+      '#type' => 'checkbox',
+      '#title' => $this->t('Check here if you want to add more information.'),
+    ];
+    $form['more_info'] = [
+      '#type' => 'details',
+      '#title' => $this->t('Additional Information'),
+      // Expand the expand_more_info fieldset if the box is checked.
+      '#states' => [
+        'expanded' => [
+          ':input[name="expand_more_info"]' => ['checked' => TRUE],
+        ],
+      ],
+    ];
+    $form['more_info']['feedback'] = [
+      '#type' => 'textarea',
+      '#title' => $this->t('What do you have to say?'),
+    ];
+    return $form;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+  }
+
+}
diff --git a/core/tests/Drupal/FunctionalJavascriptTests/Core/Form/JavascriptStatesTest.php b/core/tests/Drupal/FunctionalJavascriptTests/Core/Form/JavascriptStatesTest.php
new file mode 100644
index 0000000..994d374
--- /dev/null
+++ b/core/tests/Drupal/FunctionalJavascriptTests/Core/Form/JavascriptStatesTest.php
@@ -0,0 +1,107 @@
+<?php
+
+namespace Drupal\FunctionalJavascriptTests\Core\Form;
+
+use Drupal\FunctionalJavascriptTests\JavascriptTestBase;
+
+/**
+ * Tests the state of elements based on another elements.
+ *
+ * @group javascript
+ */
+class JavascriptStatesTest extends JavascriptTestBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['form_test'];
+
+  /**
+   * Test states of elements.
+   */
+  public function testJavascriptStates() {
+    $this->drupalGet('form-test/javascript-states-form');
+    $page = $this->getSession()->getPage();
+    $web_session = $this->assertSession();
+
+    $web_session->assertFieldNotVisible('Name');
+
+    // This should make the name element visible.
+    $page->uncheckField('I prefer to remain anonymous');
+    $web_session->assertFieldVisible('Name');
+
+    // This should make the name element invisible.
+    $page->checkField('I prefer to remain anonymous');
+    $web_session->assertFieldNotVisible('Name');
+
+    // This should make the name element visible.
+    $page->uncheckField('I prefer to remain anonymous');
+    $web_session->assertFieldVisible('Name');
+
+    // Ensure all the details elements are invisible.
+    $web_session->assertFieldsetNotVisible('High School Information');
+    $web_session->assertFieldsetNotVisible('Undergraduate Information');
+    $web_session->assertFieldsetNotVisible('Graduate School Information');
+    $web_session->assertFieldNotVisible('Enter your average');
+
+    // High school form testing.
+    $page->selectFieldOption('edit-student-type-high-school', 'high_school');
+    $web_session->assertFieldsetVisible('High School Information');
+    $web_session->assertFieldsetNotVisible('Undergraduate Information');
+    $web_session->assertFieldsetNotVisible('Graduate School Information');
+    $web_session->assertFieldVisible('Enter your average');
+
+    $web_session->assertFieldNotVisible('Your SAT score:');
+    $web_session->assertFieldNotVisible('Your ACT score:');
+    $page->checkField('SAT');
+    $web_session->assertFieldVisible('Your SAT score:');
+    $web_session->assertFieldNotVisible('Your ACT score:');
+    $page->checkField('ACT');
+    $web_session->assertFieldVisible('Your ACT score:');
+    $web_session->assertFieldVisible('Your SAT score:');
+    $page->uncheckField('SAT');
+    $page->uncheckField('ACT');
+    $web_session->assertFieldNotVisible('Your SAT score:');
+    $web_session->assertFieldNotVisible('Your ACT score:');
+
+    // Undergraduate form testing.
+    $page->selectFieldOption('edit-student-type-undergraduate', 'undergraduate');
+    $web_session->assertFieldsetNotVisible('High School Information');
+    $web_session->assertFieldsetVisible('Undergraduate Information');
+    $web_session->assertFieldsetNotVisible('Graduate School Information');
+    $web_session->assertFieldVisible('Enter your average');
+
+    $comment = $page->findById('edit-comment--description');
+    $this->assertFalse($comment->isVisible(), 'Comment item is not visible');
+    $page->selectFieldOption('How many years have you completed?', '5');
+    $this->assertTrue($comment->isVisible(), 'Comment item is visible');
+    $web_session->assertFieldNotVisible('Please enter the name of the country where your college or university is located.');
+    $page->selectFieldOption('In what country is your college or university located?', 'Other');
+    $web_session->assertFieldVisible('Please enter the name of the country where your college or university is located.');
+    $page->selectFieldOption('In what country is your college or university located?', 'UK');
+    $web_session->assertFieldNotVisible('Please enter the name of the country where your college or university is located.');
+
+    // Graduate form testing.
+    $page->selectFieldOption('edit-student-type-graduate', 'graduate');
+    $web_session->assertFieldsetNotVisible('High School Information');
+    $web_session->assertFieldsetNotVisible('Undergraduate Information');
+    $web_session->assertFieldsetVisible('Graduate School Information');
+    $web_session->assertFieldNotVisible('Enter your average');
+
+    $web_session->checkboxNotChecked('Check here if you have provided information above');
+    $page->fillField('Please describe your graduate studies', 'Some text');
+    $web_session->checkboxChecked('Check here if you have provided information above');
+    $page->fillField('Please describe your graduate studies', '');
+    // Empty and filled states are triggered on keyup event.
+    $page->findField('Please describe your graduate studies')->keyUp(PHP_EOL);
+    $web_session->checkboxNotChecked('Check here if you have provided information above');
+
+    // Feedback testing.
+    $web_session->assertFieldNotVisible('What do you have to say?');
+    $page->checkField('Check here if you want to add more information.');
+    $web_session->assertFieldVisible('What do you have to say?');
+    $page->uncheckField('Check here if you want to add more information.');
+    $web_session->assertFieldNotVisible('What do you have to say?');
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/WebAssert.php b/core/tests/Drupal/Tests/WebAssert.php
index d863de2..1cf82fb 100644
--- a/core/tests/Drupal/Tests/WebAssert.php
+++ b/core/tests/Drupal/Tests/WebAssert.php
@@ -2,6 +2,9 @@
 
 namespace Drupal\Tests;
 
+use Behat\Mink\Element\Element;
+use Behat\Mink\Element\ElementInterface;
+use Behat\Mink\Exception\ElementHtmlException;
 use Behat\Mink\WebAssert as MinkWebAssert;
 use Behat\Mink\Element\TraversableElement;
 use Behat\Mink\Exception\ElementNotFoundException;
@@ -52,16 +55,152 @@ public function buttonExists($button, TraversableElement $container = NULL) {
    */
   public function selectExists($select, TraversableElement $container = NULL) {
     $container = $container ?: $this->session->getPage();
-    $node = $container->find('named', array(
-      'select',
-      $this->session->getSelectorsHandler()->xpathLiteral($select),
-    ));
-
+    $node = $container->find('named', ['select', $select]);
     if ($node === NULL) {
       throw new ElementNotFoundException($this->session, 'select', 'id|name|label|value', $select);
     }
-
     return $node;
   }
 
+  /**
+   * Asserts that the given field is visible.
+   *
+   * @param string $locator
+   *   One of id|name|label|value for the field.
+   * @param \Behat\Mink\Element\TraversableElement $container
+   *   (optional) The document to check against. Defaults to the current page.
+   */
+  public function assertFieldVisible($locator, TraversableElement $container = NULL) {
+    $this->assertElementVisible('named', ['field', $locator], $container);
+  }
+
+  /**
+   * Asserts that the given field is not visible.
+   *
+   * @param string $locator
+   *   One of id|name|label|value for the field.
+   * @param \Behat\Mink\Element\TraversableElement $container
+   *   (optional) The document to check against. Defaults to the current page.
+   *
+   * @throws \Behat\Mink\Exception\ElementNotFoundException
+   *   When the element doesn't exist.
+   */
+  public function assertFieldNotVisible($locator, TraversableElement $container = NULL) {
+    $this->assertElementNotVisible('named', ['field', $locator], $container);
+  }
+
+  /**
+   * Asserts that the given fieldset is visible.
+   *
+   * @param string $locator
+   *   One of id|name|label|value for the field.
+   * @param \Behat\Mink\Element\TraversableElement $container
+   *   (optional) The document to check against. Defaults to the current page.
+   */
+  public function assertFieldsetVisible($locator, TraversableElement $container = NULL) {
+    $this->assertElementVisible('named', ['fieldset', $locator], $container);
+  }
+
+  /**
+   * Asserts that the given fieldset is not visible.
+   *
+   * @param string $locator
+   *   One of id|name|label|value for the field.
+   * @param \Behat\Mink\Element\TraversableElement $container
+   *   (optional) The document to check against. Defaults to the current page.
+   *
+   * @throws \Behat\Mink\Exception\ElementNotFoundException
+   *   When the element doesn't exist.
+   */
+  public function assertFieldsetNotVisible($locator, TraversableElement $container = NULL) {
+    $this->assertElementNotVisible('named', ['fieldset', $locator], $container);
+  }
+  /**
+   * Asserts that the specific element is visible on the current page.
+   *
+   * @param string $selector_type
+   *   The element selector type (css, xpath).
+   * @param string|array $selector
+   *   The element selector.
+   * @param ElementInterface $container
+   *   The document to check against.
+   *
+   * @throws \Behat\Mink\Exception\ElementNotFoundException
+   *   When the element doesn't exist.
+   */
+  public function assertElementVisible($selector_type, $selector, ElementInterface $container = NULL) {
+    $container = $container ?: $this->session->getPage();
+    $node = $container->find($selector_type, $selector);
+    if ($node === NULL) {
+      if (is_array($selector)) {
+        $selector = implode(' ', $selector);
+      }
+      throw new ElementNotFoundException($this->session->getDriver(), 'element', $selector_type, $selector);
+    }
+    $message = sprintf(
+      'Element "%s" is not visible.',
+      $this->getMatchingElementRepresentation($selector_type, $selector)
+    );
+    $this->assertElement($node->isVisible(), $message, $node);
+  }
+
+  /**
+   * Asserts that the specific element is not visible on the current page.
+   *
+   * @param string $selector_type
+   *   The element selector type (css, xpath).
+   * @param string|array $selector
+   *   The element selector.
+   * @param ElementInterface $container
+   *   The document to check against.
+   *
+   * @throws \Behat\Mink\Exception\ElementNotFoundException
+   *   When the element doesn't exist.
+   */
+  public function assertElementNotVisible($selector_type, $selector, ElementInterface $container = NULL) {
+    $container = $container ?: $this->session->getPage();
+    $node = $container->find($selector_type, $selector);
+    if ($node === NULL) {
+      if (is_array($selector)) {
+        $selector = implode(' ', $selector);
+      }
+      throw new ElementNotFoundException($this->session->getDriver(), 'element', $selector_type, $selector);
+    }
+    $message = sprintf(
+      'Element "%s" is visible.',
+      $this->getMatchingElementRepresentation($selector_type, $selector)
+    );
+    $this->assertElement(!$node->isVisible(), $message, $node);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function assertElement($condition, $message, Element $element) {
+    if ($condition) {
+      return;
+    }
+
+    throw new ElementHtmlException($message, $this->session->getDriver(), $element);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function getMatchingElementRepresentation($selector_type, $selector, $plural = FALSE) {
+    $pluralization = $plural ? 's' : '';
+
+    if (in_array($selector_type, ['named', 'named_exact', 'named_partial'])
+      && is_array($selector) && 2 === count($selector)
+    ) {
+      return sprintf('%s%s matching locator "%s"', $selector[0], $pluralization, $selector[1]);
+    }
+
+    if (is_array($selector)) {
+      $selector = implode(' ', $selector);
+    }
+
+    return sprintf('element%s matching %s "%s"', $pluralization, $selector_type, $selector);
+  }
+
 }
