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 dbdf18c3d2..9b1d4dd4ad 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 6b90cc804c..2d7c7ae01c 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/tests/Drupal/Tests/BrowserTestBase.php b/core/tests/Drupal/Tests/BrowserTestBase.php
index 5751368f61..3b706e60d6 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 0000000000..b891ec20de
--- /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;
+  }
+
+}
