diff --git a/core/includes/form.inc b/core/includes/form.inc
index 54feee0..b31e863 100644
--- a/core/includes/form.inc
+++ b/core/includes/form.inc
@@ -11,6 +11,7 @@
 use Drupal\Component\Utility\UrlHelper;
 use Drupal\Component\Utility\Xss;
 use Drupal\Core\Database\Database;
+use Drupal\Core\Form\FormElementHelper;
 use Drupal\Core\Language\Language;
 use Drupal\Core\Render\Element;
 use Drupal\Core\Template\Attribute;
@@ -1151,6 +1152,7 @@ function form_process_password_confirm($element) {
     '#value' => empty($element['#value']) ? NULL : $element['#value']['pass1'],
     '#required' => $element['#required'],
     '#attributes' => array('class' => array('password-field')),
+    '#error_use_parent' => TRUE,
   );
   $element['pass2'] =  array(
     '#type' => 'password',
@@ -1158,6 +1160,7 @@ function form_process_password_confirm($element) {
     '#value' => empty($element['#value']) ? NULL : $element['#value']['pass2'],
     '#required' => $element['#required'],
     '#attributes' => array('class' => array('password-confirm')),
+    '#error_use_parent' => TRUE,
   );
   $element['#element_validate'] = array('password_confirm_validate');
   $element['#tree'] = TRUE;
@@ -1261,6 +1264,8 @@ function form_process_radios($element) {
         '#parents' => $element['#parents'],
         '#id' => drupal_html_id('edit-' . implode('-', $parents_for_id)),
         '#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL,
+        // Errors should only be shown on the parent radios element.
+        '#error_use_parent' => TRUE,
         '#weight' => $weight,
       );
     }
@@ -1335,6 +1340,7 @@ function form_pre_render_conditional_form_element($element) {
 
   if (isset($element['#title']) || isset($element['#description'])) {
     // @see #type 'fieldgroup'
+    $element['#theme_wrappers'][] = 'form_element';
     $element['#theme_wrappers'][] = 'fieldset';
     $element['#attributes']['class'][] = 'fieldgroup';
     $element['#attributes']['class'][] = 'form-composite';
@@ -1414,6 +1420,8 @@ function form_process_checkboxes($element) {
         '#default_value' => isset($value[$key]) ? $key : NULL,
         '#attributes' => $element['#attributes'],
         '#ajax' => isset($element['#ajax']) ? $element['#ajax'] : NULL,
+        // Errors should only be shown on the parent checkboxes element.
+        '#error_use_parent' => TRUE,
         '#weight' => $weight,
       );
     }
@@ -2689,6 +2697,25 @@ function template_preprocess_form(&$variables) {
   }
   $variables['attributes'] = $element['#attributes'];
   $variables['children'] = $element['#children'];
+
+  if (!empty($element['#errors'])) {
+    $error_links = array();
+    // Loop through all form errors, and display a link for each error that
+    // is associated with a visible form element.
+    foreach ($element['#errors'] as $key => $error) {
+      if (($form_element = FormElementHelper::getElementByName($key, $element)) && Element::isVisibleElement($form_element)) {
+        $title = FormElementHelper::getElementTitle($form_element);
+        $error_links[] = l($title, '', array('fragment' => 'edit-' . str_replace('_', '-', $key), 'external' => TRUE));
+      }
+      else {
+        drupal_set_message($error, 'error');
+      }
+    }
+
+    if (!empty($error_links)) {
+      drupal_set_message(format_plural(count($error_links), '1 error has been found', '@count errors have been found') . ': ' . implode(', ', $error_links), 'error');
+    }
+  }
 }
 
 /**
@@ -2882,6 +2909,14 @@ function template_preprocess_form_element(&$variables) {
     $variables['attributes']['class'][] = 'form-disabled';
   }
 
+  // Display any error messages.
+  $variables['errors'] = NULL;
+  if (!empty($element['#errors']) && empty($element['#error_use_parent'])) {
+    // Add a class if an error exists.
+    $variables['attributes']['class'][] = 'form-error';
+    $variables['errors'] = $element['#errors'];
+  }
+
   // If #title is not set, we don't display any label or required marker.
   if (!isset($element['#title'])) {
     $element['#title_display'] = 'none';
diff --git a/core/lib/Drupal/Core/Form/FormBuilder.php b/core/lib/Drupal/Core/Form/FormBuilder.php
index aa8369a..a2873ed 100644
--- a/core/lib/Drupal/Core/Form/FormBuilder.php
+++ b/core/lib/Drupal/Core/Form/FormBuilder.php
@@ -20,6 +20,7 @@
 use Drupal\Core\Routing\UrlGeneratorInterface;
 use Drupal\Core\StringTranslation\TranslationInterface;
 use Drupal\Core\Url;
+use Drupal\Core\Utility\Error;
 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
 use Symfony\Component\HttpFoundation\RedirectResponse;
 use Symfony\Component\HttpFoundation\Request;
@@ -875,6 +876,9 @@ public function validateForm($form_id, &$form, &$form_state) {
       }
       $form_state['values'] = $values;
     }
+    if (!$form_state['programmed']) {
+      $form['#errors'] = $this->getErrors($form_state);
+    }
   }
 
   /**
@@ -1212,9 +1216,6 @@ public function setErrorByName($name, array &$form_state, $message = '') {
       if ($record) {
         $form_state['errors'][$name] = $message;
         $this->request->attributes->set('_form_errors', TRUE);
-        if ($message) {
-          $this->drupalSetMessage($message, 'error');
-        }
       }
     }
 
@@ -1742,15 +1743,6 @@ protected function watchdog($type, $message, array $variables = NULL, $severity
   }
 
   /**
-   * Wraps drupal_set_message().
-   *
-   * @return array|null
-   */
-  protected function drupalSetMessage($message = NULL, $type = 'status', $repeat = FALSE) {
-    return drupal_set_message($message, $type, $repeat);
-  }
-
-  /**
    * Wraps drupal_html_class().
    *
    * @return string
diff --git a/core/lib/Drupal/Core/Form/FormElementHelper.php b/core/lib/Drupal/Core/Form/FormElementHelper.php
new file mode 100644
index 0000000..ec5616e
--- /dev/null
+++ b/core/lib/Drupal/Core/Form/FormElementHelper.php
@@ -0,0 +1,68 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Core\Form\FormElementHelper.
+ */
+
+namespace Drupal\Core\Form;
+
+use Drupal\Core\Render\Element;
+
+/**
+ * Provides common functionality for form elements.
+ */
+class FormElementHelper {
+
+  /**
+   * Retrieves a form element.
+   *
+   * @param string $name
+   *   The name of the form element. If the #parents property of your form
+   *   element is array('foo', 'bar', 'baz') then the name is 'foo][bar][baz'.
+   * @param array $form
+   *   An associative array containing the structure of the form.
+   *
+   * @return array
+   *   The form element.
+   */
+  public static function getElementByName($name, array $form) {
+    foreach (Element::children($form) as $key) {
+      if ($key === $name) {
+        return $form[$key];
+      }
+      elseif ($element = static::getElementByName($name, $form[$key])) {
+        return $element;
+      }
+    }
+    return array();
+  }
+
+  /**
+   * Returns the title for the element.
+   *
+   * If the element has no title, this will recurse through all children of the
+   * element until a title is found.
+   *
+   * @param array $element
+   *   An associative array containing the properties of the form element.
+   *
+   * @return string
+   *   The title of the element, or an empty string if none is found.
+   */
+  public static function getElementTitle(array $element) {
+    $title = '';
+    if (isset($element['#title'])) {
+      $title = $element['#title'];
+    }
+    else {
+      foreach (Element::children($element) as $key) {
+        if ($title = static::getElementTitle($element[$key])) {
+          break;
+        }
+      }
+    }
+    return $title;
+  }
+
+}
diff --git a/core/lib/Drupal/Core/Render/Element.php b/core/lib/Drupal/Core/Render/Element.php
index 501f2de..b27c1d6 100644
--- a/core/lib/Drupal/Core/Render/Element.php
+++ b/core/lib/Drupal/Core/Render/Element.php
@@ -127,7 +127,7 @@ public static function getVisibleChildren(array $elements) {
       }
 
       // Skip value and hidden elements, since they are not rendered.
-      if (isset($child['#type']) && in_array($child['#type'], array('value', 'hidden'))) {
+      if (static::isVisibleElement($child)) {
         continue;
       }
 
@@ -138,6 +138,19 @@ public static function getVisibleChildren(array $elements) {
   }
 
   /**
+   * Determines if an element is visible.
+   *
+   * @param array $element
+   *   The element to check for visibility.
+   *
+   * @return bool
+   *   TRUE if the element is visible, otherwise FALSE.
+   */
+  public static function isVisibleElement($element) {
+    return !isset($element['#type']) || !in_array($element['#type'], array('value', 'hidden'));
+  }
+
+  /**
    * Sets HTML attributes based on element properties.
    *
    * @param array $element
diff --git a/core/modules/aggregator/lib/Drupal/aggregator/Form/OpmlFeedAdd.php b/core/modules/aggregator/lib/Drupal/aggregator/Form/OpmlFeedAdd.php
index 4284ee1..4432a08 100644
--- a/core/modules/aggregator/lib/Drupal/aggregator/Form/OpmlFeedAdd.php
+++ b/core/modules/aggregator/lib/Drupal/aggregator/Form/OpmlFeedAdd.php
@@ -115,7 +115,7 @@ public function validateForm(array &$form, array &$form_state) {
    */
   public function submitForm(array &$form, array &$form_state) {
     $validators = array('file_validate_extensions' => array('opml xml'));
-    if ($file = file_save_upload('upload', $form_state, $validators, FALSE, 0)) {
+    if ($file = file_save_upload('upload', $validators, FALSE, 0)) {
       $data = file_get_contents($file->getFileUri());
     }
     else {
diff --git a/core/modules/config/lib/Drupal/config/Form/ConfigImportForm.php b/core/modules/config/lib/Drupal/config/Form/ConfigImportForm.php
index bb5f4ed..ddbdca9 100644
--- a/core/modules/config/lib/Drupal/config/Form/ConfigImportForm.php
+++ b/core/modules/config/lib/Drupal/config/Form/ConfigImportForm.php
@@ -100,7 +100,7 @@ public function submitForm(array &$form, array &$form_state) {
         $form_state['redirect_route']['route_name'] = 'config.sync';
       }
       catch (\Exception $e) {
-        $this->setFormError('import_tarball', $form_state, $this->t('Could not extract the contents of the tar file. The error message is <em>@message</em>', array('@message' => $e->getMessage())));
+        drupal_set_message($this->t('Could not extract the contents of the tar file. The error message is <em>@message</em>', array('@message' => $e->getMessage())), 'error');
       }
       drupal_unlink($path);
     }
diff --git a/core/modules/file/file.module b/core/modules/file/file.module
index b707b57..db70bef 100644
--- a/core/modules/file/file.module
+++ b/core/modules/file/file.module
@@ -760,7 +760,7 @@ function file_cron() {
  *   - source: Path to the file before it is moved.
  *   - destination: Path to the file after it is moved (same as 'uri').
  */
-function file_save_upload($form_field_name, array &$form_state, $validators = array(), $destination = FALSE, $delta = NULL, $replace = FILE_EXISTS_RENAME) {
+function file_save_upload($form_field_name, $validators = array(), $destination = FALSE, $delta = NULL, $replace = FILE_EXISTS_RENAME) {
   $user = \Drupal::currentUser();
   static $upload_cache;
 
@@ -918,7 +918,7 @@ function file_save_upload($form_field_name, array &$form_state, $validators = ar
       else {
         $message .= ' ' . array_pop($errors);
       }
-      form_set_error($form_field_name, $form_state, $message);
+      drupal_set_message($message);
       $files[$i] = FALSE;
       continue;
     }
@@ -928,7 +928,7 @@ function file_save_upload($form_field_name, array &$form_state, $validators = ar
     // operations.
     $file->uri = $file->destination;
     if (!drupal_move_uploaded_file($file_info->getRealPath(), $file->getFileUri())) {
-      form_set_error($form_field_name, $form_state, t('File upload error. Could not move uploaded file.'));
+      drupal_set_message(t('File upload error. Could not move uploaded file.'));
       watchdog('file', 'Upload error. Could not move uploaded file %file to destination %destination.', array('%file' => $file->filename, '%destination' => $file->uri));
       $files[$i] = FALSE;
       continue;
@@ -1487,7 +1487,7 @@ function file_managed_file_save_upload($element, array &$form_state) {
   $files_uploaded = $element['#multiple'] && count(array_filter($file_upload)) > 0;
   $files_uploaded |= !$element['#multiple'] && !empty($file_upload);
   if ($files_uploaded) {
-    if (!$files = file_save_upload($upload_name, $form_state, $element['#upload_validators'], $destination)) {
+    if (!$files = file_save_upload($upload_name, $element['#upload_validators'], $destination)) {
       watchdog('file', 'The file upload failed. %upload', array('%upload' => $upload_name));
       form_set_error($upload_name, $form_state, t('Files in the !name field were unable to be uploaded.', array('!name' => $element['#title'])));
       return array();
diff --git a/core/modules/file/tests/file_test/lib/Drupal/file_test/Form/FileTestForm.php b/core/modules/file/tests/file_test/lib/Drupal/file_test/Form/FileTestForm.php
index 3c5f1fe..f2d6dc2 100644
--- a/core/modules/file/tests/file_test/lib/Drupal/file_test/Form/FileTestForm.php
+++ b/core/modules/file/tests/file_test/lib/Drupal/file_test/Form/FileTestForm.php
@@ -101,7 +101,7 @@ public function submitForm(array &$form, array &$form_state) {
       $validators['file_validate_extensions'] = array($form_state['values']['extensions']);
     }
 
-    $file = file_save_upload('file_test_upload', $form_state, $validators, $destination, 0, $form_state['values']['file_test_replace']);
+    $file = file_save_upload('file_test_upload', $validators, $destination, 0, $form_state['values']['file_test_replace']);
     if ($file) {
       $form_state['values']['file_test_upload'] = $file;
       drupal_set_message(t('File @filepath was uploaded.', array('@filepath' => $file->getFileUri())));
diff --git a/core/modules/locale/locale.bulk.inc b/core/modules/locale/locale.bulk.inc
index 4f06164..e93469f 100644
--- a/core/modules/locale/locale.bulk.inc
+++ b/core/modules/locale/locale.bulk.inc
@@ -115,7 +115,7 @@ function locale_translate_import_form($form, &$form_state) {
  */
 function locale_translate_import_form_submit($form, &$form_state) {
   // Ensure we have the file uploaded.
-  if ($file = file_save_upload('file', $form_state, $form['file']['#upload_validators'], 'translations://', 0)) {
+  if ($file = file_save_upload('file', $form['file']['#upload_validators'], 'translations://', 0)) {
 
     // Add language, if not yet supported.
     $language = \Drupal::languageManager()->getLanguage($form_state['values']['langcode']);
@@ -136,7 +136,7 @@ function locale_translate_import_form_submit($form, &$form_state) {
     batch_set($batch);
   }
   else {
-    form_set_error('file', $form_state, t('File to import not found.'));
+    drupal_set_message(t('File to import not found.'), 'error');
     $form_state['rebuild'] = TRUE;
     return;
   }
diff --git a/core/modules/search/lib/Drupal/search/Form/SearchBlockForm.php b/core/modules/search/lib/Drupal/search/Form/SearchBlockForm.php
index ad96735..40504f0 100644
--- a/core/modules/search/lib/Drupal/search/Form/SearchBlockForm.php
+++ b/core/modules/search/lib/Drupal/search/Form/SearchBlockForm.php
@@ -86,7 +86,7 @@ public function submitForm(array &$form, array &$form_state) {
     // "field is required" because the search keywords field has no title.
     // The error message would also complain about a missing #title field.)
     if ($form_state['values']['search_block_form'] == '') {
-      $this->setFormError('keys', $form_state, $this->t('Please enter some keywords.'));
+      drupal_set_message($this->t('Please enter some keywords.'), 'error');
     }
 
     $form_id = $form['form_id']['#value'];
@@ -99,7 +99,7 @@ public function submitForm(array &$form, array &$form_state) {
       );
     }
     else {
-      $this->setFormError('', $form_state, $this->t('Search is currently disabled.'));
+      drupal_set_message($this->t('Search is currently disabled.'), 'error');
     }
   }
 
diff --git a/core/modules/shortcut/shortcut.admin.inc b/core/modules/shortcut/shortcut.admin.inc
index 3408d83..e6f3a86 100644
--- a/core/modules/shortcut/shortcut.admin.inc
+++ b/core/modules/shortcut/shortcut.admin.inc
@@ -118,7 +118,7 @@ function shortcut_set_switch_validate($form, &$form_state) {
   if ($form_state['values']['set'] == 'new') {
     // Check to prevent creating a shortcut set with an empty title.
     if (trim($form_state['values']['label']) == '') {
-      form_set_error('new', $form_state, t('The new set label is required.'));
+      form_set_error('label', $form_state, t('The new set label is required.'));
     }
     // Check to prevent a duplicate title.
     if (shortcut_set_title_exists($form_state['values']['label'])) {
diff --git a/core/modules/system/css/system.theme.css b/core/modules/system/css/system.theme.css
index f397a5a..3e43abb 100644
--- a/core/modules/system/css/system.theme.css
+++ b/core/modules/system/css/system.theme.css
@@ -44,6 +44,16 @@ td.active {
 /**
  * Markup generated by Form API.
  */
+.form-error {
+  background-color: #fef5f1;
+  border: 1px solid #ed541d;
+  color: #8c2e0b;
+  padding: 10px;
+}
+.form-error-message {
+  margin-bottom: 10px;
+  min-height: 25px;
+}
 .form-item,
 .form-actions {
   margin-top: 1em;
diff --git a/core/modules/system/lib/Drupal/system/Form/ThemeSettingsForm.php b/core/modules/system/lib/Drupal/system/Form/ThemeSettingsForm.php
index 9c605e9..ad3ceaa 100644
--- a/core/modules/system/lib/Drupal/system/Form/ThemeSettingsForm.php
+++ b/core/modules/system/lib/Drupal/system/Form/ThemeSettingsForm.php
@@ -324,7 +324,7 @@ public function validateForm(array &$form, array &$form_state) {
       $validators = array('file_validate_is_image' => array());
 
       // Check for a new uploaded logo.
-      $file = file_save_upload('logo_upload', $form_state, $validators, FALSE, 0);
+      $file = file_save_upload('logo_upload', $validators, FALSE, 0);
       if (isset($file)) {
         // File upload was attempted.
         if ($file) {
@@ -340,7 +340,7 @@ public function validateForm(array &$form, array &$form_state) {
       $validators = array('file_validate_extensions' => array('ico png gif jpg jpeg apng svg'));
 
       // Check for a new uploaded favicon.
-      $file = file_save_upload('favicon_upload', $form_state, $validators, FALSE, 0);
+      $file = file_save_upload('favicon_upload', $validators, FALSE, 0);
       if (isset($file)) {
         // File upload was attempted.
         if ($file) {
diff --git a/core/modules/system/lib/Drupal/system/Tests/Form/FormTest.php b/core/modules/system/lib/Drupal/system/Tests/Form/FormTest.php
index b006591..a13a287 100644
--- a/core/modules/system/lib/Drupal/system/Tests/Form/FormTest.php
+++ b/core/modules/system/lib/Drupal/system/Tests/Form/FormTest.php
@@ -188,7 +188,7 @@ function testRequiredCheckboxesRadio() {
     }
 
     // Check the page for error messages.
-    $errors = $this->xpath('//div[contains(@class, "error")]//li');
+    $errors = $this->xpath('//div[contains(@class, "form-error-message")]//strong');
     foreach ($errors as $error) {
       $expected_key = array_search($error[0], $expected);
       // If the error message is not one of the expected messages, fail.
diff --git a/core/modules/system/templates/form-element.html.twig b/core/modules/system/templates/form-element.html.twig
index ea4d90f..4c0d8d9 100644
--- a/core/modules/system/templates/form-element.html.twig
+++ b/core/modules/system/templates/form-element.html.twig
@@ -5,6 +5,7 @@
  *
  * Available variables:
  * - attributes: HTML attributes for the containing element.
+ * - errors: (optional) Any errors for this form element, may not be set.
  * - prefix: (optional) The form element prefix, may not be set.
  * - suffix: (optional) The form element suffix, may not be set.
  * - required: The required marker, or empty if the associated form element is
@@ -37,6 +38,11 @@
  */
 #}
 <div{{ attributes }}>
+  {% if errors %}
+    <div class="form-error-message">
+      <strong>{{ errors }}</strong>
+    </div>
+  {% endif %}
   {% if label_display in ['before', 'invisible'] %}
     {{ label }}
   {% endif %}
diff --git a/core/modules/update/update.manager.inc b/core/modules/update/update.manager.inc
index f5c1d8a..83203ae 100644
--- a/core/modules/update/update.manager.inc
+++ b/core/modules/update/update.manager.inc
@@ -659,16 +659,16 @@ function update_manager_install_form_submit($form, &$form_state) {
     $field = 'project_url';
     $local_cache = update_manager_file_get($form_state['values']['project_url']);
     if (!$local_cache) {
-      form_set_error($field, $form_state, t('Unable to retrieve Drupal project from %url.', array('%url' => $form_state['values']['project_url'])));
+      drupal_set_message(t('Unable to retrieve Drupal project from %url.', array('%url' => $form_state['values']['project_url'])), 'error');
       return;
     }
   }
   elseif ($_FILES['files']['name']['project_upload']) {
     $validators = array('file_validate_extensions' => array(archiver_get_extensions()));
     $field = 'project_upload';
-    if (!($finfo = file_save_upload($field, $form_state, $validators, NULL, 0, FILE_EXISTS_REPLACE))) {
-      // Failed to upload the file. file_save_upload() calls form_set_error() on
-      // failure.
+    if (!($finfo = file_save_upload($field, $validators, NULL, 0, FILE_EXISTS_REPLACE))) {
+      // Failed to upload the file. file_save_upload() calls
+      // drupal_set_message() on failure.
       return;
     }
     $local_cache = $finfo->getFileUri();
@@ -679,13 +679,13 @@ function update_manager_install_form_submit($form, &$form_state) {
     $archive = update_manager_archive_extract($local_cache, $directory);
   }
   catch (Exception $e) {
-    form_set_error($field, $form_state, $e->getMessage());
+    drupal_set_message($e->getMessage(), 'error');
     return;
   }
 
   $files = $archive->listContents();
   if (!$files) {
-    form_set_error($field, $form_state, t('Provided archive contains no files.'));
+    drupal_set_message(t('Provided archive contains no files.'), 'error');
     return;
   }
 
@@ -696,7 +696,7 @@ function update_manager_install_form_submit($form, &$form_state) {
 
   $archive_errors = update_manager_archive_verify($project, $local_cache, $directory);
   if (!empty($archive_errors)) {
-    form_set_error($field, $form_state, array_shift($archive_errors));
+    drupal_set_message(array_shift($archive_errors), 'error');
     // @todo: Fix me in D8: We need a way to set multiple errors on the same
     // form element and have all of them appear!
     if (!empty($archive_errors)) {
@@ -715,7 +715,7 @@ function update_manager_install_form_submit($form, &$form_state) {
     $updater = Updater::factory($project_location);
   }
   catch (Exception $e) {
-    form_set_error($field, $form_state, $e->getMessage());
+    drupal_set_message($e->getMessage(), 'error');
     return;
   }
 
@@ -723,16 +723,16 @@ function update_manager_install_form_submit($form, &$form_state) {
     $project_title = Updater::getProjectTitle($project_location);
   }
   catch (Exception $e) {
-    form_set_error($field, $form_state, $e->getMessage());
+    drupal_set_message($e->getMessage(), 'error');
     return;
   }
 
   if (!$project_title) {
-    form_set_error($field, $form_state, t('Unable to determine %project name.', array('%project' => $project)));
+    drupal_set_message(t('Unable to determine %project name.', array('%project' => $project)), 'error');
   }
 
   if ($updater->isInstalled()) {
-    form_set_error($field, $form_state, t('%project is already installed.', array('%project' => $project_title)));
+    drupal_set_message(t('%project is already installed.', array('%project' => $project_title)), 'error');
     return;
   }
 
diff --git a/core/tests/Drupal/Tests/Core/Form/FormElementHelperTest.php b/core/tests/Drupal/Tests/Core/Form/FormElementHelperTest.php
new file mode 100644
index 0000000..58973bd
--- /dev/null
+++ b/core/tests/Drupal/Tests/Core/Form/FormElementHelperTest.php
@@ -0,0 +1,81 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\Tests\Core\Form\FormBuilderTest.
+ */
+
+namespace Drupal\Tests\Core\Form;
+
+use Drupal\Core\Form\FormElementHelper;
+use Drupal\Tests\UnitTestCase;
+
+/**
+ * Tests the form element helper.
+ *
+ * @group Drupal
+ * @group Form
+ *
+ * @coversDefaultClass \Drupal\Core\Form\FormElementHelper
+ */
+class FormElementHelperTest extends UnitTestCase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function getInfo() {
+    return array(
+      'name' => 'FormElementHelper test',
+      'description' => 'Tests the form element helper.',
+      'group' => 'Form API',
+    );
+  }
+
+  /**
+   * Tests the getElementByName() method.
+   *
+   * @covers ::getElementByName()
+   *
+   * @dataProvider getElementByNameProvider
+   */
+  public function testGetElementByName($name, $form, $expected) {
+    $this->assertSame($expected, FormElementHelper::getElementByName($name, $form));
+  }
+
+  /**
+   * Provides test data.
+   */
+  public function getElementByNameProvider() {
+    return array(
+      array('id', array(), array()),
+      array('id', array('id' => array('#title' => 'ID')), array('#title' => 'ID')),
+      array('id', array('fieldset' => array('id' => array('#title' => 'ID'))), array('#title' => 'ID')),
+      array('fieldset', array('fieldset' => array('id' => array('#title' => 'ID'))), array('id' => array('#title' => 'ID'))),
+    );
+  }
+
+  /**
+   * Tests the getElementTitle() method.
+   *
+   * @covers ::getElementTitle()
+   *
+   * @dataProvider getElementTitleProvider
+   */
+  public function testGetElementTitle($name, $form, $expected) {
+    $element = FormElementHelper::getElementByName($name, $form);
+    $this->assertSame($expected, FormElementHelper::getElementTitle($element));
+  }
+
+  /**
+   * Provides test data.
+   */
+  public function getElementTitleProvider() {
+    return array(
+      array('id', array(), ''),
+      array('id', array('id' => array('#title' => 'ID')), 'ID'),
+      array('id', array('fieldset' => array('id' => array('#title' => 'ID'))), 'ID'),
+      array('fieldset', array('fieldset' => array('id' => array('#title' => 'ID'))), 'ID'),
+    );
+  }
+
+}
diff --git a/core/tests/Drupal/Tests/Core/Form/FormTestBase.php b/core/tests/Drupal/Tests/Core/Form/FormTestBase.php
index 44059c4..9e789a0 100644
--- a/core/tests/Drupal/Tests/Core/Form/FormTestBase.php
+++ b/core/tests/Drupal/Tests/Core/Form/FormTestBase.php
@@ -291,12 +291,6 @@ protected function menuGetItem() {
   /**
    * {@inheritdoc}
    */
-  protected function drupalSetMessage($message = NULL, $type = 'status', $repeat = FALSE) {
-  }
-
-  /**
-   * {@inheritdoc}
-   */
   protected function watchdog($type, $message, array $variables = NULL, $severity = WATCHDOG_NOTICE, $link = NULL) {
   }
 
