diff --git a/core/modules/system/src/Tests/Batch/PageTest.php b/core/modules/system/tests/src/Functional/Batch/PageTest.php
similarity index 95%
rename from core/modules/system/src/Tests/Batch/PageTest.php
rename to core/modules/system/tests/src/Functional/Batch/PageTest.php
index dbdf18c..9b1d4dd 100644
--- a/core/modules/system/src/Tests/Batch/PageTest.php
+++ b/core/modules/system/tests/src/Functional/Batch/PageTest.php
@@ -1,15 +1,15 @@
 <?php

-namespace Drupal\system\Tests\Batch;
+namespace Drupal\Tests\system\Functional\Batch;

-use Drupal\simpletest\WebTestBase;
+use Drupal\Tests\BrowserTestBase;

 /**
  * Tests the content of the progress page.
  *
  * @group Batch
  */
-class PageTest extends WebTestBase {
+class PageTest extends BrowserTestBase {

   /**
    * Modules to enable.
diff --git a/core/modules/system/src/Tests/Batch/ProcessingTest.php b/core/modules/system/tests/src/Functional/Batch/ProcessingTest.php
similarity index 98%
rename from core/modules/system/src/Tests/Batch/ProcessingTest.php
rename to core/modules/system/tests/src/Functional/Batch/ProcessingTest.php
index 6b90cc8..2d7c7ae 100644
--- a/core/modules/system/src/Tests/Batch/ProcessingTest.php
+++ b/core/modules/system/tests/src/Functional/Batch/ProcessingTest.php
@@ -1,16 +1,16 @@
 <?php

-namespace Drupal\system\Tests\Batch;
+namespace Drupal\Tests\system\Functional\Batch;

 use Drupal\Core\Url;
-use Drupal\simpletest\WebTestBase;
+use Drupal\Tests\BrowserTestBase;

 /**
  * Tests batch processing in form and non-form workflow.
  *
  * @group Batch
  */
-class ProcessingTest extends WebTestBase {
+class ProcessingTest extends BrowserTestBase {

   /**
    * Modules to enable.
diff --git a/core/modules/user/src/Tests/UserCancelTest.php b/core/modules/user/tests/src/Functional/UserCancelTest.php
similarity index 99%
rename from core/modules/user/src/Tests/UserCancelTest.php
rename to core/modules/user/tests/src/Functional/UserCancelTest.php
index a74626e..93fab57 100644
--- a/core/modules/user/src/Tests/UserCancelTest.php
+++ b/core/modules/user/tests/src/Functional/UserCancelTest.php
@@ -1,11 +1,11 @@
 <?php

-namespace Drupal\user\Tests;
+namespace Drupal\Tests\user\Functional;

-use Drupal\comment\Tests\CommentTestTrait;
-use Drupal\simpletest\WebTestBase;
 use Drupal\comment\CommentInterface;
 use Drupal\comment\Entity\Comment;
+use Drupal\comment\Tests\CommentTestTrait;
+use Drupal\Tests\BrowserTestBase;
 use Drupal\user\Entity\User;

 /**
@@ -13,7 +13,7 @@
  *
  * @group user
  */
-class UserCancelTest extends WebTestBase {
+class UserCancelTest extends BrowserTestBase {

   use CommentTestTrait;

@@ -577,7 +577,7 @@ public function testMassUserCancelByAdmin() {
     $this->drupalPostForm(NULL, NULL, t('Cancel accounts'));
     $status = TRUE;
     foreach ($users as $account) {
-      $status = $status && (strpos($this->content, $account->getUsername() . '</em> has been deleted.') !== FALSE);
+      $status = $status && (strpos($this->getTextContent(), $account->getUsername() . ' has been deleted.') !== FALSE);
       $user_storage->resetCache([$account->id()]);
       $status = $status && !$user_storage->load($account->id());
     }
diff --git a/core/tests/Drupal/Tests/BrowserTestBase.php b/core/tests/Drupal/Tests/BrowserTestBase.php
index 5751368..3b706e6 100644
--- a/core/tests/Drupal/Tests/BrowserTestBase.php
+++ b/core/tests/Drupal/Tests/BrowserTestBase.php
@@ -23,15 +23,16 @@
 use Drupal\Core\Utility\Error;
 use Drupal\FunctionalTests\AssertLegacyTrait;
 use Drupal\simpletest\AssertHelperTrait;
-use Drupal\simpletest\ContentTypeCreationTrait;
 use Drupal\simpletest\BlockCreationTrait;
+use Drupal\simpletest\ContentTypeCreationTrait;
 use Drupal\simpletest\NodeCreationTrait;
 use Drupal\simpletest\UserCreationTrait;
+use Drupal\Tests\Traits\Core\MetaRefreshTrait;
 use PHPUnit\Framework\TestCase;
-use Symfony\Component\CssSelector\CssSelectorConverter;
-use Symfony\Component\HttpFoundation\Request;
 use Psr\Http\Message\RequestInterface;
 use Psr\Http\Message\ResponseInterface;
+use Symfony\Component\CssSelector\CssSelectorConverter;
+use Symfony\Component\HttpFoundation\Request;

 /**
  * Provides a test case for functional Drupal tests.
@@ -66,6 +67,7 @@
     createUser as drupalCreateUser;
   }
   use XdebugRequestTrait;
+  use MetaRefreshTrait;

   /**
    * The database prefix of this test run.
@@ -638,6 +640,13 @@ protected function drupalGet($path, array $options = [], array $headers = []) {
     // Ensure that any changes to variables in the other thread are picked up.
     $this->refreshVariables();

+    // Replace original page output with new output from redirected page(s).
+    if ($new = $this->checkForMetaRefresh()) {
+      $out = $new;
+      // We are finished with all meta refresh redirects, so reset the counter.
+      $this->metaRefreshCount = 0;
+    }
+
     // Log only for JavascriptTestBase tests because for Goutte we log with
     // ::getResponseLogHandler.
     if ($this->htmlOutputEnabled && !($this->getSession()->getDriver() instanceof GoutteDriver)) {
@@ -811,6 +820,12 @@ protected function submitForm(array $edit, $submit, $form_html_id = NULL) {
     // Ensure that any changes to variables in the other thread are picked up.
     $this->refreshVariables();

+    // Check if there are any meta refresh redirects (like Batch API pages).
+    if ($this->checkForMetaRefresh()) {
+      // We are finished with all meta refresh redirects, so reset the counter.
+      $this->metaRefreshCount = 0;
+    }
+
     // Log only for JavascriptTestBase tests because for Goutte we log with
     // ::getResponseLogHandler.
     if ($this->htmlOutputEnabled && !($this->getSession()->getDriver() instanceof GoutteDriver)) {
diff --git a/core/tests/Drupal/Tests/Traits/Core/MetaRefreshTrait.php b/core/tests/Drupal/Tests/Traits/Core/MetaRefreshTrait.php
new file mode 100644
index 0000000..b891ec2
--- /dev/null
+++ b/core/tests/Drupal/Tests/Traits/Core/MetaRefreshTrait.php
@@ -0,0 +1,48 @@
+<?php
+
+namespace Drupal\Tests\Traits\Core;
+
+use Drupal\Component\Utility\Html;
+
+/**
+ * Allows tests redirect on meta refresh tag found in page.
+ */
+trait MetaRefreshTrait {
+
+  /**
+   * The number of meta refresh redirects to follow, or NULL if unlimited.
+   *
+   * @var null|int
+   */
+  protected $maximumMetaRefreshCount = NULL;
+
+  /**
+   * The number of meta refresh redirects followed during ::drupalGet().
+   *
+   * @var int
+   */
+  protected $metaRefreshCount = 0;
+
+  /**
+   * Checks for meta refresh tag and if found call drupalGet() recursively.
+   *
+   * This function looks for the http-equiv attribute to be set to "Refresh" and
+   * is case-sensitive.
+   *
+   * @return string|false
+   *   Either the new page content or FALSE.
+   */
+  protected function checkForMetaRefresh() {
+    $refresh = $this->cssSelect('meta[http-equiv="Refresh"]');
+    if (!empty($refresh) && (!isset($this->maximumMetaRefreshCount) || $this->metaRefreshCount < $this->maximumMetaRefreshCount)) {
+      // Parse the content attribute of the meta tag for the format:
+      // "[delay]: URL=[page_to_redirect_to]".
+      if (preg_match('/\d+;\s*URL=(?<url>.*)/i', $refresh[0]->getAttribute('content'), $match)) {
+        $this->metaRefreshCount++;
+        return $this->drupalGet($this->getAbsoluteUrl(Html::decodeEntities($match['url'])));
+      }
+    }
+    return FALSE;
+  }
+
+}
