diff --git a/core/lib/Drupal/Component/Utility/Html.php b/core/lib/Drupal/Component/Utility/Html.php
index 60b83df..a449317 100644
--- a/core/lib/Drupal/Component/Utility/Html.php
+++ b/core/lib/Drupal/Component/Utility/Html.php
@@ -35,11 +35,11 @@ class Html {
   protected static $seenIds;
 
   /**
-   * Contains the current AJAX HTML IDs.
+   * Stores whether we want to have random HTML IDs.
    *
-   * @var string
+   * @var bool
    */
-  protected static $ajaxHTMLIDs;
+  protected static $randomIds = TRUE;
 
   /**
    * Prepares a string for use as a valid class name.
@@ -102,13 +102,13 @@ public static function cleanCssIdentifier($identifier, array $filter = array(
   }
 
   /**
-   * Sets the AJAX HTML IDs.
+   * Sets if we want to have random HTML IDs.
    *
-   * @param string $ajax_html_ids
-   *   The AJAX HTML IDs, probably coming from the current request.
+   * @param bool $random_ids
+   *   TRUE if we want to have random IDs.
    */
-  public static function setAjaxHtmlIds($ajax_html_ids = '') {
-    static::$ajaxHTMLIDs = $ajax_html_ids;
+  public static function setRandomIds($random_ids) {
+    static::$randomIds = $random_ids;
   }
 
   /**
@@ -142,43 +142,15 @@ public static function setAjaxHtmlIds($ajax_html_ids = '') {
   public static function getUniqueId($id) {
     // If this is an Ajax request, then content returned by this page request
     // will be merged with content already on the base page. The HTML IDs must
-    // be unique for the fully merged content. Therefore, initialize $seen_ids
-    // to take into account IDs that are already in use on the base page.
+    // be unique for the fully merged content. Therefore use unique IDs.
+    if (static::$randomIds) {
+      return static::getId($id) . '--' . Crypt::randomBytesBase64(8);
+    }
+
+    // @todo Remove all that code once we switch over to random IDs only,
+    // see https://www.drupal.org/node/1090592.
     if (!isset(static::$seenIdsInit)) {
-      // Ideally, Drupal would provide an API to persist state information about
-      // prior page requests in the database, and we'd be able to add this
-      // function's $seen_ids static variable to that state information in order
-      // to have it properly initialized for this page request. However, no such
-      // page state API exists, so instead, ajax.js adds all of the in-use HTML
-      // IDs to the POST data of Ajax submissions. Direct use of $_POST is
-      // normally not recommended as it could open up security risks, but
-      // because the raw POST data is cast to a number before being returned by
-      // this function, this usage is safe.
-      if (empty(static::$ajaxHTMLIDs)) {
-        static::$seenIdsInit = array();
-      }
-      else {
-        // This function ensures uniqueness by appending a counter to the base
-        // id requested by the calling function after the first occurrence of
-        // that requested id. $_POST['ajax_html_ids'] contains the ids as they
-        // were returned by this function, potentially with the appended
-        // counter, so we parse that to reconstruct the $seen_ids array.
-        $ajax_html_ids = explode(' ', static::$ajaxHTMLIDs);
-        foreach ($ajax_html_ids as $seen_id) {
-          // We rely on '--' being used solely for separating a base id from the
-          // counter, which this function ensures when returning an id.
-          $parts = explode('--', $seen_id, 2);
-          if (!empty($parts[1]) && is_numeric($parts[1])) {
-            list($seen_id, $i) = $parts;
-          }
-          else {
-            $i = 1;
-          }
-          if (!isset(static::$seenIdsInit[$seen_id]) || ($i > static::$seenIdsInit[$seen_id])) {
-            static::$seenIdsInit[$seen_id] = $i;
-          }
-        }
-      }
+      static::$seenIdsInit = array();
     }
     if (!isset(static::$seenIds)) {
       static::$seenIds = static::$seenIdsInit;
diff --git a/core/lib/Drupal/Core/EventSubscriber/AjaxSubscriber.php b/core/lib/Drupal/Core/EventSubscriber/AjaxSubscriber.php
index 8996b5d..ee4477c 100644
--- a/core/lib/Drupal/Core/EventSubscriber/AjaxSubscriber.php
+++ b/core/lib/Drupal/Core/EventSubscriber/AjaxSubscriber.php
@@ -20,13 +20,17 @@
 class AjaxSubscriber implements EventSubscriberInterface {
 
   /**
-   * Sets the AJAX HTML IDs from the current request.
+   * Sets the AJAX parameter from the current request.
    *
    * @param \Symfony\Component\HttpKernel\Event\GetResponseEvent $event
    *   The response event, which contains the current request.
    */
   public function onRequest(GetResponseEvent $event) {
-    Html::setAjaxHtmlIds($event->getRequest()->request->get('ajax_html_ids', ''));
+    // Don't randomize on HTML pages.
+    $request = $event->getRequest();
+    if ($request->getRequestFormat() === 'html' && (!$request->query->has(MainContentViewSubscriber::WRAPPER_FORMAT) || $request->query->get(MainContentViewSubscriber::WRAPPER_FORMAT) === 'html')) {
+      Html::setRandomIds(FALSE);
+    }
   }
 
   /**
diff --git a/core/lib/Drupal/Core/Form/FormBuilder.php b/core/lib/Drupal/Core/Form/FormBuilder.php
index 4b995c1..5859d9e 100644
--- a/core/lib/Drupal/Core/Form/FormBuilder.php
+++ b/core/lib/Drupal/Core/Form/FormBuilder.php
@@ -746,7 +746,16 @@ public function doBuildForm($form_id, &$element, FormStateInterface &$form_state
     }
 
     if (!isset($element['#id'])) {
-      $element['#id'] = Html::getUniqueId('edit-' . implode('-', $element['#parents']));
+      $unprocessed_id = 'edit-' . implode('-', $element['#parents']);
+      $element['#id'] = Html::getUniqueId($unprocessed_id);
+      // Provide a selector usable by JavaScript. As the ID is unique, its not
+      // possible to rely on it in JavaScript.
+      $element['#attributes']['data-drupal-selector'] = Html::getId($unprocessed_id);
+    }
+    else {
+      // Provide a selector usable by JavaScript. As the ID is unique, its not
+      // possible to rely on it in JavaScript.
+      $element['#attributes']['data-drupal-selector'] = Html::getId($element['#id']);
     }
 
     // Add the aria-describedby attribute to associate the form control with its
diff --git a/core/misc/ajax.js b/core/misc/ajax.js
index 3cb1c8d..d4e3c83 100644
--- a/core/misc/ajax.js
+++ b/core/misc/ajax.js
@@ -580,16 +580,8 @@
       Drupal.detachBehaviors(this.$form.get(0), settings, 'serialize');
     }
 
-    // Prevent duplicate HTML ids in the returned markup.
-    // @see \Drupal\Component\Utility\Html::getUniqueId()
-    var ids = document.querySelectorAll('[id]');
-    var ajaxHtmlIds = [];
-    var il = ids.length;
-    for (var i = 0; i < il; i++) {
-      ajaxHtmlIds.push(ids[i].id);
-    }
-    // Join IDs to minimize request size.
-    options.data.ajax_html_ids = ajaxHtmlIds.join(' ');
+    // Inform Drupal that this is an AJAX request.
+    options.data[Drupal.Ajax.AJAX_REQUEST_PARAMETER] = 1;
 
     // Allow Drupal to return new JavaScript and CSS files to load without
     // returning the ones already loaded.
diff --git a/core/modules/block/js/block.admin.js b/core/modules/block/js/block.admin.js
index b399dd0..23109f4 100644
--- a/core/modules/block/js/block.admin.js
+++ b/core/modules/block/js/block.admin.js
@@ -92,7 +92,7 @@
   Drupal.behaviors.blockHighlightPlacement = {
     attach: function (context, settings) {
       if (settings.blockPlacement) {
-        $('#blocks').once('block-highlight').each(function () {
+        $(context).find('[data-drupal-selector="edit-blocks"]').once('block-highlight').each(function () {
           var $container = $(this);
           // Just scrolling the document.body will not work in Firefox. The html
           // element is needed as well.
diff --git a/core/modules/block/js/block.js b/core/modules/block/js/block.js
index e5c0907..acf7e4a 100644
--- a/core/modules/block/js/block.js
+++ b/core/modules/block/js/block.js
@@ -34,9 +34,9 @@
         return vals.join(', ');
       }
 
-      $('#edit-visibility-node-type, #edit-visibility-language, #edit-visibility-user-role').drupalSetSummary(checkboxesSummary);
+      $('[data-drupal-selector="edit-visibility-node-type"], [data-drupal-selector="edit-visibility-language"], [data-drupal-selector="edit-visibility-user-role"]').drupalSetSummary(checkboxesSummary);
 
-      $('#edit-visibility-request-path').drupalSetSummary(function (context) {
+      $('[data-drupal-selector="edit-visibility-request-path"]').drupalSetSummary(function (context) {
         var $pages = $(context).find('textarea[name="visibility[request_path][pages]"]');
         if (!$pages.val()) {
           return Drupal.t('Not restricted');
diff --git a/core/modules/block/src/Tests/BlockTest.php b/core/modules/block/src/Tests/BlockTest.php
index d6e0999..be99458 100644
--- a/core/modules/block/src/Tests/BlockTest.php
+++ b/core/modules/block/src/Tests/BlockTest.php
@@ -211,7 +211,7 @@ public function testBlockThemeSelector() {
       // Set the default theme and ensure the block is placed.
       $theme_settings->set('default', $theme)->save();
       $this->drupalGet('');
-      $elements = $this->xpath('//div[@id = :id]', array(':id' => Html::getUniqueId('block-' . $block['id'])));
+      $elements = $this->xpath('//div[starts-with(@id, :id)]', array(':id' => Html::getId('block-' . $block['id'])));
       $this->assertTrue(!empty($elements), 'The block was found.');
     }
   }
diff --git a/core/modules/block/src/Tests/BlockViewBuilderTest.php b/core/modules/block/src/Tests/BlockViewBuilderTest.php
index 02fec0f..2998078 100644
--- a/core/modules/block/src/Tests/BlockViewBuilderTest.php
+++ b/core/modules/block/src/Tests/BlockViewBuilderTest.php
@@ -79,6 +79,9 @@ public function testBasicRendering() {
     ));
     $entity->save();
 
+    // Ensure to opt out of random HTML IDs.
+    Html::setRandomIds(FALSE);
+
     // Test the rendering of a block.
     $entity = Block::load('test_block1');
     $output = entity_view($entity, 'block');
diff --git a/core/modules/block/src/Tests/Views/DisplayBlockTest.php b/core/modules/block/src/Tests/Views/DisplayBlockTest.php
index 3f96ab1..b01d9d3 100644
--- a/core/modules/block/src/Tests/Views/DisplayBlockTest.php
+++ b/core/modules/block/src/Tests/Views/DisplayBlockTest.php
@@ -95,10 +95,10 @@ public function testBlockCategory() {
     $this->drupalPostForm(NULL, array(), t('Save'));
 
     // Test that the blocks are listed under the correct categories.
-    $category_id = Html::getUniqueId('edit-category-' . SafeMarkup::checkPlain($category));
+    $category_id = Html::getId('edit-category-' . SafeMarkup::checkPlain($category));
     $arguments[':id'] = $category_id;
     $this->drupalGet('admin/structure/block');
-    $elements = $this->xpath('//details[@id=:id]//li[contains(@class, :li_class)]/a[contains(@href, :href) and text()=:text]', $arguments);
+    $elements = $this->xpath('//details[starts-with(@id, :id)]//li[contains(@class, :li_class)]/a[contains(@href, :href) and text()=:text]', $arguments);
     $this->assertTrue(!empty($elements), 'The test block appears in the custom category.');
 
     $arguments = array(
@@ -122,7 +122,7 @@ public function testBlockCategory() {
       )),
       ':text' => $edit['label'],
     );
-    $elements = $this->xpath('//details[@id=:id]//li[contains(@class, :li_class)]/a[contains(@href, :href) and text()=:text]', $arguments);
+    $elements = $this->xpath('//details[starts-with(@id, :id)]//li[contains(@class, :li_class)]/a[contains(@href, :href) and text()=:text]', $arguments);
     $this->assertTrue(!empty($elements), 'The second duplicated test block appears in the custom category.');
   }
 
diff --git a/core/modules/ckeditor/js/ckeditor.drupalimage.admin.js b/core/modules/ckeditor/js/ckeditor.drupalimage.admin.js
index 96288cf..a4ebce2 100644
--- a/core/modules/ckeditor/js/ckeditor.drupalimage.admin.js
+++ b/core/modules/ckeditor/js/ckeditor.drupalimage.admin.js
@@ -14,7 +14,7 @@
    */
   Drupal.behaviors.ckeditorDrupalImageSettingsSummary = {
     attach: function () {
-      $('#edit-editor-settings-plugins-drupalimage').drupalSetSummary(function (context) {
+      $('[data-ckeditor-plugin-id="drupalimage"]').drupalSetSummary(function (context) {
         var root = 'input[name="editor[settings][plugins][drupalimage][image_upload]';
         var $status = $(root + '[status]"]');
         var $maxFileSize = $(root + '[max_size]"]');
diff --git a/core/modules/ckeditor/js/ckeditor.stylescombo.admin.js b/core/modules/ckeditor/js/ckeditor.stylescombo.admin.js
index b73d1b1..76e7421 100644
--- a/core/modules/ckeditor/js/ckeditor.stylescombo.admin.js
+++ b/core/modules/ckeditor/js/ckeditor.stylescombo.admin.js
@@ -33,7 +33,7 @@
       var that = this;
       $context.find('[name="editor[settings][plugins][stylescombo][styles]"]')
         .on('blur.ckeditorStylesComboSettings', function () {
-          var styles = $.trim($('#edit-editor-settings-plugins-stylescombo-styles').val());
+          var styles = $.trim($(this).val());
           var stylesSet = that._generateStylesSetSetting(styles);
           if (!_.isEqual(previousStylesSet, stylesSet)) {
             previousStylesSet = stylesSet;
@@ -105,8 +105,8 @@
    */
   Drupal.behaviors.ckeditorStylesComboSettingsSummary = {
     attach: function () {
-      $('#edit-editor-settings-plugins-stylescombo').drupalSetSummary(function (context) {
-        var styles = $.trim($('#edit-editor-settings-plugins-stylescombo-styles').val());
+      $('[data-ckeditor-plugin-id="stylescombo"]').drupalSetSummary(function (context) {
+        var styles = $.trim($('[data-drupal-selector="edit-editor-settings-plugins-stylescombo-styles"]').val());
         if (styles.length === 0) {
           return Drupal.t('No styles configured');
         }
diff --git a/core/modules/contact/src/Tests/ContactPersonalTest.php b/core/modules/contact/src/Tests/ContactPersonalTest.php
index 102452e..1f309dff 100644
--- a/core/modules/contact/src/Tests/ContactPersonalTest.php
+++ b/core/modules/contact/src/Tests/ContactPersonalTest.php
@@ -200,10 +200,10 @@ function testPersonalContactAccess() {
     // Test enabling and disabling the contact page through the user profile
     // form.
     $this->drupalGet('user/' . $this->webUser->id() . '/edit');
-    $this->assertNoFieldChecked('edit-contact--2');
+    $this->assertNoFieldChecked('edit-contact');
     $this->assertFalse(\Drupal::service('user.data')->get('contact', $this->webUser->id(), 'enabled'), 'Personal contact form disabled');
     $this->drupalPostForm(NULL, array('contact' => TRUE), t('Save'));
-    $this->assertFieldChecked('edit-contact--2');
+    $this->assertFieldChecked('edit-contact');
     $this->assertTrue(\Drupal::service('user.data')->get('contact', $this->webUser->id(), 'enabled'), 'Personal contact form enabled');
 
     // Test with disabled global default contact form in combination with a user
@@ -271,10 +271,10 @@ protected function checkContactAccess($response, $contact_value = NULL) {
     $this->drupalLogin($this->adminUser);
     $this->drupalGet('admin/people/create');
     if ($this->config('contact.settings')->get('user_default_enabled', TRUE)) {
-      $this->assertFieldChecked('edit-contact--2');
+      $this->assertFieldChecked('edit-contact');
     }
     else {
-      $this->assertNoFieldChecked('edit-contact--2');
+      $this->assertNoFieldChecked('edit-contact');
     }
     $name = $this->randomMachineName();
     $edit = array(
diff --git a/core/modules/field/src/Tests/Boolean/BooleanFieldTest.php b/core/modules/field/src/Tests/Boolean/BooleanFieldTest.php
index 28f6334..647180c 100644
--- a/core/modules/field/src/Tests/Boolean/BooleanFieldTest.php
+++ b/core/modules/field/src/Tests/Boolean/BooleanFieldTest.php
@@ -171,7 +171,7 @@ function testBooleanField() {
       t('Display setting checkbox is available')
     );
     $this->assertFieldByXPath(
-      '*//input[@id="edit-fields-' . $field_name . '-settings-edit-form-settings-display-label" and @value="1"]',
+      '*//input[starts-with(@id, "edit-fields-' . $field_name . '-settings-edit-form-settings-display-label") and @value="1"]',
       TRUE,
       t('Display label changes label of the checkbox')
     );
diff --git a/core/modules/field_ui/field_ui.js b/core/modules/field_ui/field_ui.js
index 3c9e5ae..f73df41 100644
--- a/core/modules/field_ui/field_ui.js
+++ b/core/modules/field_ui/field_ui.js
@@ -12,7 +12,7 @@
    */
   Drupal.behaviors.fieldUIFieldStorageAddForm = {
     attach: function (context) {
-      var $form = $(context).find('#field-ui-field-storage-add-form').once('field_ui_add');
+      var $form = $(context).find('[data-drupal-selector="field-ui-field-storage-add-form"]').once('field_ui_add');
       if ($form.length) {
         // Add a few 'form-required' css classes here. We can not use the Form
         // API '#required' property because both label elements for "add new"
@@ -214,7 +214,7 @@
 
         // Fire the Ajax update.
         $('input[name=refresh_rows]').val(rowNames.join(' '));
-        $('input#edit-refresh').trigger('mousedown');
+        $('input[data-drupal-selector="edit-refresh"]').trigger('mousedown');
 
         // Disabled elements do not appear in POST ajax data, so we mark the
         // elements disabled only after firing the request.
diff --git a/core/modules/file/src/Element/ManagedFile.php b/core/modules/file/src/Element/ManagedFile.php
index afde5af..6b06599 100644
--- a/core/modules/file/src/Element/ManagedFile.php
+++ b/core/modules/file/src/Element/ManagedFile.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\file\Element;
 
+use Drupal\Component\Utility\Html;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Render\Element\FormElement;
 use Drupal\Core\Url;
@@ -130,9 +131,6 @@ public static function valueCallback(&$element, $input, FormStateInterface $form
    * support for a default value.
    */
   public static function processManagedFile(&$element, FormStateInterface $form_state, &$complete_form) {
-    // Append the '-upload' to the #id so the field label's 'for' attribute
-    // corresponds with the file element.
-    $element['#id'] .= '-upload';
 
     // This is used sometimes so let's implode it just once.
     $parents_prefix = implode('_', $element['#parents']);
@@ -144,6 +142,9 @@ public static function processManagedFile(&$element, FormStateInterface $form_st
     $element['#files'] = !empty($fids) ? File::loadMultiple($fids) : FALSE;
     $element['#tree'] = TRUE;
 
+    // Generate a unique wrapper HTML ID.
+    $ajax_wrapper_id = Html::getUniqueId('ajax-wrapper');
+
     $ajax_settings = [
       'url' => Url::fromRoute('file.ajax_upload'),
       'options' => [
@@ -152,7 +153,7 @@ public static function processManagedFile(&$element, FormStateInterface $form_st
           'form_build_id' => $complete_form['form_build_id']['#value'],
         ],
       ],
-      'wrapper' => $element['#id'] . '-ajax-wrapper',
+      'wrapper' => $ajax_wrapper_id,
       'effect' => 'fade',
       'progress' => [
         'type' => $element['#progress_indicator'],
@@ -260,8 +261,12 @@ public static function processManagedFile(&$element, FormStateInterface $form_st
       $element['upload']['#attached']['drupalSettings']['file']['elements']['#' . $element['#id']] = $extension_list;
     }
 
+    // Let #id point to the file element, so the field label's 'for' corresponds
+    // with it.
+    $element['#id'] = &$element['upload']['#id'];
+
     // Prefix and suffix used for Ajax replacement.
-    $element['#prefix'] = '<div id="' . $element['#id'] . '-ajax-wrapper">';
+    $element['#prefix'] = '<div id="' . $ajax_wrapper_id . '">';
     $element['#suffix'] = '</div>';
 
     return $element;
diff --git a/core/modules/file/src/Tests/FileFieldWidgetTest.php b/core/modules/file/src/Tests/FileFieldWidgetTest.php
index ec02bb4..10f2cad 100644
--- a/core/modules/file/src/Tests/FileFieldWidgetTest.php
+++ b/core/modules/file/src/Tests/FileFieldWidgetTest.php
@@ -9,7 +9,6 @@
 
 use Drupal\comment\Entity\Comment;
 use Drupal\comment\Tests\CommentTestTrait;
-use Drupal\Component\Utility\Html;
 use Drupal\field\Entity\FieldConfig;
 use Drupal\field_ui\Tests\FieldUiTestTrait;
 use Drupal\user\RoleInterface;
@@ -86,7 +85,8 @@ function testSingleValuedWidget() {
       $this->assertNoFieldByXPath('//input[@type="submit"]', t('Remove'), 'After clicking the "Remove" button, it is no longer displayed.');
       $this->assertFieldByXpath('//input[@type="submit"]', t('Upload'), 'After clicking the "Remove" button, the "Upload" button is displayed.');
       // Test label has correct 'for' attribute.
-      $label = $this->xpath("//label[@for='edit-" . Html::cleanCssIdentifier($field_name) . "-0-upload']");
+      $input = $this->xpath('//input[@name="files[' . $field_name . '_0]"]');
+      $label = $this->xpath('//label[@for="' . (string) $input[0]['id'] . '"]');
       $this->assertTrue(isset($label[0]), 'Label for upload found.');
 
       // Save the node and ensure it does not have the file.
diff --git a/core/modules/filter/src/Tests/FilterFormTest.php b/core/modules/filter/src/Tests/FilterFormTest.php
index f8555fd..1e5674a 100644
--- a/core/modules/filter/src/Tests/FilterFormTest.php
+++ b/core/modules/filter/src/Tests/FilterFormTest.php
@@ -91,40 +91,40 @@ protected function doFilterFormTestAsAdmin() {
     $this->assertEnabledTextarea('edit-all-formats-no-default-value');
     // If no default is given, the format with the lowest weight becomes the
     // default.
-    $this->assertOptions('edit-all-formats-no-default-format--2', $formats, 'filtered_html');
+    $this->assertOptions('edit-all-formats-no-default-format', $formats, 'filtered_html');
     $this->assertEnabledTextarea('edit-all-formats-default-value');
     // \Drupal\filter_test\Form\FilterTestFormatForm::buildForm() uses
     // 'filter_test' as the default value in this case.
-    $this->assertOptions('edit-all-formats-default-format--2', $formats, 'filter_test');
+    $this->assertOptions('edit-all-formats-default-format', $formats, 'filter_test');
     $this->assertEnabledTextarea('edit-all-formats-default-missing-value');
     // If a missing format is set as the default, administrators must select a
     // valid replacement format.
-    $this->assertRequiredSelectAndOptions('edit-all-formats-default-missing-format--2', $formats);
+    $this->assertRequiredSelectAndOptions('edit-all-formats-default-missing-format', $formats);
 
     // Test a text format element with a predefined list of formats.
     $formats = array('full_html', 'filter_test');
     $this->assertEnabledTextarea('edit-restricted-formats-no-default-value');
-    $this->assertOptions('edit-restricted-formats-no-default-format--2', $formats, 'full_html');
+    $this->assertOptions('edit-restricted-formats-no-default-format', $formats, 'full_html');
     $this->assertEnabledTextarea('edit-restricted-formats-default-value');
-    $this->assertOptions('edit-restricted-formats-default-format--2', $formats, 'full_html');
+    $this->assertOptions('edit-restricted-formats-default-format', $formats, 'full_html');
     $this->assertEnabledTextarea('edit-restricted-formats-default-missing-value');
-    $this->assertRequiredSelectAndOptions('edit-restricted-formats-default-missing-format--2', $formats);
+    $this->assertRequiredSelectAndOptions('edit-restricted-formats-default-missing-format', $formats);
     $this->assertEnabledTextarea('edit-restricted-formats-default-disallowed-value');
-    $this->assertRequiredSelectAndOptions('edit-restricted-formats-default-disallowed-format--2', $formats);
+    $this->assertRequiredSelectAndOptions('edit-restricted-formats-default-disallowed-format', $formats);
 
     // Test a text format element with a fixed format.
     $formats = array('filter_test');
     // When there is only a single option there is no point in choosing.
     $this->assertEnabledTextarea('edit-single-format-no-default-value');
-    $this->assertNoSelect('edit-single-format-no-default-format--2');
+    $this->assertNoSelect('edit-single-format-no-default-format');
     $this->assertEnabledTextarea('edit-single-format-default-value');
-    $this->assertNoSelect('edit-single-format-default-format--2');
+    $this->assertNoSelect('edit-single-format-default-format');
     // If the select has a missing or disallowed format, administrators must
     // explicitly choose the format.
     $this->assertEnabledTextarea('edit-single-format-default-missing-value');
-    $this->assertRequiredSelectAndOptions('edit-single-format-default-missing-format--2', $formats);
+    $this->assertRequiredSelectAndOptions('edit-single-format-default-missing-format', $formats);
     $this->assertEnabledTextarea('edit-single-format-default-disallowed-value');
-    $this->assertRequiredSelectAndOptions('edit-single-format-default-disallowed-format--2', $formats);
+    $this->assertRequiredSelectAndOptions('edit-single-format-default-disallowed-format', $formats);
   }
 
   /**
@@ -140,11 +140,11 @@ protected function doFilterFormTestAsNonAdmin() {
     $this->assertEnabledTextarea('edit-all-formats-no-default-value');
     // If no default is given, the format with the lowest weight becomes the
     // default. This happens to be 'filtered_html'.
-    $this->assertOptions('edit-all-formats-no-default-format--2', $formats, 'filtered_html');
+    $this->assertOptions('edit-all-formats-no-default-format', $formats, 'filtered_html');
     $this->assertEnabledTextarea('edit-all-formats-default-value');
     // \Drupal\filter_test\Form\FilterTestFormatForm::buildForm() uses
     // 'filter_test' as the default value in this case.
-    $this->assertOptions('edit-all-formats-default-format--2', $formats, 'filter_test');
+    $this->assertOptions('edit-all-formats-default-format', $formats, 'filter_test');
     // If a missing format is given as default, non-admin users are presented
     // with a disabled textarea.
     $this->assertDisabledTextarea('edit-all-formats-default-missing-value');
@@ -153,7 +153,7 @@ protected function doFilterFormTestAsNonAdmin() {
     $this->assertEnabledTextarea('edit-restricted-formats-no-default-value');
     // The user only has access to the 'filter_test' format, so when no default
     // is given that is preselected and the text format select is hidden.
-    $this->assertNoSelect('edit-restricted-formats-no-default-format--2');
+    $this->assertNoSelect('edit-restricted-formats-no-default-format');
     // When the format that the user does not have access to is preselected, the
     // textarea should be disabled.
     $this->assertDisabledTextarea('edit-restricted-formats-default-value');
@@ -163,9 +163,9 @@ protected function doFilterFormTestAsNonAdmin() {
     // Test a text format element with a fixed format.
     // When there is only a single option there is no point in choosing.
     $this->assertEnabledTextarea('edit-single-format-no-default-value');
-    $this->assertNoSelect('edit-single-format-no-default-format--2');
+    $this->assertNoSelect('edit-single-format-no-default-format');
     $this->assertEnabledTextarea('edit-single-format-default-value');
-    $this->assertNoSelect('edit-single-format-default-format--2');
+    $this->assertNoSelect('edit-single-format-default-format');
     // If the select has a missing or disallowed format make sure the textarea
     // is disabled.
     $this->assertDisabledTextarea('edit-single-format-default-missing-value');
@@ -191,7 +191,7 @@ protected function assertNoSelect($id) {
   /**
    * Asserts that a select element has the correct options.
    *
-   * @param string $id
+   * @param string $drupal_selector
    *   The HTML ID of the select element.
    * @param array $expected_options
    *   An array of option values.
@@ -201,20 +201,20 @@ protected function assertNoSelect($id) {
    * @return bool
    *   TRUE if the assertion passed; FALSE otherwise.
    */
-  protected function assertOptions($id, array $expected_options, $selected) {
-    $select = $this->xpath('//select[@id=:id]', array(':id' => $id));
+  protected function assertOptions($drupal_selector, array $expected_options, $selected) {
+    $select = $this->xpath('//select[@data-drupal-selector=:data_drupal_selector]', array(':data_drupal_selector' => $drupal_selector));
     $select = reset($select);
     $passed = $this->assertTrue($select instanceof \SimpleXMLElement, SafeMarkup::format('Field @id exists.', array(
-      '@id' => $id,
+      '@id' => $drupal_selector,
     )));
 
     $found_options = $this->getAllOptions($select);
     foreach ($found_options as $found_key => $found_option) {
       $expected_key = array_search($found_option->attributes()->value, $expected_options);
       if ($expected_key !== FALSE) {
-        $this->pass(SafeMarkup::format('Option @option for field @id exists.', array(
+        $this->pass(SafeMarkup::format('Option @option for field @data_drupal_selector exists.', array(
           '@option' => $expected_options[$expected_key],
-          '@id' => $id,
+          '@data_drupal_selector' => $drupal_selector,
         )));
         unset($found_options[$found_key]);
         unset($expected_options[$expected_key]);
@@ -224,28 +224,28 @@ protected function assertOptions($id, array $expected_options, $selected) {
     // Make sure that all expected options were found and that there are no
     // unexpected options.
     foreach ($expected_options as $expected_option) {
-      $this->fail(SafeMarkup::format('Option @option for field @id exists.', array(
+      $this->fail(SafeMarkup::format('Option @option for field @data_drupal_selector exists.', array(
         '@option' => $expected_option,
-        '@id' => $id,
+        '@data_drupal_selector' => $drupal_selector,
       )));
       $passed = FALSE;
     }
     foreach ($found_options as $found_option) {
-      $this->fail(SafeMarkup::format('Option @option for field @id does not exist.', array(
+      $this->fail(SafeMarkup::format('Option @option for field @data_drupal_selector does not exist.', array(
         '@option' => $found_option->attributes()->value,
-        '@id' => $id,
+        '@data_drupal_selector' => $drupal_selector,
       )));
       $passed = FALSE;
     }
 
-    return $passed && $this->assertOptionSelected($id, $selected);
+    return $passed && $this->assertOptionSelected($drupal_selector, $selected);
   }
 
   /**
    * Asserts that there is a select element with the given ID that is required.
    *
-   * @param string $id
-   *   The HTML ID of the select element.
+   * @param string $drupal_selector
+   *   The HTML drupal_selector of the select element.
    * @param array $options
    *   An array of option values that are contained in the select element
    *   besides the "- Select -" option.
@@ -253,62 +253,62 @@ protected function assertOptions($id, array $expected_options, $selected) {
    * @return bool
    *   TRUE if the assertion passed; FALSE otherwise.
    */
-  protected function assertRequiredSelectAndOptions($id, array $options) {
-    $select = $this->xpath('//select[@id=:id and contains(@required, "required")]', array(
-      ':id' => $id,
+  protected function assertRequiredSelectAndOptions($drupal_selector, array $options) {
+    $select = $this->xpath('//select[@data-drupal-selector=:data_drupal_selector and contains(@required, "required")]', array(
+      ':data_drupal_selector' => $drupal_selector,
     ));
     $select = reset($select);
-    $passed = $this->assertTrue($select instanceof \SimpleXMLElement, SafeMarkup::format('Required field @id exists.', array(
-      '@id' => $id,
+    $passed = $this->assertTrue($select instanceof \SimpleXMLElement, SafeMarkup::format('Required field @data_drupal_selector exists.', array(
+      '@data_drupal_selector' => $drupal_selector,
     )));
     // A required select element has a "- Select -" option whose key is an empty
     // string.
     $options[] = '';
-    return $passed && $this->assertOptions($id, $options, '');
+    return $passed && $this->assertOptions($drupal_selector, $options, '');
   }
 
   /**
    * Asserts that a textarea with a given ID exists and is not disabled.
    *
-   * @param string $id
-   *   The HTML ID of the textarea.
+   * @param string $drupal_selector
+   *   The HTML drupal selector of the textarea.
    *
    * @return bool
    *   TRUE if the assertion passed; FALSE otherwise.
    */
-  protected function assertEnabledTextarea($id) {
-    $textarea = $this->xpath('//textarea[@id=:id and not(contains(@disabled, "disabled"))]', array(
-      ':id' => $id,
+  protected function assertEnabledTextarea($drupal_selector) {
+    $textarea = $this->xpath('//textarea[@data-drupal-selector=:data_drupal_selector and not(contains(@disabled, "disabled"))]', array(
+      ':data_drupal_selector' => $drupal_selector,
     ));
     $textarea = reset($textarea);
-    return $this->assertTrue($textarea instanceof \SimpleXMLElement, SafeMarkup::format('Enabled field @id exists.', array(
-      '@id' => $id,
+    return $this->assertTrue($textarea instanceof \SimpleXMLElement, SafeMarkup::format('Enabled field @data_drupal_selector exists.', array(
+      '@data_drupal_selector' => $drupal_selector,
     )));
   }
 
   /**
    * Asserts that a textarea with a given ID has been disabled from editing.
    *
-   * @param string $id
-   *   The HTML ID of the textarea.
+   * @param string $drupal_selector
+   *   The HTML drupal selector of the textarea.
    *
    * @return bool
    *   TRUE if the assertion passed; FALSE otherwise.
    */
-  protected function assertDisabledTextarea($id) {
-    $textarea = $this->xpath('//textarea[@id=:id and contains(@disabled, "disabled")]', array(
-      ':id' => $id,
+  protected function assertDisabledTextarea($drupal_selector) {
+    $textarea = $this->xpath('//textarea[@data-drupal-selector=:data_drupal_selector and contains(@disabled, "disabled")]', array(
+      ':data_drupal_selector' => $drupal_selector,
     ));
     $textarea = reset($textarea);
-    $passed = $this->assertTrue($textarea instanceof \SimpleXMLElement, SafeMarkup::format('Disabled field @id exists.', array(
-      '@id' => $id,
+    $passed = $this->assertTrue($textarea instanceof \SimpleXMLElement, SafeMarkup::format('Disabled field @data_drupal_selector exists.', array(
+      '@data_drupal_selector' => $drupal_selector,
     )));
     $expected = 'This field has been disabled because you do not have sufficient permissions to edit it.';
-    $passed = $passed && $this->assertEqual((string) $textarea, $expected, SafeMarkup::format('Disabled textarea @id hides text in an inaccessible text format.', array(
-      '@id' => $id,
+    $passed = $passed && $this->assertEqual((string) $textarea, $expected, SafeMarkup::format('Disabled textarea @data_drupal_selector hides text in an inaccessible text format.', array(
+      '@data_drupal_selector' => $drupal_selector,
     )));
     // Make sure the text format select is not shown.
-    $select_id = str_replace('value', 'format--2', $id);
+    $select_id = str_replace('value', 'format', $drupal_selector);
     return $passed && $this->assertNoSelect($select_id);
   }
 
diff --git a/core/modules/image/src/Tests/ImageFieldDisplayTest.php b/core/modules/image/src/Tests/ImageFieldDisplayTest.php
index 74b07ed..25d9781 100644
--- a/core/modules/image/src/Tests/ImageFieldDisplayTest.php
+++ b/core/modules/image/src/Tests/ImageFieldDisplayTest.php
@@ -251,15 +251,9 @@ function testImageFieldSettings() {
     $node_storage->resetCache(array($nid));
     $node = $node_storage->load($nid);
     $file = $node->{$field_name}->entity;
-    $image_style = array(
-      '#theme' => 'image_style',
-      '#uri' => $file->getFileUri(),
-      '#width' => 40,
-      '#height' => 20,
-      '#style_name' => 'medium',
-    );
-    $default_output = drupal_render($image_style);
-    $this->assertRaw($default_output, "Preview image is displayed using 'medium' style.");
+
+    $url = file_create_url(ImageStyle::load('medium')->buildUrl($file->getFileUri()));
+    $this->assertTrue($this->cssSelect('img[width=40][height=20][class=image-style-medium][src="' . $url . '"]'));
 
     // Add alt/title fields to the image and verify that they are displayed.
     $image = array(
@@ -317,8 +311,10 @@ function testImageFieldSettings() {
       'files[' . $field_name . '_2][]' => drupal_realpath($test_image->uri),
     );
     $this->drupalPostAjaxForm(NULL, $edit, $field_name . '_2_upload_button');
-    $this->assertNoRaw('<input multiple type="file" id="edit-' . strtr($field_name, '_', '-') . '-2-upload" name="files[' . $field_name . '_2][]" size="22" class="js-form-file form-file">');
-    $this->assertRaw('<input multiple type="file" id="edit-' . strtr($field_name, '_', '-') . '-3-upload" name="files[' . $field_name . '_3][]" size="22" class="js-form-file form-file">');
+    $result = $this->xpath('//input[@name="files[' . $field_name . '_2][]"]');
+    $this->assertEqual(0, count($result));
+    $result = $this->xpath('//input[@name="files[' . $field_name . '_3][]"]');
+    $this->assertEqual(1, count($result));
   }
 
   /**
diff --git a/core/modules/search/src/Tests/SearchBlockTest.php b/core/modules/search/src/Tests/SearchBlockTest.php
index bf18056..b3f3615 100644
--- a/core/modules/search/src/Tests/SearchBlockTest.php
+++ b/core/modules/search/src/Tests/SearchBlockTest.php
@@ -100,7 +100,7 @@ public function testSearchFormBlock() {
     // Same test again, using the search page form for the second search this
     // time.
     $this->submitGetForm('node', array('keys' => $this->randomMachineName(1)), t('Search'));
-    $this->drupalPostForm(NULL, array('keys' => $this->randomMachineName()), t('Search'), array(), array(), 'search-form');
+    $this->drupalPostForm(NULL, array('keys' => $this->randomMachineName()), t('Search'), array(), array(), '//form[@data-drupal-selector="search-form"]');
     $this->assertNoText('You must include at least one positive keyword', 'Keyword message is not displayed when searching for long word after short word search');
 
   }
diff --git a/core/modules/simpletest/src/AssertContentTrait.php b/core/modules/simpletest/src/AssertContentTrait.php
index 240aea9..d746fb5 100644
--- a/core/modules/simpletest/src/AssertContentTrait.php
+++ b/core/modules/simpletest/src/AssertContentTrait.php
@@ -822,14 +822,13 @@ protected function assertThemeOutput($callback, array $variables = array(), $exp
   }
 
   /**
-   * Asserts that a field exists in the current page by the given XPath.
+   * Asserts that a field exists in the current page with a given Xpath result.
    *
-   * @param string $xpath
-   *   XPath used to find the field.
+   * @param \SimpleXmlElement[] $fields
+   *   Xml elements.
    * @param string $value
-   *   (optional) Value of the field to assert. You may pass in NULL (default)
-   *   to skip checking the actual value, while still checking that the field
-   *   exists.
+   *   (optional) Value of the field to assert. You may pass in NULL (default) to skip
+   *   checking the actual value, while still checking that the field exists.
    * @param string $message
    *   (optional) A message to display with the assertion. Do not translate
    *   messages: use format_string() to embed variables in the message text, not
@@ -843,9 +842,7 @@ protected function assertThemeOutput($callback, array $variables = array(), $exp
    * @return bool
    *   TRUE on pass, FALSE on fail.
    */
-  protected function assertFieldByXPath($xpath, $value = NULL, $message = '', $group = 'Other') {
-    $fields = $this->xpath($xpath);
-
+  protected function assertFieldsByValue($fields, $value = NULL, $message = '', $group = 'Other') {
     // If value specified then check array for match.
     $found = TRUE;
     if (isset($value)) {
@@ -881,6 +878,34 @@ protected function assertFieldByXPath($xpath, $value = NULL, $message = '', $gro
   }
 
   /**
+   * Asserts that a field exists in the current page by the given XPath.
+   *
+   * @param string $xpath
+   *   XPath used to find the field.
+   * @param string $value
+   *   (optional) Value of the field to assert. You may pass in NULL (default)
+   *   to skip checking the actual value, while still checking that the field
+   *   exists.
+   * @param string $message
+   *   (optional) A message to display with the assertion. Do not translate
+   *   messages: use format_string() to embed variables in the message text, not
+   *   t(). If left blank, a default message will be displayed.
+   * @param string $group
+   *   (optional) The group this message is in, which is displayed in a column
+   *   in test output. Use 'Debug' to indicate this is debugging output. Do not
+   *   translate this string. Defaults to 'Other'; most tests do not override
+   *   this default.
+   *
+   * @return bool
+   *   TRUE on pass, FALSE on fail.
+   */
+  protected function assertFieldByXPath($xpath, $value = NULL, $message = '', $group = 'Other') {
+    $fields = $this->xpath($xpath);
+
+    return $this->assertFieldsByValue($fields, $value, $message, $group);
+  }
+
+  /**
    * Get the selected value from a select field.
    *
    * @param \SimpleXmlElement $element
@@ -1065,8 +1090,8 @@ protected function assertNoFieldById($id, $value = '', $message = '', $group = '
   /**
    * Asserts that a checkbox field in the current page is checked.
    *
-   * @param string $id
-   *   ID of field to assert.
+   * @param string $drupal_selector
+   *   Drupal data selector of field to assert.
    * @param string $message
    *   (optional) A message to display with the assertion. Do not translate
    *   messages: use format_string() to embed variables in the message text, not
@@ -1080,15 +1105,15 @@ protected function assertNoFieldById($id, $value = '', $message = '', $group = '
    * @return bool
    *   TRUE on pass, FALSE on fail.
    */
-  protected function assertFieldChecked($id, $message = '', $group = 'Browser') {
-    $elements = $this->xpath('//input[@id=:id]', array(':id' => $id));
-    return $this->assertTrue(isset($elements[0]) && !empty($elements[0]['checked']), $message ? $message : SafeMarkup::format('Checkbox field @id is checked.', array('@id' => $id)), $group);
+  protected function assertFieldChecked($drupal_selector, $message = '', $group = 'Browser') {
+    $elements = $this->xpath('//input[@data-drupal-selector=:data_drupal_selector]', array(':data_drupal_selector' => $drupal_selector));
+    return $this->assertTrue(isset($elements[0]) && !empty($elements[0]['checked']), $message ? $message : SafeMarkup::format('Checkbox field @id is checked.', array('@id' => $drupal_selector)), $group);
   }
 
   /**
    * Asserts that a checkbox field in the current page is not checked.
    *
-   * @param string $id
+   * @param string $drupal_selector
    *   ID of field to assert.
    * @param string $message
    *   (optional) A message to display with the assertion. Do not translate
@@ -1103,15 +1128,15 @@ protected function assertFieldChecked($id, $message = '', $group = 'Browser') {
    * @return bool
    *   TRUE on pass, FALSE on fail.
    */
-  protected function assertNoFieldChecked($id, $message = '', $group = 'Browser') {
-    $elements = $this->xpath('//input[@id=:id]', array(':id' => $id));
-    return $this->assertTrue(isset($elements[0]) && empty($elements[0]['checked']), $message ? $message : SafeMarkup::format('Checkbox field @id is not checked.', array('@id' => $id)), $group);
+  protected function assertNoFieldChecked($drupal_selector, $message = '', $group = 'Browser') {
+    $elements = $this->xpath('//input[@data-drupal-selector=:data_drupal_selector]', array(':data_drupal_selector' => $drupal_selector));
+    return $this->assertTrue(isset($elements[0]) && empty($elements[0]['checked']), $message ? $message : SafeMarkup::format('Checkbox field @id is not checked.', array('@id' => $drupal_selector)), $group);
   }
 
   /**
    * Asserts that a select option in the current page exists.
    *
-   * @param string $id
+   * @param string $drupal_selector
    *   ID of select field to assert.
    * @param string $option
    *   Option to assert.
@@ -1128,15 +1153,15 @@ protected function assertNoFieldChecked($id, $message = '', $group = 'Browser')
    * @return bool
    *   TRUE on pass, FALSE on fail.
    */
-  protected function assertOption($id, $option, $message = '', $group = 'Browser') {
-    $options = $this->xpath('//select[@id=:id]//option[@value=:option]', array(':id' => $id, ':option' => $option));
-    return $this->assertTrue(isset($options[0]), $message ? $message : SafeMarkup::format('Option @option for field @id exists.', array('@option' => $option, '@id' => $id)), $group);
+  protected function assertOption($drupal_selector, $option, $message = '', $group = 'Browser') {
+    $options = $this->xpath('//select[@data-drupal-selector=:data_drupal_selector]//option[@value=:option]', array(':data_drupal_selector' => $drupal_selector, ':option' => $option));
+    return $this->assertTrue(isset($options[0]), $message ? $message : SafeMarkup::format('Option @option for field @id exists.', array('@option' => $option, '@id' => $drupal_selector)), $group);
   }
 
   /**
    * Asserts that a select option in the current page does not exist.
    *
-   * @param string $id
+   * @param string $drupal_selector
    *   ID of select field to assert.
    * @param string $option
    *   Option to assert.
@@ -1153,16 +1178,16 @@ protected function assertOption($id, $option, $message = '', $group = 'Browser')
    * @return bool
    *   TRUE on pass, FALSE on fail.
    */
-  protected function assertNoOption($id, $option, $message = '', $group = 'Browser') {
-    $selects = $this->xpath('//select[@id=:id]', array(':id' => $id));
-    $options = $this->xpath('//select[@id=:id]//option[@value=:option]', array(':id' => $id, ':option' => $option));
-    return $this->assertTrue(isset($selects[0]) && !isset($options[0]), $message ? $message : SafeMarkup::format('Option @option for field @id does not exist.', array('@option' => $option, '@id' => $id)), $group);
+  protected function assertNoOption($drupal_selector, $option, $message = '', $group = 'Browser') {
+    $selects = $this->xpath('//select[@data-drupal-selector=:data_drupal_selector]', array(':data_drupal_selector' => $drupal_selector));
+    $options = $this->xpath('//select[@data-drupal-selector=:data_drupal_selector]//option[@value=:option]', array(':data_drupal_selector' => $drupal_selector, ':option' => $option));
+    return $this->assertTrue(isset($selects[0]) && !isset($options[0]), $message ? $message : SafeMarkup::format('Option @option for field @id does not exist.', array('@option' => $option, '@id' => $drupal_selector)), $group);
   }
 
   /**
    * Asserts that a select option in the current page is checked.
    *
-   * @param string $id
+   * @param string $drupal_selector
    *   ID of select field to assert.
    * @param string $option
    *   Option to assert.
@@ -1181,15 +1206,15 @@ protected function assertNoOption($id, $option, $message = '', $group = 'Browser
    *
    * @todo $id is unusable. Replace with $name.
    */
-  protected function assertOptionSelected($id, $option, $message = '', $group = 'Browser') {
-    $elements = $this->xpath('//select[@id=:id]//option[@value=:option]', array(':id' => $id, ':option' => $option));
-    return $this->assertTrue(isset($elements[0]) && !empty($elements[0]['selected']), $message ? $message : SafeMarkup::format('Option @option for field @id is selected.', array('@option' => $option, '@id' => $id)), $group);
+  protected function assertOptionSelected($drupal_selector, $option, $message = '', $group = 'Browser') {
+    $elements = $this->xpath('//select[@data-drupal-selector=:data_drupal_selector]//option[@value=:option]', array(':data_drupal_selector' => $drupal_selector, ':option' => $option));
+    return $this->assertTrue(isset($elements[0]) && !empty($elements[0]['selected']), $message ? $message : SafeMarkup::format('Option @option for field @id is selected.', array('@option' => $option, '@id' => $drupal_selector)), $group);
   }
 
   /**
    * Asserts that a select option in the current page is not checked.
    *
-   * @param string $id
+   * @param string $drupal_selector
    *   ID of select field to assert.
    * @param string $option
    *   Option to assert.
@@ -1206,16 +1231,16 @@ protected function assertOptionSelected($id, $option, $message = '', $group = 'B
    * @return bool
    *   TRUE on pass, FALSE on fail.
    */
-  protected function assertNoOptionSelected($id, $option, $message = '', $group = 'Browser') {
-    $elements = $this->xpath('//select[@id=:id]//option[@value=:option]', array(':id' => $id, ':option' => $option));
-    return $this->assertTrue(isset($elements[0]) && empty($elements[0]['selected']), $message ? $message : SafeMarkup::format('Option @option for field @id is not selected.', array('@option' => $option, '@id' => $id)), $group);
+  protected function assertNoOptionSelected($drupal_selector, $option, $message = '', $group = 'Browser') {
+    $elements = $this->xpath('//select[@data-drupal-selector=:data_drupal_selector]//option[@value=:option]', array(':data_drupal_selector' => $drupal_selector, ':option' => $option));
+    return $this->assertTrue(isset($elements[0]) && empty($elements[0]['selected']), $message ? $message : SafeMarkup::format('Option @option for field @id is not selected.', array('@option' => $option, '@id' => $drupal_selector)), $group);
   }
 
   /**
-   * Asserts that a field exists with the given name or ID.
+   * Asserts that a field exists with the given name or ID or drupal-selector.
    *
    * @param string $field
-   *   Name or ID of field to assert.
+   *   Name or ID or data-drupal-selector of field to assert.
    * @param string $message
    *   (optional) A message to display with the assertion. Do not translate
    *   messages: use format_string() to embed variables in the message text, not
@@ -1230,14 +1255,14 @@ protected function assertNoOptionSelected($id, $option, $message = '', $group =
    *   TRUE on pass, FALSE on fail.
    */
   protected function assertField($field, $message = '', $group = 'Other') {
-    return $this->assertFieldByXPath($this->constructFieldXpath('name', $field) . '|' . $this->constructFieldXpath('id', $field), NULL, $message, $group);
+    return $this->assertFieldByXPath($this->constructFieldXpath('name', $field) . '|' . $this->constructFieldXpath('id', $field) . '|' . $this->constructFieldXpath('data-drupal-selector', $field), NULL, $message, $group);
   }
 
   /**
-   * Asserts that a field does not exist with the given name or ID.
+   * Asserts that a field does not exist with the given name or ID or data-drupal-selector.
    *
    * @param string $field
-   *   Name or ID of field to assert.
+   *   Name or ID or data-drupal-selector of field to assert.
    * @param string $message
    *   (optional) A message to display with the assertion. Do not translate
    *   messages: use format_string() to embed variables in the message text, not
@@ -1252,7 +1277,7 @@ protected function assertField($field, $message = '', $group = 'Other') {
    *   TRUE on pass, FALSE on fail.
    */
   protected function assertNoField($field, $message = '', $group = 'Other') {
-    return $this->assertNoFieldByXPath($this->constructFieldXpath('name', $field) . '|' . $this->constructFieldXpath('id', $field), NULL, $message, $group);
+    return $this->assertNoFieldByXPath($this->constructFieldXpath('name', $field) . '|' . $this->constructFieldXpath('id', $field) . '|' . $this->constructFieldXpath('data-drupal-selector', $field), NULL, $message, $group);
   }
 
   /**
diff --git a/core/modules/simpletest/src/WebTestBase.php b/core/modules/simpletest/src/WebTestBase.php
index 3affb15..884255c 100644
--- a/core/modules/simpletest/src/WebTestBase.php
+++ b/core/modules/simpletest/src/WebTestBase.php
@@ -19,6 +19,7 @@
 use Drupal\Core\Database\Database;
 use Drupal\Core\DrupalKernel;
 use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\EventSubscriber\AjaxSubscriber;
 use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
 use Drupal\Core\Extension\MissingDependencyException;
 use Drupal\Core\Render\Element;
@@ -1611,13 +1612,10 @@ protected function drupalGetAjax($path, array $options = array(), array $headers
    * @param $headers
    *   An array containing additional HTTP request headers, each formatted as
    *   "name: value".
-   * @param $form_html_id
-   *   (optional) HTML ID of the form to be submitted. On some pages
+   * @param $xpath
+   *   (optional) The xpath selector of the form to be submitted. On some pages
    *   there are many identical forms, so just using the value of the submit
    *   button is not enough. For example: 'trigger-node-presave-assign-form'.
-   *   Note that this is not the Drupal $form_id, but rather the HTML ID of the
-   *   form, which is typically the same thing but with hyphens replacing the
-   *   underscores.
    * @param $extra_post
    *   (optional) A string of additional data to append to the POST submission.
    *   This can be used to add POST data for which there are no HTML fields, as
@@ -1625,7 +1623,7 @@ protected function drupalGetAjax($path, array $options = array(), array $headers
    *   POST data, so it must already be urlencoded and contain a leading "&"
    *   (e.g., "&extra_var1=hello+world&extra_var2=you%26me").
    */
-  protected function drupalPostForm($path, $edit, $submit, array $options = array(), array $headers = array(), $form_html_id = NULL, $extra_post = NULL) {
+  protected function drupalPostForm($path, $edit, $submit, array $options = array(), array $headers = array(), $xpath = NULL, $extra_post = NULL) {
     $submit_matches = FALSE;
     $ajax = is_array($submit);
     if (isset($path)) {
@@ -1635,10 +1633,7 @@ protected function drupalPostForm($path, $edit, $submit, array $options = array(
     if ($this->parse()) {
       $edit_save = $edit;
       // Let's iterate over all the forms.
-      $xpath = "//form";
-      if (!empty($form_html_id)) {
-        $xpath .= "[@id='" . $form_html_id . "']";
-      }
+      $xpath = $xpath ?: '//form';
       $forms = $this->xpath($xpath);
       foreach ($forms as $form) {
         // We try to set the fields of this form as specified in $edit.
@@ -1741,11 +1736,10 @@ protected function drupalPostForm($path, $edit, $submit, array $options = array(
    * @param $headers
    *   (optional) An array containing additional HTTP request headers, each
    *   formatted as "name: value". Forwarded to drupalPostForm().
-   * @param $form_html_id
-   *   (optional) HTML ID of the form to be submitted, use when there is more
-   *   than one identical form on the same page and the value of the triggering
-   *   element is not enough to identify the form. Note this is not the Drupal
-   *   ID of the form but rather the HTML ID of the form.
+   * @param $form_xpath
+   *   (optional) The xpath selector of the form to be submitted. On some pages
+   *   there are many identical forms, so just using the value of the submit
+   *   button is not enough. For example: 'trigger-node-presave-assign-form'.
    * @param $ajax_settings
    *   (optional) An array of Ajax settings which if specified will be used in
    *   place of the Ajax settings of the triggering element.
@@ -1757,7 +1751,7 @@ protected function drupalPostForm($path, $edit, $submit, array $options = array(
    * @see drupalProcessAjaxResponse()
    * @see ajax.js
    */
-  protected function drupalPostAjaxForm($path, $edit, $triggering_element, $ajax_path = NULL, array $options = array(), array $headers = array(), $form_html_id = NULL, $ajax_settings = NULL) {
+  protected function drupalPostAjaxForm($path, $edit, $triggering_element, $ajax_path = NULL, array $options = array(), array $headers = array(), $form_xpath = NULL, $ajax_settings = NULL) {
 
     // Get the content of the initial page prior to calling drupalPostForm(),
     // since drupalPostForm() replaces $this->content.
@@ -1785,8 +1779,8 @@ protected function drupalPostAjaxForm($path, $edit, $triggering_element, $ajax_p
       else {
         $xpath = '//*[@name="' . $triggering_element . '"]';
       }
-      if (isset($form_html_id)) {
-        $xpath = '//form[@id="' . $form_html_id . '"]' . $xpath;
+      if (isset($form_xpath)) {
+        $xpath = $form_xpath . $xpath;
       }
       $element = $this->xpath($xpath);
       $element_id = (string) $element[0]['id'];
@@ -1800,13 +1794,6 @@ protected function drupalPostAjaxForm($path, $edit, $triggering_element, $ajax_p
         $extra_post[$key] = $value;
       }
     }
-    $ajax_html_ids = array();
-    foreach ($this->xpath('//*[@id]') as $element) {
-      $ajax_html_ids[] = (string) $element['id'];
-    }
-    if (!empty($ajax_html_ids)) {
-      $extra_post['ajax_html_ids'] = implode(' ', $ajax_html_ids);
-    }
     $extra_post += $this->getAjaxPageStatePostData();
     // Now serialize all the $extra_post values, and prepend it with an '&'.
     $extra_post = '&' . $this->serializePostValues($extra_post);
@@ -1836,7 +1823,7 @@ protected function drupalPostAjaxForm($path, $edit, $triggering_element, $ajax_p
     $ajax_path = $this->container->get('unrouted_url_assembler')->assemble('base://' . $ajax_path, $options);
 
     // Submit the POST request.
-    $return = Json::decode($this->drupalPostForm(NULL, $edit, array('path' => $ajax_path, 'triggering_element' => $triggering_element), $options, $headers, $form_html_id, $extra_post));
+    $return = Json::decode($this->drupalPostForm(NULL, $edit, array('path' => $ajax_path, 'triggering_element' => $triggering_element), $options, $headers, $form_xpath, $extra_post));
 
     // Change the page content by applying the returned commands.
     if (!empty($ajax_settings) && !empty($return)) {
diff --git a/core/modules/system/src/Tests/Ajax/DialogTest.php b/core/modules/system/src/Tests/Ajax/DialogTest.php
index e290977..f34bcf3 100644
--- a/core/modules/system/src/Tests/Ajax/DialogTest.php
+++ b/core/modules/system/src/Tests/Ajax/DialogTest.php
@@ -124,6 +124,10 @@ public function testDialog() {
       // Don't send a target.
       'submit' => array()
     ));
+    // Make sure the selector ID starts with the right string.
+    $this->assert(strpos($ajax_result[3]['selector'], $no_target_expected_response['selector']) === 0, 'Selector starts with right string.');
+    unset($ajax_result[3]['selector']);
+    unset($no_target_expected_response['selector']);
     $this->assertEqual($no_target_expected_response, $ajax_result[3], 'Normal dialog with no target JSON response matches.');
 
     // Emulate closing the dialog via an AJAX request. There is no non-JS
@@ -162,22 +166,23 @@ public function testDialog() {
     // Emulate going to the JS version of the form and check the JSON response.
     $ajax_result = $this->drupalGetAjax('ajax-test/dialog-form', array('query' => array(MainContentViewSubscriber::WRAPPER_FORMAT => 'drupal_modal')));
     $expected_ajax_settings = [
-      'edit-preview' => [
-        'callback' => '::preview',
-        'event' => 'click',
-        'url' => Url::fromRoute('system.ajax')->toString(),
-        'dialogType' => 'ajax',
-        'submit' => [
-          '_triggering_element_name' => 'op',
-          '_triggering_element_value' => 'Preview',
-        ],
+      'callback' => '::preview',
+      'event' => 'click',
+      'url' => Url::fromRoute('system.ajax')->toString(),
+      'dialogType' => 'ajax',
+      'submit' => [
+        '_triggering_element_name' => 'op',
+        '_triggering_element_value' => 'Preview',
       ],
     ];
-    $this->assertEqual($expected_ajax_settings, $ajax_result[0]['settings']['ajax']);
+    $ajax_result_0_settings = $ajax_result[0]['settings']['ajax'];
+    $this->assertTrue(strpos(key($ajax_result_0_settings), 'edit-preview--') === 0, 'Ensure that the used HTML ID is starting with edit-preview');
+
+    $this->assertEqual($expected_ajax_settings, reset($ajax_result_0_settings));
     $this->setRawContent($ajax_result[3]['data']);
     // Remove the data, the form build id and token will never match.
     unset($ajax_result[3]['data']);
-    $form = $this->xpath("//form[@id='ajax-test-form']");
+    $form = $this->xpath("//form[starts-with(@id, 'ajax-test-form')]");
     $this->assertTrue(!empty($form), 'Modal dialog JSON contains form.');
     $this->assertEqual($form_expected_response, $ajax_result[3]);
 
@@ -193,7 +198,7 @@ public function testDialog() {
     $this->setRawContent($ajax_result[3]['data']);
     // Remove the data, the form build id and token will never match.
     unset($ajax_result[3]['data']);
-    $form = $this->xpath("//form[@id='contact-form-add-form']");
+    $form = $this->xpath("//form[starts-with(@id, 'contact-form-add-form')]");
     $this->assertTrue(!empty($form), 'Modal dialog JSON contains entity form.');
     $this->assertEqual($entity_form_expected_response, $ajax_result[3]);
   }
diff --git a/core/modules/system/src/Tests/Ajax/MultiFormTest.php b/core/modules/system/src/Tests/Ajax/MultiFormTest.php
index 449861c..c9c6124 100644
--- a/core/modules/system/src/Tests/Ajax/MultiFormTest.php
+++ b/core/modules/system/src/Tests/Ajax/MultiFormTest.php
@@ -58,10 +58,9 @@ function testMultiForm() {
     // each Ajax submission, but these variables are stable and help target the
     // desired elements.
     $field_name = 'field_ajax_test';
-    $field_xpaths = array(
-      'node-page-form' => '//form[@id="node-page-form"]//div[contains(@class, "field-name-field-ajax-test")]',
-      'node-page-form--2' => '//form[@id="node-page-form--2"]//div[contains(@class, "field-name-field-ajax-test")]',
-    );
+
+    $form_xpath = '//form[starts-with(@id, "node-page-form")]';
+    $field_xpath = '//div[contains(@class, "field-name-field-ajax-test")]';
     $button_name = $field_name . '_add_more';
     $button_value = t('Add another item');
     $button_xpath_suffix = '//input[@name="' . $button_name . '"]';
@@ -71,19 +70,28 @@ function testMultiForm() {
     // of field items and "add more" button for the multi-valued field within
     // each form.
     $this->drupalGet('form-test/two-instances-of-same-form');
-    foreach ($field_xpaths as $field_xpath) {
-      $this->assert(count($this->xpath($field_xpath . $field_items_xpath_suffix)) == 1, 'Found the correct number of field items on the initial page.');
-      $this->assertFieldByXPath($field_xpath . $button_xpath_suffix, NULL, 'Found the "add more" button on the initial page.');
+
+    $fields = $this->xpath($form_xpath . $field_xpath);
+    $this->assertEqual(count($fields), 2);
+    foreach ($fields as $field) {
+      $this->assertEqual(count($field->xpath('.' . $field_items_xpath_suffix)), 1, 'Found the correct number of field items on the initial page.');
+      $this->assertFieldsByValue($field->xpath('.' . $button_xpath_suffix), NULL, 'Found the "add more" button on the initial page.');
     }
+
     $this->assertNoDuplicateIds(t('Initial page contains unique IDs'), 'Other');
 
     // Submit the "add more" button of each form twice. After each corresponding
     // page update, ensure the same as above.
-    foreach ($field_xpaths as $form_html_id => $field_xpath) {
-      for ($i = 0; $i < 2; $i++) {
-        $this->drupalPostAjaxForm(NULL, array(), array($button_name => $button_value), 'system/ajax', array(), array(), $form_html_id);
-        $this->assert(count($this->xpath($field_xpath . $field_items_xpath_suffix)) == $i+2, 'Found the correct number of field items after an AJAX submission.');
-        $this->assertFieldByXPath($field_xpath . $button_xpath_suffix, NULL, 'Found the "add more" button after an AJAX submission.');
+    for ($i = 0; $i < 2; $i++) {
+      $forms = $this->xpath($form_xpath);
+      foreach ($forms as $offset => $form) {
+        $form_selector = '//form[@data-drupal-selector="' . (string) $form['data-drupal-selector'] . '"]';
+        $this->drupalPostAjaxForm(NULL, array(), array($button_name => $button_value), 'system/ajax', array(), array(), $form_selector);
+        $form = $this->xpath($form_xpath)[$offset];
+        $field = $form->xpath('.' . $field_xpath);
+
+        $this->assertEqual(count($field[0]->xpath('.' . $field_items_xpath_suffix)), $i+2, 'Found the correct number of field items after an AJAX submission.');
+        $this->assertFieldsByValue($field[0]->xpath('.' . $button_xpath_suffix), NULL, 'Found the "add more" button after an AJAX submission.');
         $this->assertNoDuplicateIds(t('Updated page contains unique IDs'), 'Other');
       }
     }
diff --git a/core/modules/system/src/Tests/Form/RebuildTest.php b/core/modules/system/src/Tests/Form/RebuildTest.php
index e993b49..1be5fd7 100644
--- a/core/modules/system/src/Tests/Form/RebuildTest.php
+++ b/core/modules/system/src/Tests/Form/RebuildTest.php
@@ -96,7 +96,7 @@ function testPreserveFormActionAfterAJAX() {
     // submission and verify it worked by ensuring the updated page has two text
     // field items in the field for which we just added an item.
     $this->drupalGet('node/add/page');
-    $this->drupalPostAjaxForm(NULL, array(), array('field_ajax_test_add_more' => t('Add another item')), 'system/ajax', array(), array(), 'node-page-form');
+    $this->drupalPostAjaxForm(NULL, array(), array('field_ajax_test_add_more' => t('Add another item')), 'system/ajax', array(), array(), '//form[@data-drupal-selector="node-page-form"]');
     $this->assert(count($this->xpath('//div[contains(@class, "field-name-field-ajax-test")]//input[@type="text"]')) == 2, 'AJAX submission succeeded.');
 
     // Submit the form with the non-Ajax "Save" button, leaving the title field
diff --git a/core/modules/system/src/Tests/Form/TriggeringElementTest.php b/core/modules/system/src/Tests/Form/TriggeringElementTest.php
index 24cc2d2..32b2d02 100644
--- a/core/modules/system/src/Tests/Form/TriggeringElementTest.php
+++ b/core/modules/system/src/Tests/Form/TriggeringElementTest.php
@@ -31,26 +31,26 @@ class TriggeringElementTest extends WebTestBase {
   function testNoButtonInfoInPost() {
     $path = 'form-test/clicked-button';
     $edit = array();
-    $form_html_id = 'form-test-clicked-button';
+    $drupal_selector = '//form[@data-drupal-selector="form-test-clicked-button"]';
 
     // Ensure submitting a form with no buttons results in no triggering element
     // and the form submit handler not running.
-    $this->drupalPostForm($path, $edit, NULL, array(), array(), $form_html_id);
+    $this->drupalPostForm($path, $edit, NULL, array(), array(), $drupal_selector);
     $this->assertText('There is no clicked button.', '$form_state->getTriggeringElement() set to NULL.');
     $this->assertNoText('Submit handler for form_test_clicked_button executed.', 'Form submit handler did not execute.');
 
     // Ensure submitting a form with one or more submit buttons results in the
     // triggering element being set to the first one the user has access to. An
     // argument with 'r' in it indicates a restricted (#access=FALSE) button.
-    $this->drupalPostForm($path . '/s', $edit, NULL, array(), array(), $form_html_id);
+    $this->drupalPostForm($path . '/s', $edit, NULL, array(), array(), $drupal_selector);
     $this->assertText('The clicked button is button1.', '$form_state->getTriggeringElement() set to only button.');
     $this->assertText('Submit handler for form_test_clicked_button executed.', 'Form submit handler executed.');
 
-    $this->drupalPostForm($path . '/s/s', $edit, NULL, array(), array(), $form_html_id);
+    $this->drupalPostForm($path . '/s/s', $edit, NULL, array(), array(), $drupal_selector);
     $this->assertText('The clicked button is button1.', '$form_state->getTriggeringElement() set to first button.');
     $this->assertText('Submit handler for form_test_clicked_button executed.', 'Form submit handler executed.');
 
-    $this->drupalPostForm($path . '/rs/s', $edit, NULL, array(), array(), $form_html_id);
+    $this->drupalPostForm($path . '/rs/s', $edit, NULL, array(), array(), $drupal_selector);
     $this->assertText('The clicked button is button2.', '$form_state->getTriggeringElement() set to first available button.');
     $this->assertText('Submit handler for form_test_clicked_button executed.', 'Form submit handler executed.');
 
@@ -58,15 +58,15 @@ function testNoButtonInfoInPost() {
     // triggering element being set to the first button, regardless of type. For
     // the FAPI 'button' type, this should result in the submit handler not
     // executing. The types are 's'(ubmit), 'b'(utton), and 'i'(mage_button).
-    $this->drupalPostForm($path . '/s/b/i', $edit, NULL, array(), array(), $form_html_id);
+    $this->drupalPostForm($path . '/s/b/i', $edit, NULL, array(), array(), $drupal_selector);
     $this->assertText('The clicked button is button1.', '$form_state->getTriggeringElement() set to first button.');
     $this->assertText('Submit handler for form_test_clicked_button executed.', 'Form submit handler executed.');
 
-    $this->drupalPostForm($path . '/b/s/i', $edit, NULL, array(), array(), $form_html_id);
+    $this->drupalPostForm($path . '/b/s/i', $edit, NULL, array(), array(), $drupal_selector);
     $this->assertText('The clicked button is button1.', '$form_state->getTriggeringElement() set to first button.');
     $this->assertNoText('Submit handler for form_test_clicked_button executed.', 'Form submit handler did not execute.');
 
-    $this->drupalPostForm($path . '/i/s/b', $edit, NULL, array(), array(), $form_html_id);
+    $this->drupalPostForm($path . '/i/s/b', $edit, NULL, array(), array(), $drupal_selector);
     $this->assertText('The clicked button is button1.', '$form_state->getTriggeringElement() set to first button.');
     $this->assertText('Submit handler for form_test_clicked_button executed.', 'Form submit handler executed.');
   }
@@ -77,7 +77,7 @@ function testNoButtonInfoInPost() {
    */
   function testAttemptAccessControlBypass() {
     $path = 'form-test/clicked-button';
-    $form_html_id = 'form-test-clicked-button';
+    $drupal_selector = '//form[@data-drupal-selector="form-test-clicked-button"]';
 
     // Retrieve a form where 'button1' has #access=FALSE and 'button2' doesn't.
     $this->drupalGet($path . '/rs/s');
@@ -87,9 +87,9 @@ function testAttemptAccessControlBypass() {
     // a little trickery here, to work around the safeguards in drupalPostForm(): by
     // renaming the text field that is in the form to 'button1', we can get the
     // data we want into \Drupal::request()->request.
-    $elements = $this->xpath('//form[@id="' . $form_html_id . '"]//input[@name="text"]');
+    $elements = $this->xpath($drupal_selector . '//input[@name="text"]');
     $elements[0]['name'] = 'button1';
-    $this->drupalPostForm(NULL, array('button1' => 'button1'), NULL, array(), array(), $form_html_id);
+    $this->drupalPostForm(NULL, array('button1' => 'button1'), NULL, array(), array(), $drupal_selector);
 
     // Ensure that the triggering element was not set to the restricted button.
     // Do this with both a negative and positive assertion, because negative
diff --git a/core/modules/views/src/Controller/ViewAjaxController.php b/core/modules/views/src/Controller/ViewAjaxController.php
index 2608344..e7d6bec 100644
--- a/core/modules/views/src/Controller/ViewAjaxController.php
+++ b/core/modules/views/src/Controller/ViewAjaxController.php
@@ -12,6 +12,8 @@
 use Drupal\Core\Ajax\ReplaceCommand;
 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
 use Drupal\Core\Entity\EntityStorageInterface;
+use Drupal\Core\EventSubscriber\AjaxSubscriber;
+use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
 use Drupal\Core\Path\CurrentPathStack;
 use Drupal\Core\Render\RendererInterface;
 use Drupal\Core\Routing\RedirectDestinationInterface;
@@ -133,7 +135,7 @@ public function ajaxView(Request $request) {
 
       // Remove all of this stuff from the query of the request so it doesn't
       // end up in pagers and tablesort URLs.
-      foreach (array('view_name', 'view_display_id', 'view_args', 'view_path', 'view_dom_id', 'pager_element', 'view_base_path', 'ajax_html_ids') as $key) {
+      foreach (array('view_name', 'view_display_id', 'view_args', 'view_path', 'view_dom_id', 'pager_element', 'view_base_path', MainContentViewSubscriber::WRAPPER_FORMAT) as $key) {
         $request->query->remove($key);
         $request->request->remove($key);
       }
diff --git a/core/modules/views/src/Tests/Plugin/ExposedFormTest.php b/core/modules/views/src/Tests/Plugin/ExposedFormTest.php
index 7e58113..97384e8 100644
--- a/core/modules/views/src/Tests/Plugin/ExposedFormTest.php
+++ b/core/modules/views/src/Tests/Plugin/ExposedFormTest.php
@@ -143,7 +143,7 @@ public function testExposedFormRender() {
     $expected_action = $view->display_handler->getUrlInfo()->toString();
     $this->assertFieldByXPath('//form/@action', $expected_action, 'The expected value for the action attribute was found.');
     // Make sure the description is shown.
-    $result = $this->xpath('//form//div[contains(@id, :id) and normalize-space(text())=:description]', array(':id' => 'edit-type--description', ':description' => t('Exposed description')));
+    $result = $this->xpath('//form//div[contains(@id, :id) and contains(@id, :id_suffix) and normalize-space(text())=:description]', array(':id' => 'edit-type--', ':id_suffix' => 'description', ':description' => t('Exposed description')));
     $this->assertEqual(count($result), 1, 'Filter description was found.');
   }
 
diff --git a/core/modules/views_ui/js/views-admin.js b/core/modules/views_ui/js/views-admin.js
index 5f1ce22..539c406 100644
--- a/core/modules/views_ui/js/views-admin.js
+++ b/core/modules/views_ui/js/views-admin.js
@@ -21,7 +21,7 @@
     attach: function () {
       // Only show the SQL rewrite warning when the user has chosen the
       // corresponding checkbox.
-      $('#edit-query-options-disable-sql-rewrite').on('click', function () {
+      $('[data-drupal-selector="edit-query-options-disable-sql-rewrite"]').on('click', function () {
         $('.sql-rewrite-warning').toggleClass('js-hide');
       });
     }
@@ -412,7 +412,7 @@
     /**
      * Add a keyup handler to the search box.
      */
-    this.$searchBox = this.$form.find('#edit-override-controls-options-search');
+    this.$searchBox = this.$form.find('[data-drupal-selector="edit-override-controls-options-search"]');
     this.$searchBox.on('keyup', $.proxy(this.handleKeyup, this));
 
     /**
@@ -950,25 +950,27 @@
    */
   Drupal.behaviors.viewsFilterConfigSelectAll = {
     attach: function (context) {
-      // Show the select all checkbox.
-      $(context).find('#views-ui-handler-form div.form-item-options-value-all').once('filterConfigSelectAll')
-        .show()
-        .find('input[type=checkbox]')
-        .on('click', function () {
-          var checked = $(this).is(':checked');
+      var $context = $(context);
+
+      var $selectAll = $context.find('.form-item-options-value-all').once('filterConfigSelectAll');
+      var $selectAllCheckbox = $selectAll.find('input[type=checkbox]');
+      var $checkboxes = $selectAll.closest('.form-checkboxes').find('.js-form-type-checkbox:not(.form-item-options-value-all) input[type="checkbox"]');
+
+      if ($selectAll.length) {
+         // Show the select all checkbox.
+        $selectAll.show();
+        $selectAllCheckbox.on('click', function () {
           // Update all checkbox beside the select all checkbox.
-          $(this).parents('.form-checkboxes').find('input[type=checkbox]').each(function () {
-            $(this).attr('checked', checked);
-          });
+          $checkboxes.prop('checked', $(this).is(':checked'));
         });
-      // Uncheck the select all checkbox if any of the others are unchecked.
-      $('#views-ui-handler-form').find('div.js-form-type-checkbox').not($('.form-item-options-value-all'))
-        .find('input[type=checkbox]')
-        .on('click', function () {
+
+        // Uncheck the select all checkbox if any of the others are unchecked.
+        $checkboxes.on('click', function () {
           if ($(this).is('checked') === false) {
-            $('#edit-options-value-all').prop('checked', false);
+            $selectAllCheckbox.prop('checked', false);
           }
         });
+      }
     }
   };
 
@@ -990,7 +992,7 @@
    */
   Drupal.behaviors.viewsUiCheckboxify = {
     attach: function (context, settings) {
-      var $buttons = $('#edit-options-expose-button-button, #edit-options-group-button-button').once('views-ui-checkboxify');
+      var $buttons = $('[data-drupal-selector="edit-options-expose-button-button"], [data-drupal-selector="edit-options-group-button-button"]').once('views-ui-checkboxify');
       var length = $buttons.length;
       var i;
       for (i = 0; i < length; i++) {
@@ -1068,7 +1070,7 @@
    */
   Drupal.behaviors.viewsUiOverrideSelect = {
     attach: function (context) {
-      $(context).find('#edit-override-dropdown').once('views-ui-override-button-text').each(function () {
+      $(context).find('[data-drupal-selector="edit-override-dropdown"]').once('views-ui-override-button-text').each(function () {
         // Closures! :(
         var $context = $(context);
         var $submit = $context.find('[id^=edit-submit]');
diff --git a/core/modules/views_ui/src/ViewUI.php b/core/modules/views_ui/src/ViewUI.php
index 6532689..d920553 100644
--- a/core/modules/views_ui/src/ViewUI.php
+++ b/core/modules/views_ui/src/ViewUI.php
@@ -12,6 +12,8 @@
 use Drupal\Component\Utility\Timer;
 use Drupal\Component\Utility\Xss;
 use Drupal\Core\Config\Entity\ThirdPartySettingsInterface;
+use Drupal\Core\EventSubscriber\AjaxSubscriber;
+use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Url;
 use Drupal\views\Views;
@@ -582,7 +584,7 @@ public function renderPreview($display_id, $args = array()) {
       // have some input in the query parameters, so we merge request() and
       // query() to ensure we get it all.
       $exposed_input = array_merge(\Drupal::request()->request->all(), \Drupal::request()->query->all());
-      foreach (array('view_name', 'view_display_id', 'view_args', 'view_path', 'view_dom_id', 'pager_element', 'view_base_path', 'ajax_html_ids', 'ajax_page_state', 'form_id', 'form_build_id', 'form_token') as $key) {
+      foreach (array('view_name', 'view_display_id', 'view_args', 'view_path', 'view_dom_id', 'pager_element', 'view_base_path', MainContentViewSubscriber::WRAPPER_FORMAT, 'ajax_page_state', 'form_id', 'form_build_id', 'form_token') as $key) {
         if (isset($exposed_input[$key])) {
           unset($exposed_input[$key]);
         }
diff --git a/core/tests/Drupal/Tests/Component/Utility/HtmlTest.php b/core/tests/Drupal/Tests/Component/Utility/HtmlTest.php
index 18b07f7..e8b827d 100644
--- a/core/tests/Drupal/Tests/Component/Utility/HtmlTest.php
+++ b/core/tests/Drupal/Tests/Component/Utility/HtmlTest.php
@@ -25,6 +25,9 @@ class HtmlTest extends UnitTestCase {
   protected function setUp() {
     parent::setUp();
 
+    // Ensure to cleanup state of eventual previous requests.
+    Html::setRandomIds(TRUE);
+
     $property = new \ReflectionProperty('Drupal\Component\Utility\Html', 'seenIdsInit');
     $property->setAccessible(TRUE);
     $property->setValue(NULL);
@@ -108,6 +111,7 @@ public function testHtmlClass() {
    * @covers ::getUniqueId
    */
   public function testHtmlGetUniqueId($expected, $source, $reset = FALSE) {
+    Html::setRandomIds(FALSE);
     if ($reset) {
       Html::resetSeenIds();
     }
@@ -143,19 +147,24 @@ public function providerTestHtmlGetUniqueId() {
    *   The expected result.
    * @param string $source
    *   The string being transformed to an ID.
-   * @param bool $reset
-   *   (optional) If TRUE, reset the list of seen IDs. Defaults to FALSE.
    *
    * @dataProvider providerTestHtmlGetUniqueIdWithAjaxIds
    *
    * @covers ::getUniqueId
    */
-  public function testHtmlGetUniqueIdWithAjaxIds($expected, $source, $reset = FALSE) {
-    if ($reset) {
-      Html::resetSeenIds();
+  public function testHtmlGetUniqueIdWithAjaxIds($expected, $source) {
+    $id = Html::getUniqueId($source);
+
+    // Note, we truncate two hyphens at the end.
+    // @see \Drupal\Component\Utility\Html::getId()
+    if (strpos($source, '--') !== FALSE) {
+      $random_suffix = substr($id, strlen($source) + 1);
     }
-    Html::setAjaxHtmlIds('test-unique-id1 test-unique-id2--3');
-    $this->assertSame($expected, Html::getUniqueId($source));
+    else {
+      $random_suffix = substr($id, strlen($source) + 2);
+    }
+    $expected = $expected . $random_suffix;
+    $this->assertSame($expected, $id);
   }
 
   /**
@@ -166,10 +175,11 @@ public function testHtmlGetUniqueIdWithAjaxIds($expected, $source, $reset = FALS
    */
   public function providerTestHtmlGetUniqueIdWithAjaxIds() {
     return array(
-      array('test-unique-id1--2', 'test-unique-id1', TRUE),
-      array('test-unique-id1--3', 'test-unique-id1'),
-      array('test-unique-id2--4', 'test-unique-id2', TRUE),
-      array('test-unique-id2--5', 'test-unique-id2'),
+      array('test-unique-id1--', 'test-unique-id1'),
+      // Note, we truncate two hyphens at the end.
+      // @see \Drupal\Component\Utility\Html::getId()
+      array('test-unique-id1---', 'test-unique-id1--'),
+      array('test-unique-id2--', 'test-unique-id2'),
     );
   }
 
@@ -186,6 +196,7 @@ public function providerTestHtmlGetUniqueIdWithAjaxIds() {
    * @covers ::getId
    */
   public function testHtmlGetId($expected, $source) {
+    Html::setRandomIds(FALSE);
     $this->assertSame($expected, Html::getId($source));
   }
 
diff --git a/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php b/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php
index 77c8763..5906069 100644
--- a/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php
+++ b/core/tests/Drupal/Tests/Core/Form/FormBuilderTest.php
@@ -7,6 +7,7 @@
 
 namespace Drupal\Tests\Core\Form;
 
+use Drupal\Component\Utility\Html;
 use Drupal\Core\DependencyInjection\ContainerInjectionInterface;
 use Drupal\Core\Form\EnforcedResponseException;
 use Drupal\Core\Form\FormInterface;
@@ -227,7 +228,7 @@ public function testGetFormWithClassString() {
 
     $form = $this->formBuilder->getForm($form_id);
     $this->assertFormElement($expected_form, $form, 'test');
-    $this->assertSame('test-form', $form['#id']);
+    $this->assertStringStartsWith('test-form', $form['#id']);
   }
 
   /**
@@ -257,7 +258,7 @@ public function testBuildFormWithClassString() {
 
     $form = $this->formBuilder->buildForm($form_id, $form_state);
     $this->assertFormElement($expected_form, $form, 'test');
-    $this->assertSame('test-form', $form['#id']);
+    $this->assertStringStartsWith('test-form', $form['#id']);
   }
 
   /**
@@ -372,11 +373,14 @@ public function testUniqueHtmlId() {
 
     $form_state = new FormState();
     $form = $this->simulateFormSubmission($form_id, $form_arg, $form_state);
-    $this->assertSame('test-form-id', $form['#id']);
+    $first_form_id = $form['#id'];
+    $this->assertStringStartsWith('test-form-id', $first_form_id);
 
     $form_state = new FormState();
     $form = $this->simulateFormSubmission($form_id, $form_arg, $form_state);
-    $this->assertSame('test-form-id--2', $form['#id']);
+    $second_form_id = $form['#id'];
+    $this->assertStringStartsWith('test-form-id', $second_form_id);
+    $this->assertNotEquals($first_form_id, $second_form_id);
   }
 
   /**
