diff --git a/core/modules/comment/src/Tests/CommentTestBase.php b/core/modules/comment/src/Tests/CommentTestBase.php
index c42d988..9ab9ec7 100644
--- a/core/modules/comment/src/Tests/CommentTestBase.php
+++ b/core/modules/comment/src/Tests/CommentTestBase.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\comment\Tests;
 
+@trigger_error('\Drupal\comment\Tests\CommentTestBase is deprecated in Drupal 8.4.0 and will be removed before Drupal 9.0.0. Instead, use \Drupal\Tests\comment\Functional\CommentTestBase', E_USER_DEPRECATED);
+
 use Drupal\comment\Entity\CommentType;
 use Drupal\comment\Entity\Comment;
 use Drupal\comment\CommentInterface;
@@ -12,6 +14,9 @@
 
 /**
  * Provides setup and helper methods for comment tests.
+ *
+ * @deprecated in Drupal 8.4.0 and will be removed before Drupal 9.0.0.
+ *   Use \Drupal\Tests\comment\Functional\CommentTestBase instead.
  */
 abstract class CommentTestBase extends WebTestBase {
 
diff --git a/core/modules/comment/src/Tests/Views/CommentTestBase.php b/core/modules/comment/src/Tests/Views/CommentTestBase.php
index d7bec04..1c70ebe 100644
--- a/core/modules/comment/src/Tests/Views/CommentTestBase.php
+++ b/core/modules/comment/src/Tests/Views/CommentTestBase.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\comment\Tests\Views;
 
+@trigger_error('\Drupal\comment\Tests\Views\CommentTestBase is deprecated in Drupal 8.4.0 and will be removed before Drupal 9.0.0. Instead, use \Drupal\Tests\comment\Functional\Views\CommentTestBase', E_USER_DEPRECATED);
+
 use Drupal\comment\Tests\CommentTestTrait;
 use Drupal\views\Tests\ViewTestBase;
 use Drupal\views\Tests\ViewTestData;
@@ -9,6 +11,9 @@
 
 /**
  * Tests the argument_comment_user_uid handler.
+ *
+ * @deprecated in Drupal 8.4.0 and will be removed before Drupal 9.0.0.
+ *   Use \Drupal\Tests\comment\Functional\Views\CommentTestBase instead.
  */
 abstract class CommentTestBase extends ViewTestBase {
 
diff --git a/core/modules/comment/src/Tests/CommentActionsTest.php b/core/modules/comment/tests/src/Functional/CommentActionsTest.php
similarity index 97%
rename from core/modules/comment/src/Tests/CommentActionsTest.php
rename to core/modules/comment/tests/src/Functional/CommentActionsTest.php
index 56d34e4..79786fc 100644
--- a/core/modules/comment/src/Tests/CommentActionsTest.php
+++ b/core/modules/comment/tests/src/Functional/CommentActionsTest.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Drupal\comment\Tests;
+namespace Drupal\Tests\comment\Functional;
 
 use Drupal\comment\Entity\Comment;
 use Drupal\system\Entity\Action;
diff --git a/core/modules/comment/src/Tests/CommentAdminTest.php b/core/modules/comment/tests/src/Functional/CommentAdminTest.php
similarity index 98%
rename from core/modules/comment/src/Tests/CommentAdminTest.php
rename to core/modules/comment/tests/src/Functional/CommentAdminTest.php
index 980ad30..c05bcb8 100644
--- a/core/modules/comment/src/Tests/CommentAdminTest.php
+++ b/core/modules/comment/tests/src/Functional/CommentAdminTest.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Drupal\comment\Tests;
+namespace Drupal\Tests\comment\Functional;
 
 use Drupal\user\RoleInterface;
 use Drupal\comment\Entity\Comment;
@@ -90,7 +90,7 @@ public function testApprovalAdminInterface() {
     ];
     $this->drupalPostForm(NULL, $edit, t('Update'));
     $this->assertText(t('Are you sure you want to delete these comments and all their children?'), 'Confirmation required.');
-    $this->drupalPostForm(NULL, $edit, t('Delete comments'));
+    $this->drupalPostForm(NULL, [], t('Delete comments'));
     $this->assertText(t('No comments available.'), 'All comments were deleted.');
     // Test message when no comments selected.
     $edit = [
diff --git a/core/modules/comment/src/Tests/CommentAnonymousTest.php b/core/modules/comment/tests/src/Functional/CommentAnonymousTest.php
similarity index 97%
rename from core/modules/comment/src/Tests/CommentAnonymousTest.php
rename to core/modules/comment/tests/src/Functional/CommentAnonymousTest.php
index d60c073..3f294c0 100644
--- a/core/modules/comment/src/Tests/CommentAnonymousTest.php
+++ b/core/modules/comment/tests/src/Functional/CommentAnonymousTest.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Drupal\comment\Tests;
+namespace Drupal\Tests\comment\Functional;
 
 use Drupal\user\RoleInterface;
 
@@ -43,7 +43,7 @@ public function testAnonymous() {
     $edit['comment_body[0][value]'] = $body;
     $this->drupalPostForm($this->node->urlInfo(), $edit, t('Preview'));
     // Cannot use assertRaw here since both title and body are in the form.
-    $preview = (string) $this->cssSelect('.preview')[0]->asXML();
+    $preview = (string) $this->getSession()->getPage()->find('css', '.preview')->getHtml();
     $this->assertTrue(strpos($preview, $title) !== FALSE, 'Anonymous user can preview comment title.');
     $this->assertTrue(strpos($preview, $body) !== FALSE, 'Anonymous user can preview comment body.');
 
@@ -56,7 +56,7 @@ public function testAnonymous() {
     $edit['comment_body[0][value]'] = $body;
     $this->drupalPostForm($this->node->urlInfo(), $edit, t('Preview'));
     // Cannot use assertRaw here since both title and body are in the form.
-    $preview = (string) $this->cssSelect('.preview')[0]->asXML();
+    $preview = (string) $this->getSession()->getPage()->find('css', '.preview')->getHtml();
     $this->assertTrue(strpos($preview, $title) !== FALSE, 'Anonymous user can preview comment title.');
     $this->assertTrue(strpos($preview, $body) !== FALSE, 'Anonymous user can preview comment body.');
     user_role_grant_permissions(RoleInterface::ANONYMOUS_ID, ['skip comment approval']);
diff --git a/core/modules/comment/src/Tests/CommentBlockTest.php b/core/modules/comment/tests/src/Functional/CommentBlockTest.php
similarity index 98%
rename from core/modules/comment/src/Tests/CommentBlockTest.php
rename to core/modules/comment/tests/src/Functional/CommentBlockTest.php
index 37cc5de..db7d1bd 100644
--- a/core/modules/comment/src/Tests/CommentBlockTest.php
+++ b/core/modules/comment/tests/src/Functional/CommentBlockTest.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Drupal\comment\Tests;
+namespace Drupal\Tests\comment\Functional;
 
 use Drupal\Component\Utility\SafeMarkup;
 use Drupal\user\RoleInterface;
diff --git a/core/modules/comment/src/Tests/CommentBookTest.php b/core/modules/comment/tests/src/Functional/CommentBookTest.php
similarity index 94%
rename from core/modules/comment/src/Tests/CommentBookTest.php
rename to core/modules/comment/tests/src/Functional/CommentBookTest.php
index 4afd07e..950ecaa 100644
--- a/core/modules/comment/src/Tests/CommentBookTest.php
+++ b/core/modules/comment/tests/src/Functional/CommentBookTest.php
@@ -1,10 +1,10 @@
 <?php
 
-namespace Drupal\comment\Tests;
+namespace Drupal\Tests\comment\Functional;
 
 use Drupal\comment\CommentInterface;
 use Drupal\node\Entity\Node;
-use Drupal\simpletest\WebTestBase;
+use Drupal\Tests\BrowserTestBase;
 use Drupal\comment\Entity\Comment;
 
 /**
@@ -12,7 +12,7 @@
  *
  * @group comment
  */
-class CommentBookTest extends WebTestBase {
+class CommentBookTest extends BrowserTestBase {
 
   use CommentTestTrait;
 
diff --git a/core/modules/comment/src/Tests/CommentFieldsTest.php b/core/modules/comment/tests/src/Functional/CommentFieldsTest.php
similarity index 99%
rename from core/modules/comment/src/Tests/CommentFieldsTest.php
rename to core/modules/comment/tests/src/Functional/CommentFieldsTest.php
index a463314..2df9973 100644
--- a/core/modules/comment/src/Tests/CommentFieldsTest.php
+++ b/core/modules/comment/tests/src/Functional/CommentFieldsTest.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Drupal\comment\Tests;
+namespace Drupal\Tests\comment\Functional;
 
 use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
 use Drupal\field\Entity\FieldStorageConfig;
diff --git a/core/modules/comment/src/Tests/CommentInterfaceTest.php b/core/modules/comment/tests/src/Functional/CommentInterfaceTest.php
similarity index 99%
rename from core/modules/comment/src/Tests/CommentInterfaceTest.php
rename to core/modules/comment/tests/src/Functional/CommentInterfaceTest.php
index 6f67ca8..7342633 100644
--- a/core/modules/comment/src/Tests/CommentInterfaceTest.php
+++ b/core/modules/comment/tests/src/Functional/CommentInterfaceTest.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Drupal\comment\Tests;
+namespace Drupal\Tests\comment\Functional;
 
 use Drupal\comment\CommentManagerInterface;
 use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
diff --git a/core/modules/comment/src/Tests/CommentLanguageTest.php b/core/modules/comment/tests/src/Functional/CommentLanguageTest.php
similarity index 97%
rename from core/modules/comment/src/Tests/CommentLanguageTest.php
rename to core/modules/comment/tests/src/Functional/CommentLanguageTest.php
index eee0862..ab9b253 100644
--- a/core/modules/comment/src/Tests/CommentLanguageTest.php
+++ b/core/modules/comment/tests/src/Functional/CommentLanguageTest.php
@@ -1,18 +1,18 @@
 <?php
 
-namespace Drupal\comment\Tests;
+namespace Drupal\Tests\comment\Functional;
 
 use Drupal\comment\Entity\Comment;
 use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
 use Drupal\field\Entity\FieldStorageConfig;
-use Drupal\simpletest\WebTestBase;
+use Drupal\Tests\BrowserTestBase;
 
 /**
  * Tests for comment language.
  *
  * @group comment
  */
-class CommentLanguageTest extends WebTestBase {
+class CommentLanguageTest extends BrowserTestBase {
 
   use CommentTestTrait;
 
diff --git a/core/modules/comment/src/Tests/CommentLinksAlterTest.php b/core/modules/comment/tests/src/Functional/CommentLinksAlterTest.php
similarity index 94%
rename from core/modules/comment/src/Tests/CommentLinksAlterTest.php
rename to core/modules/comment/tests/src/Functional/CommentLinksAlterTest.php
index 88e092f..e49361b 100644
--- a/core/modules/comment/src/Tests/CommentLinksAlterTest.php
+++ b/core/modules/comment/tests/src/Functional/CommentLinksAlterTest.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Drupal\comment\Tests;
+namespace Drupal\Tests\comment\Functional;
 
 /**
  * Tests comment links altering.
diff --git a/core/modules/comment/src/Tests/CommentLinksTest.php b/core/modules/comment/tests/src/Functional/CommentLinksTest.php
similarity index 99%
rename from core/modules/comment/src/Tests/CommentLinksTest.php
rename to core/modules/comment/tests/src/Functional/CommentLinksTest.php
index 589a355..3c68a01 100644
--- a/core/modules/comment/src/Tests/CommentLinksTest.php
+++ b/core/modules/comment/tests/src/Functional/CommentLinksTest.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Drupal\comment\Tests;
+namespace Drupal\Tests\comment\Functional;
 
 use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
 use Drupal\Core\Language\LanguageInterface;
diff --git a/core/modules/comment/src/Tests/CommentNewIndicatorTest.php b/core/modules/comment/tests/src/Functional/CommentNewIndicatorTest.php
similarity index 99%
rename from core/modules/comment/src/Tests/CommentNewIndicatorTest.php
rename to core/modules/comment/tests/src/Functional/CommentNewIndicatorTest.php
index 55609e3..a93bd1c 100644
--- a/core/modules/comment/src/Tests/CommentNewIndicatorTest.php
+++ b/core/modules/comment/tests/src/Functional/CommentNewIndicatorTest.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Drupal\comment\Tests;
+namespace Drupal\Tests\comment\Functional;
 
 use Drupal\Component\Serialization\Json;
 use Drupal\Core\Language\LanguageInterface;
diff --git a/core/modules/comment/src/Tests/CommentNodeAccessTest.php b/core/modules/comment/tests/src/Functional/CommentNodeAccessTest.php
similarity index 98%
rename from core/modules/comment/src/Tests/CommentNodeAccessTest.php
rename to core/modules/comment/tests/src/Functional/CommentNodeAccessTest.php
index ffbffa1..6842dc1 100644
--- a/core/modules/comment/src/Tests/CommentNodeAccessTest.php
+++ b/core/modules/comment/tests/src/Functional/CommentNodeAccessTest.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Drupal\comment\Tests;
+namespace Drupal\Tests\comment\Functional;
 
 use Drupal\comment\CommentManagerInterface;
 
diff --git a/core/modules/comment/src/Tests/CommentNodeChangesTest.php b/core/modules/comment/tests/src/Functional/CommentNodeChangesTest.php
similarity index 96%
rename from core/modules/comment/src/Tests/CommentNodeChangesTest.php
rename to core/modules/comment/tests/src/Functional/CommentNodeChangesTest.php
index 148198d..a63ce8b 100644
--- a/core/modules/comment/src/Tests/CommentNodeChangesTest.php
+++ b/core/modules/comment/tests/src/Functional/CommentNodeChangesTest.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Drupal\comment\Tests;
+namespace Drupal\Tests\comment\Functional;
 
 use Drupal\comment\Entity\Comment;
 use Drupal\field\Entity\FieldConfig;
diff --git a/core/modules/comment/src/Tests/CommentNonNodeTest.php b/core/modules/comment/tests/src/Functional/CommentNonNodeTest.php
similarity index 99%
rename from core/modules/comment/src/Tests/CommentNonNodeTest.php
rename to core/modules/comment/tests/src/Functional/CommentNonNodeTest.php
index 3c285d8..1e0e871 100644
--- a/core/modules/comment/src/Tests/CommentNonNodeTest.php
+++ b/core/modules/comment/tests/src/Functional/CommentNonNodeTest.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Drupal\comment\Tests;
+namespace Drupal\Tests\comment\Functional;
 
 use Drupal\comment\CommentInterface;
 use Drupal\comment\Entity\Comment;
@@ -10,7 +10,7 @@
 use Drupal\field\Entity\FieldConfig;
 use Drupal\field\Entity\FieldStorageConfig;
 use Drupal\field_ui\Tests\FieldUiTestTrait;
-use Drupal\simpletest\WebTestBase;
+use Drupal\Tests\BrowserTestBase;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\user\RoleInterface;
 
@@ -19,7 +19,7 @@
  *
  * @group comment
  */
-class CommentNonNodeTest extends WebTestBase {
+class CommentNonNodeTest extends BrowserTestBase {
 
   use FieldUiTestTrait;
   use CommentTestTrait;
diff --git a/core/modules/comment/src/Tests/CommentPagerTest.php b/core/modules/comment/tests/src/Functional/CommentPagerTest.php
similarity index 99%
rename from core/modules/comment/src/Tests/CommentPagerTest.php
rename to core/modules/comment/tests/src/Functional/CommentPagerTest.php
index 9c48978..bf26aa1 100644
--- a/core/modules/comment/src/Tests/CommentPagerTest.php
+++ b/core/modules/comment/tests/src/Functional/CommentPagerTest.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Drupal\comment\Tests;
+namespace Drupal\Tests\comment\Functional;
 
 use Drupal\comment\CommentManagerInterface;
 use Drupal\Component\Utility\SafeMarkup;
diff --git a/core/modules/comment/src/Tests/CommentPreviewTest.php b/core/modules/comment/tests/src/Functional/CommentPreviewTest.php
similarity index 99%
rename from core/modules/comment/src/Tests/CommentPreviewTest.php
rename to core/modules/comment/tests/src/Functional/CommentPreviewTest.php
index 1b07fe6..ddda3a1 100644
--- a/core/modules/comment/src/Tests/CommentPreviewTest.php
+++ b/core/modules/comment/tests/src/Functional/CommentPreviewTest.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Drupal\comment\Tests;
+namespace Drupal\Tests\comment\Functional;
 
 use Drupal\comment\CommentManagerInterface;
 use Drupal\Component\Render\MarkupInterface;
diff --git a/core/modules/comment/src/Tests/CommentRssTest.php b/core/modules/comment/tests/src/Functional/CommentRssTest.php
similarity index 97%
rename from core/modules/comment/src/Tests/CommentRssTest.php
rename to core/modules/comment/tests/src/Functional/CommentRssTest.php
index 35cad71..60708bd 100644
--- a/core/modules/comment/src/Tests/CommentRssTest.php
+++ b/core/modules/comment/tests/src/Functional/CommentRssTest.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Drupal\comment\Tests;
+namespace Drupal\Tests\comment\Functional;
 
 use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
 use Drupal\Core\Cache\Cache;
diff --git a/core/modules/comment/src/Tests/CommentStatisticsTest.php b/core/modules/comment/tests/src/Functional/CommentStatisticsTest.php
similarity index 99%
rename from core/modules/comment/src/Tests/CommentStatisticsTest.php
rename to core/modules/comment/tests/src/Functional/CommentStatisticsTest.php
index 0d1ec3a..4a0eae0 100644
--- a/core/modules/comment/src/Tests/CommentStatisticsTest.php
+++ b/core/modules/comment/tests/src/Functional/CommentStatisticsTest.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Drupal\comment\Tests;
+namespace Drupal\Tests\comment\Functional;
 
 use Drupal\comment\CommentManagerInterface;
 use Drupal\comment\Entity\Comment;
diff --git a/core/modules/comment/tests/src/Functional/CommentTestBase.php b/core/modules/comment/tests/src/Functional/CommentTestBase.php
new file mode 100644
index 0000000..e34f307
--- /dev/null
+++ b/core/modules/comment/tests/src/Functional/CommentTestBase.php
@@ -0,0 +1,401 @@
+<?php
+
+namespace Drupal\Tests\comment\Functional;
+
+use Drupal\comment\Entity\CommentType;
+use Drupal\comment\Entity\Comment;
+use Drupal\comment\CommentInterface;
+use Drupal\field\Entity\FieldConfig;
+use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
+use Drupal\node\Entity\NodeType;
+use Drupal\Tests\BrowserTestBase;
+
+/**
+ * Provides setup and helper methods for comment tests.
+ */
+abstract class CommentTestBase extends BrowserTestBase {
+
+  use CommentTestTrait;
+
+  /**
+   * Modules to install.
+   *
+   * @var array
+   */
+  public static $modules = ['block', 'comment', 'node', 'history', 'field_ui', 'datetime'];
+
+  /**
+   * An administrative user with permission to configure comment settings.
+   *
+   * @var \Drupal\user\UserInterface
+   */
+  protected $adminUser;
+
+  /**
+   * A normal user with permission to post comments.
+   *
+   * @var \Drupal\user\UserInterface
+   */
+  protected $webUser;
+
+  /**
+   * A test node to which comments will be posted.
+   *
+   * @var \Drupal\node\NodeInterface
+   */
+  protected $node;
+
+  protected function setUp() {
+    parent::setUp();
+
+    // Create an article content type only if it does not yet exist, so that
+    // child classes may specify the standard profile.
+    $types = NodeType::loadMultiple();
+    if (empty($types['article'])) {
+      $this->drupalCreateContentType(['type' => 'article', 'name' => t('Article')]);
+    }
+
+    // Create two test users.
+    $this->adminUser = $this->drupalCreateUser([
+      'administer content types',
+      'administer comments',
+      'administer comment types',
+      'administer comment fields',
+      'administer comment display',
+      'skip comment approval',
+      'post comments',
+      'access comments',
+      // Usernames aren't shown in comment edit form autocomplete unless this
+      // permission is granted.
+      'access user profiles',
+      'access content',
+     ]);
+    $this->webUser = $this->drupalCreateUser([
+      'access comments',
+      'post comments',
+      'create article content',
+      'edit own comments',
+      'skip comment approval',
+      'access content',
+    ]);
+
+    // Create comment field on article.
+    $this->addDefaultCommentField('node', 'article');
+
+    // Create a test node authored by the web user.
+    $this->node = $this->drupalCreateNode(['type' => 'article', 'promote' => 1, 'uid' => $this->webUser->id()]);
+    $this->drupalPlaceBlock('local_tasks_block');
+  }
+
+  /**
+   * Posts a comment.
+   *
+   * @param \Drupal\Core\Entity\EntityInterface|null $entity
+   *   Node to post comment on or NULL to post to the previously loaded page.
+   * @param string $comment
+   *   Comment body.
+   * @param string $subject
+   *   Comment subject.
+   * @param string $contact
+   *   Set to NULL for no contact info, TRUE to ignore success checking, and
+   *   array of values to set contact info.
+   * @param string $field_name
+   *   (optional) Field name through which the comment should be posted.
+   *   Defaults to 'comment'.
+   *
+   * @return \Drupal\comment\CommentInterface|null
+   *   The posted comment or NULL when posted comment was not found.
+   */
+  public function postComment($entity, $comment, $subject = '', $contact = NULL, $field_name = 'comment') {
+    $edit = [];
+    $edit['comment_body[0][value]'] = $comment;
+
+    if ($entity !== NULL) {
+      $field = FieldConfig::loadByName($entity->getEntityTypeId(), $entity->bundle(), $field_name);
+    }
+    else {
+      $field = FieldConfig::loadByName('node', 'article', $field_name);
+    }
+    $preview_mode = $field->getSetting('preview');
+
+    // Must get the page before we test for fields.
+    if ($entity !== NULL) {
+      $this->drupalGet('comment/reply/' . $entity->getEntityTypeId() . '/' . $entity->id() . '/' . $field_name);
+    }
+
+    // Determine the visibility of subject form field.
+    if (entity_get_form_display('comment', 'comment', 'default')->getComponent('subject')) {
+      // Subject input allowed.
+      $edit['subject[0][value]'] = $subject;
+    }
+    else {
+      $this->assertNoFieldByName('subject[0][value]', '', 'Subject field not found.');
+    }
+
+    if ($contact !== NULL && is_array($contact)) {
+      $edit += $contact;
+    }
+    switch ($preview_mode) {
+      case DRUPAL_REQUIRED:
+        // Preview required so no save button should be found.
+        $this->assertNoFieldByName('op', t('Save'), 'Save button not found.');
+        $this->drupalPostForm(NULL, $edit, t('Preview'));
+        // Don't break here so that we can test post-preview field presence and
+        // function below.
+      case DRUPAL_OPTIONAL:
+        $this->assertFieldByName('op', t('Preview'), 'Preview button found.');
+        $this->assertFieldByName('op', t('Save'), 'Save button found.');
+        $this->drupalPostForm(NULL, $edit, t('Save'));
+        break;
+
+      case DRUPAL_DISABLED:
+        $this->assertNoFieldByName('op', t('Preview'), 'Preview button not found.');
+        $this->assertFieldByName('op', t('Save'), 'Save button found.');
+        $this->drupalPostForm(NULL, $edit, t('Save'));
+        break;
+    }
+    $match = [];
+    // Get comment ID
+    preg_match('/#comment-([0-9]+)/', $this->getURL(), $match);
+
+    // Get comment.
+    if ($contact !== TRUE) { // If true then attempting to find error message.
+      if ($subject) {
+        $this->assertText($subject, 'Comment subject posted.');
+      }
+      $this->assertText($comment, 'Comment body posted.');
+      $this->assertTrue((!empty($match) && !empty($match[1])), 'Comment id found.');
+    }
+
+    if (isset($match[1])) {
+      \Drupal::entityManager()->getStorage('comment')->resetCache([$match[1]]);
+      return Comment::load($match[1]);
+    }
+  }
+
+  /**
+   * Checks current page for specified comment.
+   *
+   * @param \Drupal\comment\CommentInterface $comment
+   *   The comment object.
+   * @param bool $reply
+   *   Boolean indicating whether the comment is a reply to another comment.
+   *
+   * @return bool
+   *   Boolean indicating whether the comment was found.
+   */
+  public function commentExists(CommentInterface $comment = NULL, $reply = FALSE) {
+    if ($comment) {
+      $comment_element = $this->getSession()->getPage()->find('css', '.comment-wrapper ' . ($reply ? '.indented ' : '') . '#comment-' . $comment->id() . ' ~ article');
+      if (empty($comment_element)) {
+        return FALSE;
+      }
+
+      $comment_title = $comment_element->find('xpath', 'div/h3/a');
+      if (empty($comment_title) || $comment_title->getText() !== $comment->getSubject()) {
+        return FALSE;
+      }
+
+      $comment_body = $comment_element->find('xpath', 'div/div/p');
+      if (empty($comment_body) || $comment_body->getText() !== $comment->comment_body->value) {
+        return FALSE;
+      }
+
+      return TRUE;
+    }
+    else {
+      return FALSE;
+    }
+  }
+
+  /**
+   * Deletes a comment.
+   *
+   * @param \Drupal\comment\CommentInterface $comment
+   *   Comment to delete.
+   */
+  public function deleteComment(CommentInterface $comment) {
+    $this->drupalPostForm('comment/' . $comment->id() . '/delete', [], t('Delete'));
+    $this->assertText(t('The comment and all its replies have been deleted.'), 'Comment deleted.');
+  }
+
+  /**
+   * Sets the value governing whether the subject field should be enabled.
+   *
+   * @param bool $enabled
+   *   Boolean specifying whether the subject field should be enabled.
+   */
+  public function setCommentSubject($enabled) {
+    $form_display = entity_get_form_display('comment', 'comment', 'default');
+    if ($enabled) {
+      $form_display->setComponent('subject', [
+        'type' => 'string_textfield',
+      ]);
+    }
+    else {
+      $form_display->removeComponent('subject');
+    }
+    $form_display->save();
+    // Display status message.
+    $this->pass('Comment subject ' . ($enabled ? 'enabled' : 'disabled') . '.');
+  }
+
+  /**
+   * Sets the value governing the previewing mode for the comment form.
+   *
+   * @param int $mode
+   *   The preview mode: DRUPAL_DISABLED, DRUPAL_OPTIONAL or DRUPAL_REQUIRED.
+   * @param string $field_name
+   *   (optional) Field name through which the comment should be posted.
+   *   Defaults to 'comment'.
+   */
+  public function setCommentPreview($mode, $field_name = 'comment') {
+    switch ($mode) {
+      case DRUPAL_DISABLED:
+        $mode_text = 'disabled';
+        break;
+
+      case DRUPAL_OPTIONAL:
+        $mode_text = 'optional';
+        break;
+
+      case DRUPAL_REQUIRED:
+        $mode_text = 'required';
+        break;
+    }
+    $this->setCommentSettings('preview', $mode, format_string('Comment preview @mode_text.', ['@mode_text' => $mode_text]), $field_name);
+  }
+
+  /**
+   * Sets the value governing whether the comment form is on its own page.
+   *
+   * @param bool $enabled
+   *   TRUE if the comment form should be displayed on the same page as the
+   *   comments; FALSE if it should be displayed on its own page.
+   * @param string $field_name
+   *   (optional) Field name through which the comment should be posted.
+   *   Defaults to 'comment'.
+   */
+  public function setCommentForm($enabled, $field_name = 'comment') {
+    $this->setCommentSettings('form_location', ($enabled ? CommentItemInterface::FORM_BELOW : CommentItemInterface::FORM_SEPARATE_PAGE), 'Comment controls ' . ($enabled ? 'enabled' : 'disabled') . '.', $field_name);
+  }
+
+  /**
+   * Sets the value governing restrictions on anonymous comments.
+   *
+   * @param int $level
+   *   The level of the contact information allowed for anonymous comments:
+   *   - 0: No contact information allowed.
+   *   - 1: Contact information allowed but not required.
+   *   - 2: Contact information required.
+   */
+  public function setCommentAnonymous($level) {
+    $this->setCommentSettings('anonymous', $level, format_string('Anonymous commenting set to level @level.', ['@level' => $level]));
+  }
+
+  /**
+   * Sets the value specifying the default number of comments per page.
+   *
+   * @param int $number
+   *   Comments per page value.
+   * @param string $field_name
+   *   (optional) Field name through which the comment should be posted.
+   *   Defaults to 'comment'.
+   */
+  public function setCommentsPerPage($number, $field_name = 'comment') {
+    $this->setCommentSettings('per_page', $number, format_string('Number of comments per page set to @number.', ['@number' => $number]), $field_name);
+  }
+
+  /**
+   * Sets a comment settings variable for the article content type.
+   *
+   * @param string $name
+   *   Name of variable.
+   * @param string $value
+   *   Value of variable.
+   * @param string $message
+   *   Status message to display.
+   * @param string $field_name
+   *   (optional) Field name through which the comment should be posted.
+   *   Defaults to 'comment'.
+   */
+  public function setCommentSettings($name, $value, $message, $field_name = 'comment') {
+    $field = FieldConfig::loadByName('node', 'article', $field_name);
+    $field->setSetting($name, $value);
+    $field->save();
+    // Display status message.
+    $this->pass($message);
+  }
+
+  /**
+   * Checks whether the commenter's contact information is displayed.
+   *
+   * @return bool
+   *   Contact info is available.
+   */
+  public function commentContactInfoAvailable() {
+    return preg_match('/(input).*?(name="name").*?(input).*?(name="mail").*?(input).*?(name="homepage")/s', $this->getRawContent());
+  }
+
+  /**
+   * Performs the specified operation on the specified comment.
+   *
+   * @param \Drupal\comment\CommentInterface $comment
+   *   Comment to perform operation on.
+   * @param string $operation
+   *   Operation to perform.
+   * @param bool $approval
+   *   Operation is found on approval page.
+   */
+  public function performCommentOperation(CommentInterface $comment, $operation, $approval = FALSE) {
+    $edit = [];
+    $edit['operation'] = $operation;
+    $edit['comments[' . $comment->id() . ']'] = TRUE;
+    $this->drupalPostForm('admin/content/comment' . ($approval ? '/approval' : ''), $edit, t('Update'));
+
+    if ($operation == 'delete') {
+      $this->drupalPostForm(NULL, [], t('Delete comments'));
+      $this->assertRaw(\Drupal::translation()->formatPlural(1, 'Deleted 1 comment.', 'Deleted @count comments.'), format_string('Operation "@operation" was performed on comment.', ['@operation' => $operation]));
+    }
+    else {
+      $this->assertText(t('The update has been performed.'), format_string('Operation "@operation" was performed on comment.', ['@operation' => $operation]));
+    }
+  }
+
+  /**
+   * Gets the comment ID for an unapproved comment.
+   *
+   * @param string $subject
+   *   Comment subject to find.
+   *
+   * @return int
+   *   Comment id.
+   */
+  public function getUnapprovedComment($subject) {
+    $this->drupalGet('admin/content/comment/approval');
+    preg_match('/href="(.*?)#comment-([^"]+)"(.*?)>(' . $subject . ')/', $this->getRawContent(), $match);
+
+    return $match[2];
+  }
+
+  /**
+   * Creates a comment comment type (bundle).
+   *
+   * @param string $label
+   *   The comment type label.
+   *
+   * @return \Drupal\comment\Entity\CommentType
+   *   Created comment type.
+   */
+  protected function createCommentType($label) {
+    $bundle = CommentType::create([
+      'id' => $label,
+      'label' => $label,
+      'description' => '',
+      'target_entity_type_id' => 'node',
+    ]);
+    $bundle->save();
+    return $bundle;
+  }
+
+}
diff --git a/core/modules/comment/src/Tests/CommentTestTrait.php b/core/modules/comment/tests/src/Functional/CommentTestTrait.php
similarity index 99%
rename from core/modules/comment/src/Tests/CommentTestTrait.php
rename to core/modules/comment/tests/src/Functional/CommentTestTrait.php
index 038a3df..6fba763 100644
--- a/core/modules/comment/src/Tests/CommentTestTrait.php
+++ b/core/modules/comment/tests/src/Functional/CommentTestTrait.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Drupal\comment\Tests;
+namespace Drupal\Tests\comment\Functional;
 
 use Drupal\Component\Utility\Unicode;
 use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
diff --git a/core/modules/comment/src/Tests/CommentThreadingTest.php b/core/modules/comment/tests/src/Functional/CommentThreadingTest.php
similarity index 99%
rename from core/modules/comment/src/Tests/CommentThreadingTest.php
rename to core/modules/comment/tests/src/Functional/CommentThreadingTest.php
index 8f412b2..cff22ba 100644
--- a/core/modules/comment/src/Tests/CommentThreadingTest.php
+++ b/core/modules/comment/tests/src/Functional/CommentThreadingTest.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Drupal\comment\Tests;
+namespace Drupal\Tests\comment\Functional;
 
 use Drupal\comment\CommentManagerInterface;
 
diff --git a/core/modules/comment/src/Tests/CommentTitleTest.php b/core/modules/comment/tests/src/Functional/CommentTitleTest.php
similarity index 93%
rename from core/modules/comment/src/Tests/CommentTitleTest.php
rename to core/modules/comment/tests/src/Functional/CommentTitleTest.php
index 30d3935..f9db448 100644
--- a/core/modules/comment/src/Tests/CommentTitleTest.php
+++ b/core/modules/comment/tests/src/Functional/CommentTitleTest.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Drupal\comment\Tests;
+namespace Drupal\Tests\comment\Functional;
 
 /**
  * Tests to ensure that appropriate and accessible markup is created for comment
@@ -60,8 +60,8 @@ public function testCommentPopulatedTitles() {
     // Tests that markup is created for comment with heading.
     $this->assertPattern('|<h3[^>]*><a[^>]*>' . $subject_text . '</a></h3>|', 'Comment title is rendered in h3 when title populated.');
     // Tests that the comment's title link is the permalink of the comment.
-    $comment_permalink = $this->cssSelect('.permalink');
-    $comment_permalink = (string) $comment_permalink[0]['href'];
+    $comment_permalink = $this->getSession()->getPage()->find('css', '.permalink');
+    $comment_permalink = $comment_permalink->getAttribute('href');
     // Tests that the comment's title link contains the url fragment.
     $this->assertTrue(strpos($comment_permalink, '#comment-' . $comment1->id()), "The comment's title link contains the url fragment.");
     $this->assertEqual($comment1->permalink()->toString(), $comment_permalink, "The comment's title has the correct link.");
diff --git a/core/modules/comment/src/Tests/CommentTokenReplaceTest.php b/core/modules/comment/tests/src/Functional/CommentTokenReplaceTest.php
similarity index 99%
rename from core/modules/comment/src/Tests/CommentTokenReplaceTest.php
rename to core/modules/comment/tests/src/Functional/CommentTokenReplaceTest.php
index 6caea17..a7fe6b5 100644
--- a/core/modules/comment/src/Tests/CommentTokenReplaceTest.php
+++ b/core/modules/comment/tests/src/Functional/CommentTokenReplaceTest.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Drupal\comment\Tests;
+namespace Drupal\Tests\comment\Functional;
 
 use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
 use Drupal\Component\Render\FormattableMarkup;
diff --git a/core/modules/comment/src/Tests/CommentTranslationUITest.php b/core/modules/comment/tests/src/Functional/CommentTranslationUITest.php
similarity index 99%
rename from core/modules/comment/src/Tests/CommentTranslationUITest.php
rename to core/modules/comment/tests/src/Functional/CommentTranslationUITest.php
index 4613d66..e13e4be 100644
--- a/core/modules/comment/src/Tests/CommentTranslationUITest.php
+++ b/core/modules/comment/tests/src/Functional/CommentTranslationUITest.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Drupal\comment\Tests;
+namespace Drupal\Tests\comment\Functional;
 
 use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
 use Drupal\content_translation\Tests\ContentTranslationUITestBase;
diff --git a/core/modules/comment/src/Tests/CommentTypeTest.php b/core/modules/comment/tests/src/Functional/CommentTypeTest.php
similarity index 99%
rename from core/modules/comment/src/Tests/CommentTypeTest.php
rename to core/modules/comment/tests/src/Functional/CommentTypeTest.php
index 343bb91..e4ff2ea 100644
--- a/core/modules/comment/src/Tests/CommentTypeTest.php
+++ b/core/modules/comment/tests/src/Functional/CommentTypeTest.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Drupal\comment\Tests;
+namespace Drupal\Tests\comment\Functional;
 use Drupal\comment\Entity\Comment;
 use Drupal\comment\Entity\CommentType;
 use Drupal\comment\Plugin\Field\FieldType\CommentItemInterface;
diff --git a/core/modules/comment/src/Tests/CommentUninstallTest.php b/core/modules/comment/tests/src/Functional/CommentUninstallTest.php
similarity index 95%
rename from core/modules/comment/src/Tests/CommentUninstallTest.php
rename to core/modules/comment/tests/src/Functional/CommentUninstallTest.php
index 7873915..737e8f9 100644
--- a/core/modules/comment/src/Tests/CommentUninstallTest.php
+++ b/core/modules/comment/tests/src/Functional/CommentUninstallTest.php
@@ -1,17 +1,17 @@
 <?php
 
-namespace Drupal\comment\Tests;
+namespace Drupal\Tests\comment\Functional;
 
 use Drupal\field\Entity\FieldStorageConfig;
 use Drupal\Core\Extension\ModuleUninstallValidatorException;
-use Drupal\simpletest\WebTestBase;
+use Drupal\Tests\BrowserTestBase;
 
 /**
  * Tests comment module uninstallation.
  *
  * @group comment
  */
-class CommentUninstallTest extends WebTestBase {
+class CommentUninstallTest extends BrowserTestBase {
 
   use CommentTestTrait;
 
diff --git a/core/modules/comment/src/Tests/Views/ArgumentUserUIDTest.php b/core/modules/comment/tests/src/Functional/Views/ArgumentUserUIDTest.php
similarity index 96%
rename from core/modules/comment/src/Tests/Views/ArgumentUserUIDTest.php
rename to core/modules/comment/tests/src/Functional/Views/ArgumentUserUIDTest.php
index d3526dc..ad08f4f 100644
--- a/core/modules/comment/src/Tests/Views/ArgumentUserUIDTest.php
+++ b/core/modules/comment/tests/src/Functional/Views/ArgumentUserUIDTest.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Drupal\comment\Tests\Views;
+namespace Drupal\Tests\comment\Functional\Views;
 
 use Drupal\comment\Entity\Comment;
 use Drupal\user\Entity\User;
diff --git a/core/modules/comment/src/Tests/Views/CommentFieldFilterTest.php b/core/modules/comment/tests/src/Functional/Views/CommentFieldFilterTest.php
similarity index 96%
rename from core/modules/comment/src/Tests/Views/CommentFieldFilterTest.php
rename to core/modules/comment/tests/src/Functional/Views/CommentFieldFilterTest.php
index 48f7d6d..32f5dfc 100644
--- a/core/modules/comment/src/Tests/Views/CommentFieldFilterTest.php
+++ b/core/modules/comment/tests/src/Functional/Views/CommentFieldFilterTest.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Drupal\comment\Tests\Views;
+namespace Drupal\Tests\comment\Functional\Views;
 
 use Drupal\language\Entity\ConfigurableLanguage;
 use Drupal\comment\Entity\Comment;
@@ -31,8 +31,8 @@ class CommentFieldFilterTest extends CommentTestBase {
    */
   public $commentTitles = [];
 
-  public function setUp() {
-    parent::setUp();
+  protected function setUp($import_test_views = true) {
+    parent::setUp($import_test_views);
     $this->drupalLogin($this->drupalCreateUser(['access comments']));
 
     // Add two new languages.
diff --git a/core/modules/comment/src/Tests/Views/CommentFieldNameTest.php b/core/modules/comment/tests/src/Functional/Views/CommentFieldNameTest.php
similarity index 94%
rename from core/modules/comment/src/Tests/Views/CommentFieldNameTest.php
rename to core/modules/comment/tests/src/Functional/Views/CommentFieldNameTest.php
index 870a4f4..86c9437 100644
--- a/core/modules/comment/src/Tests/Views/CommentFieldNameTest.php
+++ b/core/modules/comment/tests/src/Functional/Views/CommentFieldNameTest.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Drupal\comment\Tests\Views;
+namespace Drupal\Tests\comment\Functional\Views;
 
 use Drupal\comment\Entity\Comment;
 use Drupal\Core\Render\RenderContext;
@@ -39,8 +39,8 @@ class CommentFieldNameTest extends CommentTestBase {
   /**
    * {@inheritdoc}
    */
-  protected function setUp() {
-    parent::setUp();
+  protected function setUp($import_test_views = true) {
+    parent::setUp($import_test_views);
     $this->addDefaultCommentField('node', 'page', $this->fieldName);
     $this->customComment = Comment::create([
       'entity_id' => $this->nodeUserCommented->id(),
diff --git a/core/modules/comment/src/Tests/Views/CommentOperationsTest.php b/core/modules/comment/tests/src/Functional/Views/CommentOperationsTest.php
similarity index 94%
rename from core/modules/comment/src/Tests/Views/CommentOperationsTest.php
rename to core/modules/comment/tests/src/Functional/Views/CommentOperationsTest.php
index 89dc364..4bef37e 100644
--- a/core/modules/comment/src/Tests/Views/CommentOperationsTest.php
+++ b/core/modules/comment/tests/src/Functional/Views/CommentOperationsTest.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Drupal\comment\Tests\Views;
+namespace Drupal\Tests\comment\Functional\Views;
 
 /**
  * Tests comment operations.
diff --git a/core/modules/comment/src/Tests/Views/CommentRestExportTest.php b/core/modules/comment/tests/src/Functional/Views/CommentRestExportTest.php
similarity index 85%
rename from core/modules/comment/src/Tests/Views/CommentRestExportTest.php
rename to core/modules/comment/tests/src/Functional/Views/CommentRestExportTest.php
index ed7ce55..070c70e 100644
--- a/core/modules/comment/src/Tests/Views/CommentRestExportTest.php
+++ b/core/modules/comment/tests/src/Functional/Views/CommentRestExportTest.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Drupal\comment\Tests\Views;
+namespace Drupal\Tests\comment\Functional\Views;
 
 use Drupal\Component\Serialization\Json;
 use Drupal\comment\Entity\Comment;
@@ -24,8 +24,8 @@ class CommentRestExportTest extends CommentTestBase {
    */
   public static $modules = ['node', 'comment', 'comment_test_views', 'rest', 'hal'];
 
-  protected function setUp() {
-    parent::setUp();
+  protected function setUp($import_test_views = true) {
+    parent::setUp($import_test_views);
     // Add another anonymous comment.
     $comment = [
       'uid' => 0,
@@ -51,7 +51,7 @@ protected function setUp() {
    * Test comment row.
    */
   public function testCommentRestExport() {
-    $this->drupalGetWithFormat(sprintf('node/%d/comments', $this->nodeUserCommented->id()), 'hal_json');
+    $this->drupalGet(sprintf('node/%d/comments', $this->nodeUserCommented->id()), ['query' => ['_format' => 'hal_json']]);
     $this->assertResponse(200);
     $contents = Json::decode($this->getRawContent());
     $this->assertEqual($contents[0]['subject'], 'How much wood would a woodchuck chuck');
diff --git a/core/modules/comment/src/Tests/Views/CommentRowTest.php b/core/modules/comment/tests/src/Functional/Views/CommentRowTest.php
similarity index 91%
rename from core/modules/comment/src/Tests/Views/CommentRowTest.php
rename to core/modules/comment/tests/src/Functional/Views/CommentRowTest.php
index d73811e..7cf3ef3 100644
--- a/core/modules/comment/src/Tests/Views/CommentRowTest.php
+++ b/core/modules/comment/tests/src/Functional/Views/CommentRowTest.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Drupal\comment\Tests\Views;
+namespace Drupal\Tests\comment\Functional\Views;
 
 /**
  * Tests the comment row plugin.
diff --git a/core/modules/comment/tests/src/Functional/Views/CommentTestBase.php b/core/modules/comment/tests/src/Functional/Views/CommentTestBase.php
new file mode 100644
index 0000000..a37c95a
--- /dev/null
+++ b/core/modules/comment/tests/src/Functional/Views/CommentTestBase.php
@@ -0,0 +1,90 @@
+<?php
+
+namespace Drupal\Tests\comment\Functional\Views;
+
+use Drupal\Tests\comment\Functional\CommentTestTrait;
+use Drupal\Tests\views\Functional\ViewTestBase;
+use Drupal\views\Tests\ViewTestData;
+use Drupal\comment\Entity\Comment;
+
+/**
+ * Tests the argument_comment_user_uid handler.
+ */
+abstract class CommentTestBase extends ViewTestBase {
+
+  use CommentTestTrait;
+
+  /**
+   * Modules to install.
+   *
+   * @var array
+   */
+  public static $modules = ['node', 'comment', 'comment_test_views'];
+
+  /**
+   * A normal user with permission to post comments (without approval).
+   *
+   * @var \Drupal\user\UserInterface
+   */
+  protected $account;
+
+  /**
+   * A second normal user that will author a node for $account to comment on.
+   *
+   * @var \Drupal\user\UserInterface
+   */
+  protected $account2;
+
+  /**
+   * Stores a node posted by the user created as $account.
+   *
+   * @var \Drupal\node\NodeInterface
+   */
+  protected $nodeUserPosted;
+
+  /**
+   * Stores a node posted by the user created as $account2.
+   *
+   * @var \Drupal\node\NodeInterface
+   */
+  protected $nodeUserCommented;
+
+  /**
+   * Stores a comment used by the tests.
+   *
+   * @var \Drupal\comment\Entity\Comment
+   */
+  protected $comment;
+
+  protected function setUp($import_test_views = true) {
+    parent::setUp($import_test_views);
+
+    ViewTestData::createTestViews(get_class($this), ['comment_test_views']);
+
+    // Add two users, create a node with the user1 as author and another node
+    // with user2 as author. For the second node add a comment from user1.
+    $this->account = $this->drupalCreateUser(['skip comment approval']);
+    $this->account2 = $this->drupalCreateUser();
+    $this->drupalLogin($this->account);
+
+    $this->drupalCreateContentType(['type' => 'page', 'name' => t('Basic page')]);
+    $this->addDefaultCommentField('node', 'page');
+
+    $this->nodeUserPosted = $this->drupalCreateNode();
+    $this->nodeUserCommented = $this->drupalCreateNode(['uid' => $this->account2->id()]);
+
+    $comment = [
+      'uid' => $this->loggedInUser->id(),
+      'entity_id' => $this->nodeUserCommented->id(),
+      'entity_type' => 'node',
+      'field_name' => 'comment',
+      'subject' => 'How much wood would a woodchuck chuck',
+      'cid' => '',
+      'pid' => '',
+      'mail' => 'someone@example.com',
+    ];
+    $this->comment = Comment::create($comment);
+    $this->comment->save();
+  }
+
+}
diff --git a/core/modules/comment/src/Tests/Views/DefaultViewRecentCommentsTest.php b/core/modules/comment/tests/src/Functional/Views/DefaultViewRecentCommentsTest.php
similarity index 93%
rename from core/modules/comment/src/Tests/Views/DefaultViewRecentCommentsTest.php
rename to core/modules/comment/tests/src/Functional/Views/DefaultViewRecentCommentsTest.php
index 3e5f26b..7b9fd9f 100644
--- a/core/modules/comment/src/Tests/Views/DefaultViewRecentCommentsTest.php
+++ b/core/modules/comment/tests/src/Functional/Views/DefaultViewRecentCommentsTest.php
@@ -1,12 +1,12 @@
 <?php
 
-namespace Drupal\comment\Tests\Views;
+namespace Drupal\Tests\comment\Functional\Views;
 
 use Drupal\comment\CommentInterface;
 use Drupal\comment\Entity\Comment;
-use Drupal\comment\Tests\CommentTestTrait;
+use Drupal\Tests\comment\Functional\CommentTestTrait;
 use Drupal\views\Views;
-use Drupal\views\Tests\ViewTestBase;
+use Drupal\Tests\views\Functional\ViewTestBase;
 
 /**
  * Tests results for the Recent Comments view shipped with the module.
@@ -59,8 +59,8 @@ class DefaultViewRecentCommentsTest extends ViewTestBase {
    */
   public $node;
 
-  protected function setUp() {
-    parent::setUp();
+  protected function setUp($import_test_views = true) {
+    parent::setUp($import_test_views);
 
     // Create a new content type
     $content_type = $this->drupalCreateContentType();
diff --git a/core/modules/comment/src/Tests/Views/FilterUserUIDTest.php b/core/modules/comment/tests/src/Functional/Views/FilterUserUIDTest.php
similarity index 96%
rename from core/modules/comment/src/Tests/Views/FilterUserUIDTest.php
rename to core/modules/comment/tests/src/Functional/Views/FilterUserUIDTest.php
index 442a0e7..719c597 100644
--- a/core/modules/comment/src/Tests/Views/FilterUserUIDTest.php
+++ b/core/modules/comment/tests/src/Functional/Views/FilterUserUIDTest.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Drupal\comment\Tests\Views;
+namespace Drupal\Tests\comment\Functional\Views;
 
 use Drupal\comment\Entity\Comment;
 use Drupal\user\Entity\User;
diff --git a/core/modules/comment/src/Tests/Views/NodeCommentsTest.php b/core/modules/comment/tests/src/Functional/Views/NodeCommentsTest.php
similarity index 93%
rename from core/modules/comment/src/Tests/Views/NodeCommentsTest.php
rename to core/modules/comment/tests/src/Functional/Views/NodeCommentsTest.php
index b0ee315..e2c5d15 100644
--- a/core/modules/comment/src/Tests/Views/NodeCommentsTest.php
+++ b/core/modules/comment/tests/src/Functional/Views/NodeCommentsTest.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Drupal\comment\Tests\Views;
+namespace Drupal\Tests\comment\Functional\Views;
 
 /**
  * Tests comments on nodes.
diff --git a/core/modules/comment/src/Tests/Views/RowRssTest.php b/core/modules/comment/tests/src/Functional/Views/RowRssTest.php
similarity index 93%
rename from core/modules/comment/src/Tests/Views/RowRssTest.php
rename to core/modules/comment/tests/src/Functional/Views/RowRssTest.php
index 343b797..11bedf3 100644
--- a/core/modules/comment/src/Tests/Views/RowRssTest.php
+++ b/core/modules/comment/tests/src/Functional/Views/RowRssTest.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Drupal\comment\Tests\Views;
+namespace Drupal\Tests\comment\Functional\Views;
 
 /**
  * Tests the comment rss row plugin.
diff --git a/core/modules/system/tests/modules/test_page_test/src/Form/TestForm.php b/core/modules/system/tests/modules/test_page_test/src/Form/TestForm.php
index 5f25dd7..6ce4115 100644
--- a/core/modules/system/tests/modules/test_page_test/src/Form/TestForm.php
+++ b/core/modules/system/tests/modules/test_page_test/src/Form/TestForm.php
@@ -57,6 +57,18 @@ public function buildForm(array $form, FormStateInterface $form_state) {
       '#value' => $this->t('Save'),
     ];
 
+    $form['duplicate_button'] = [
+      '#type' => 'submit',
+      '#name' => 'duplicate_button',
+      '#value' => 'Duplicate button 1',
+    ];
+
+    $form['duplicate_button_2'] = [
+      '#type' => 'submit',
+      '#name' => 'duplicate_button',
+      '#value' => 'Duplicate button 2',
+    ];
+
     return $form;
   }
 
diff --git a/core/modules/views/src/Tests/ViewTestBase.php b/core/modules/views/tests/src/Functional/ViewTestBase.php
similarity index 85%
rename from core/modules/views/src/Tests/ViewTestBase.php
rename to core/modules/views/tests/src/Functional/ViewTestBase.php
index 720d528..0465f96 100644
--- a/core/modules/views/src/Tests/ViewTestBase.php
+++ b/core/modules/views/tests/src/Functional/ViewTestBase.php
@@ -1,9 +1,12 @@
 <?php
 
-namespace Drupal\views\Tests;
+namespace Drupal\Tests\views\Functional;
 
+use Behat\Mink\Exception\ElementNotFoundException;
 use Drupal\Core\Database\Query\SelectInterface;
-use Drupal\simpletest\WebTestBase;
+use Drupal\Tests\BrowserTestBase;
+use Drupal\views\Tests\ViewResultAssertionTrait;
+use Drupal\views\Tests\ViewTestData;
 use Drupal\views\ViewExecutable;
 
 /**
@@ -16,7 +19,7 @@
  * @see \Drupal\Tests\views\Kernel\ViewsKernelTestBase
  * @see \Drupal\simpletest\WebTestBase
  */
-abstract class ViewTestBase extends WebTestBase {
+abstract class ViewTestBase extends BrowserTestBase {
 
   use ViewResultAssertionTrait;
 
@@ -91,17 +94,23 @@ protected function orderResultSet($result_set, $column, $reverse = FALSE) {
    *
    * @param string $id
    *   The HTML ID of the button
-   * @param string $label
+   * @param string $expected_label
    *   The expected label for the button.
    * @param string $message
    *   (optional) A custom message to display with the assertion. If no custom
    *   message is provided, the message will indicate the button label.
    *
-   * @return bool
-   *   TRUE if the assertion was successful, or FALSE on failure.
+   * @throws \Behat\Mink\Exception\ElementNotFoundException
    */
   protected function helperButtonHasLabel($id, $expected_label, $message = 'Label has the expected value: %label.') {
-    return $this->assertFieldById($id, $expected_label, t($message, ['%label' => $expected_label]));
+    $xpath = $this->assertSession()->buildXPathQuery('//button[@id=:value]|//input[@id=:value]', [':value' => $id]);
+    $field = $this->getSession()->getPage()->find('xpath', $xpath);
+
+    if (empty($field)) {
+      throw new ElementNotFoundException($this->getSession()->getDriver(), 'form field', 'id', $field);
+    }
+
+    $this->assertEquals($expected_label, $field->getValue());
   }
 
   /**
diff --git a/core/tests/Drupal/FunctionalTests/AssertLegacyTrait.php b/core/tests/Drupal/FunctionalTests/AssertLegacyTrait.php
index c88949d..2a6ebee 100644
--- a/core/tests/Drupal/FunctionalTests/AssertLegacyTrait.php
+++ b/core/tests/Drupal/FunctionalTests/AssertLegacyTrait.php
@@ -223,10 +223,7 @@ protected function assertResponse($code) {
    *   $this->assertSession()->fieldValueEquals() instead.
    */
   protected function assertFieldByName($name, $value = NULL) {
-    $this->assertSession()->fieldExists($name);
-    if ($value !== NULL) {
-      $this->assertSession()->fieldValueEquals($name, (string) $value);
-    }
+    $this->assertFieldByXPath($this->constructFieldXpath('name', $name), $value);
   }
 
   /**
@@ -245,12 +242,7 @@ protected function assertFieldByName($name, $value = NULL) {
    *   $this->assertSession()->fieldValueNotEquals() instead.
    */
   protected function assertNoFieldByName($name, $value = '') {
-    if ($this->getSession()->getPage()->findField($name) && isset($value)) {
-      $this->assertSession()->fieldValueNotEquals($name, (string) $value);
-    }
-    else {
-      $this->assertSession()->fieldNotExists($name);
-    }
+    $this->assertNoFieldByXPath($this->constructFieldXpath('name', $name), $value);
   }
 
   /**
@@ -271,16 +263,7 @@ protected function assertNoFieldByName($name, $value = '') {
    *   $this->assertSession()->fieldValueEquals() instead.
    */
   protected function assertFieldById($id, $value = '') {
-    $xpath = $this->assertSession()->buildXPathQuery('//textarea[@id=:value]|//input[@id=:value]|//select[@id=:value]', [':value' => $id]);
-    $field = $this->getSession()->getPage()->find('xpath', $xpath);
-
-    if (empty($field)) {
-      throw new ElementNotFoundException($this->getSession()->getDriver(), 'form field', 'id', $field);
-    }
-
-    if ($value !== NULL) {
-      $this->assertEquals($value, $field->getValue());
-    }
+    $this->assertFieldByXPath($this->constructFieldXpath('id', $id), $value);
   }
 
   /**
@@ -293,7 +276,7 @@ protected function assertFieldById($id, $value = '') {
    *   Use $this->assertSession()->fieldExists() instead.
    */
   protected function assertField($field) {
-    $this->assertSession()->fieldExists($field);
+    $this->assertFieldByXPath($this->constructFieldXpath('name', $field) . '|' . $this->constructFieldXpath('id', $field));
   }
 
   /**
@@ -306,7 +289,7 @@ protected function assertField($field) {
    *   Use $this->assertSession()->fieldNotExists() instead.
    */
   protected function assertNoField($field) {
-    $this->assertSession()->fieldNotExists($field);
+    $this->assertNoFieldByXPath($this->constructFieldXpath('name', $field) . '|' . $this->constructFieldXpath('id', $field), NULL);
   }
 
   /**
@@ -430,20 +413,7 @@ protected function assertNoLinkByHref($href) {
    *   $this->assertSession()->fieldValueNotEquals() instead.
    */
   protected function assertNoFieldById($id, $value = '') {
-    $xpath = $this->assertSession()->buildXPathQuery('//textarea[@id=:value]|//input[@id=:value]|//select[@id=:value]', [':value' => $id]);
-    $field = $this->getSession()->getPage()->find('xpath', $xpath);
-
-    // Return early if the field could not be found as expected.
-    if ($field === NULL) {
-      return;
-    }
-
-    if (!isset($value)) {
-      throw new ExpectationException(sprintf('Id "%s" appears on this page, but it should not.', $id), $this->getSession()->getDriver());
-    }
-    elseif ($value === $field->getValue()) {
-      throw new ExpectationException(sprintf('Failed asserting that %s is not equal to %s', $field->getValue(), $value), $this->getSession()->getDriver());
-    }
+    $this->assertNoFieldByXPath($this->constructFieldXpath('id', $id), $value);
   }
 
   /**
@@ -564,13 +534,20 @@ protected function assertNoFieldChecked($id) {
    *   (optional) A message to display with the assertion. Do not translate
    *   messages with t().
    *
+   * @throws ExpectationException
+   *
    * @deprecated Scheduled for removal in Drupal 9.0.0.
    *   Use $this->xpath() instead and check the values directly in the test.
    */
   protected function assertFieldByXPath($xpath, $value = NULL, $message = '') {
-    $fields = $this->xpath($xpath);
+    try {
+      $fields = $this->xpath($xpath);
 
-    $this->assertFieldsByValue($fields, $value, $message);
+      $this->assertFieldsByValue($fields, $value, $message);
+    }
+    catch (\PHPUnit_Framework_ExpectationFailedException $e) {
+      throw new ExpectationException($e->getMessage(), $this->getSession()->getDriver());
+    }
   }
 
   /**
@@ -585,25 +562,32 @@ protected function assertFieldByXPath($xpath, $value = NULL, $message = '') {
    *   (optional) A message to display with the assertion. Do not translate
    *   messages with t().
    *
+   * @throws \Behat\Mink\Exception\ExpectationException
+   *
    * @deprecated Scheduled for removal in Drupal 9.0.0.
    *   Use $this->xpath() instead and assert that the result is empty.
    */
   protected function assertNoFieldByXPath($xpath, $value = NULL, $message = '') {
-    $fields = $this->xpath($xpath);
-
-    // If value specified then check array for match.
-    $found = TRUE;
-    if (isset($value)) {
-      $found = FALSE;
-      if ($fields) {
-        foreach ($fields as $field) {
-          if ($field->getAttribute('value') == $value) {
-            $found = TRUE;
+    try {
+      $fields = $this->xpath($xpath);
+
+      // If value specified then check array for match.
+      $found = TRUE;
+      if (isset($value)) {
+        $found = FALSE;
+        if ($fields) {
+          foreach ($fields as $field) {
+            if ($field->getAttribute('value') == $value) {
+              $found = TRUE;
+            }
           }
         }
       }
+      $this->assertFalse($fields && $found, $message);
+    }
+    catch (\PHPUnit_Framework_ExpectationFailedException $e) {
+      throw new ExpectationException($e->getMessage(), $this->getSession()->getDriver());
     }
-    return $this->assertFalse($fields && $found, $message);
   }
 
   /**
@@ -637,7 +621,7 @@ protected function assertFieldsByValue($fields, $value = NULL, $message = '') {
             // Select element with an option.
             $found = TRUE;
           }
-          elseif ($field->getText() == $value) {
+          elseif ($field->getTagName() !== 'input' && $field->getText() == $value) {
             // Text area with correct text.
             $found = TRUE;
           }
@@ -773,6 +757,25 @@ protected function buildXPathQuery($xpath, array $args = []) {
   }
 
   /**
+   * Helper: Constructs an XPath for the given set of attributes and value.
+   *
+   * @param string $attribute
+   *   Field attributes.
+   * @param string $value
+   *   Value of field.
+   *
+   * @return string
+   *   XPath for specified values.
+   *
+   * @deprecated Scheduled for removal in Drupal 9.0.0.
+   *   Use $this->getSession()->getPage()->findField() instead.
+   */
+  protected function constructFieldXpath($attribute, $value) {
+    $xpath = '//textarea[@' . $attribute . '=:value]|//input[@' . $attribute . '=:value]|//select[@' . $attribute . '=:value]';
+    return $this->buildXPathQuery($xpath, [':value' => $value]);
+  }
+
+  /**
    * Gets the current raw content.
    *
    * @deprecated Scheduled for removal in Drupal 9.0.0.
diff --git a/core/tests/Drupal/FunctionalTests/BrowserTestBaseTest.php b/core/tests/Drupal/FunctionalTests/BrowserTestBaseTest.php
index 9931804..5e66f86 100644
--- a/core/tests/Drupal/FunctionalTests/BrowserTestBaseTest.php
+++ b/core/tests/Drupal/FunctionalTests/BrowserTestBaseTest.php
@@ -201,13 +201,13 @@ public function testLegacyFieldAsserts() {
       $this->assertFieldById('edit-name');
       $this->fail('The "edit-name" field with no value was found.');
     }
-    catch (\PHPUnit_Framework_ExpectationFailedException $e) {
+    catch (ExpectationException $e) {
       $this->pass('The "edit-name" field with no value was not found.');
     }
 
-    // Test that the assertion fails correctly if NULL is passed in.
+    // Test that the assertion fails correctly if another value is passed in.
     try {
-      $this->assertFieldById('name', NULL);
+      $this->assertFieldById('edit-name', 'not the value');
       $this->fail('The "name" field was found.');
     }
     catch (ExpectationException $e) {
@@ -237,6 +237,19 @@ public function testLegacyFieldAsserts() {
       $this->pass('The "name" field was found.');
     }
 
+    // Test that multiple fields with the same name are validated correctly.
+    $this->assertFieldByName('duplicate_button', 'Duplicate button 1');
+    $this->assertFieldByName('duplicate_button', 'Duplicate button 2');
+    $this->assertNoFieldByName('duplicate_button', 'Rabbit');
+
+    try {
+      $this->assertNoFieldByName('duplicate_button', 'Duplicate button 2');
+      $this->fail('The "duplicate_button" field with the value Duplicate button 2 was not found.');
+    }
+    catch (ExpectationException $e) {
+      $this->pass('The "duplicate_button" field with the value Duplicate button 2 was found.');
+    }
+
     $this->assertOptionByText('options', 'one');
     try {
       $this->assertOptionByText('options', 'four');
