diff --git a/modules/redirect_404/src/Tests/Redirect404TestBase.php b/modules/redirect_404/src/Tests/Redirect404TestBase.php
deleted file mode 100644
index 2842aeb..0000000
--- a/modules/redirect_404/src/Tests/Redirect404TestBase.php
+++ /dev/null
@@ -1,144 +0,0 @@
-<?php
-
-namespace Drupal\redirect_404\Tests;
-
-use Drupal\Component\Render\FormattableMarkup;
-use Drupal\simpletest\WebTestBase;
-
-/**
- * This class provides methods specifically for testing redirect 404 paths.
- */
-abstract class Redirect404TestBase extends WebTestBase {
-
-  /**
-   * Modules to enable.
-   *
-   * @var array
-   */
-  public static $modules = [
-    'redirect_404',
-    'node',
-    'path',
-  ];
-
-  /**
-   * Permissions for the admin user.
-   *
-   * @var array
-   */
-  protected $adminPermissions = [
-    'administer redirects',
-    'administer redirect settings',
-    'access content',
-    'bypass node access',
-    'create url aliases',
-    'administer url aliases',
-  ];
-
-  /**
-   * A user with administrative permissions.
-   *
-   * @var \Drupal\user\UserInterface
-   */
-  protected $adminUser;
-
-  /**
-   * {@inheritdoc}
-   */
-  public function setUp() {
-    parent::setUp();
-
-    // Create an admin user.
-    $this->adminUser = $this->drupalCreateUser($this->adminPermissions);
-    $this->drupalLogin($this->adminUser);
-
-    $this->drupalCreateContentType(['type' => 'page', 'name' => 'Page']);
-  }
-
-  /**
-   * Passes if the language of the 404 path IS found on the loaded page.
-   *
-   * Because assertText() checks also in the Language select options, this
-   * specific assertion in the redirect 404 table body is needed.
-   *
-   * @param string $language
-   *   The language to assert in the redirect 404 table body.
-   * @param string $body
-   *   (optional) The table body xpath where to assert the language. Defaults
-   *   to '//table/tbody'.
-   * @param string $message
-   *   (optional) A message to display with the assertion. Do not translate
-   *   messages: use \Drupal\Component\Utility\SafeMarkup::format() to embed
-   *   variables in the message text, not t(). If left blank, a default message
-   *   will be displayed.
-   *
-   * @return bool
-   *   TRUE on pass, FALSE on fail.
-   */
-  protected function assertLanguageInTableBody($language, $body = '//table/tbody', $message = '') {
-    return $this->assertLanguageInTableBodyHelper($language, $body, $message, FALSE);
-  }
-
-  /**
-   * Passes if the language of the 404 path is NOT found on the loaded page.
-   *
-   * Because assertText() checks also in the Language select options, this
-   * specific assertion in the redirect 404 table body is needed.
-   *
-   * @param string $language
-   *   The language to assert in the redirect 404 table body.
-   * @param string $body
-   *   (optional) The table body xpath where to assert the language. Defaults
-   *   to '//table/tbody'.
-   * @param string $message
-   *   (optional) A message to display with the assertion. Do not translate
-   *   messages: use \Drupal\Component\Utility\SafeMarkup::format() to embed
-   *   variables in the message text, not t(). If left blank, a default message
-   *   will be displayed.
-   *
-   * @return bool
-   *   TRUE on pass, FALSE on fail.
-   */
-  protected function assertNoLanguageInTableBody($language, $body = '//table/tbody', $message = '') {
-    return $this->assertLanguageInTableBodyHelper($language, $body, $message, TRUE);
-  }
-
-  /**
-   * Helper for assertLanguageInTableBody and assertNoLanguageInTableBody.
-   *
-   * @param array $language
-   *   The language to assert in the redirect 404 table body.
-   * @param string $body
-   *   (optional) The table body xpath where to assert the language. Defaults
-   *   to '//table/tbody'.
-   * @param string $message
-   *   (optional) A message to display with the assertion. Do not translate
-   *   messages: use \Drupal\Component\Utility\SafeMarkup::format() to embed
-   *   variables in the message text, not t(). If left blank, a default message
-   *   will be displayed.
-   * @param bool $not_exists
-   *   (optional) TRUE if this language should not exist, FALSE if it should.
-   *   Defaults to TRUE.
-   *
-   * @return bool
-   *   TRUE on pass, FALSE on fail.
-   */
-  protected function assertLanguageInTableBodyHelper($language, $body = '//table/tbody', $message = '', $not_exists = TRUE) {
-    if (!$message) {
-      if (!$not_exists) {
-        $message = new FormattableMarkup('Language "@language" found in 404 table.', ['@language' => $language]);
-      }
-      else {
-        $message = new FormattableMarkup('Language "@language" not found in 404 table.', ['@language' => $language]);
-      }
-    }
-
-    if ($not_exists) {
-      return $this->assertFalse(strpos($this->xpath($body)[0]->asXML(), $language), $message);
-    }
-    else {
-      return $this->assertTrue(strpos($this->xpath($body)[0]->asXML(), $language), $message);
-    }
-  }
-
-}
diff --git a/modules/redirect_404/src/Tests/Fix404RedirectUILanguageTest.php b/modules/redirect_404/tests/src/Functional/Fix404RedirectUILanguageTest.php
similarity index 95%
rename from modules/redirect_404/src/Tests/Fix404RedirectUILanguageTest.php
rename to modules/redirect_404/tests/src/Functional/Fix404RedirectUILanguageTest.php
index 89a14d2..eb966b9 100644
--- a/modules/redirect_404/src/Tests/Fix404RedirectUILanguageTest.php
+++ b/modules/redirect_404/tests/src/Functional/Fix404RedirectUILanguageTest.php
@@ -1,12 +1,12 @@
 <?php
 
-namespace Drupal\redirect_404\Tests;
+namespace Drupal\Tests\redirect_404\Functional;
 
 use Drupal\Component\Utility\UrlHelper;
 use Drupal\Core\Language\LanguageInterface;
 use Drupal\Core\Url;
 use Drupal\language\Entity\ConfigurableLanguage;
-use Drupal\redirect\Tests\AssertRedirectTrait;
+use Drupal\Tests\redirect\Functional\AssertRedirectTrait;
 
 /**
  * UI tests for redirect_404 module with language and content translation.
@@ -105,7 +105,7 @@ class Fix404RedirectUILanguageTest extends Redirect404TestBase {
     $this->assertUrl('admin/config/search/redirect/404');
     $this->assertText('There are no 404 errors to fix.');
     // Check if the redirect works as expected.
-    $this->assertRedirect('fr/testing', 'fr/node', 'HTTP/1.1 301 Moved Permanently');
+    $this->assertRedirect('fr/testing', 'fr/node', 301);
 
     // Test removing a redirect assignment, visit again the non existing page.
     $this->drupalGet('admin/config/search/redirect');
@@ -136,7 +136,7 @@ class Fix404RedirectUILanguageTest extends Redirect404TestBase {
     $this->drupalGet('admin/config/search/redirect');
     $this->assertLanguageInTableBody('Spanish');
     // Check if the redirect works as expected.
-    $this->assertRedirect('es/testing', 'es/node', 'HTTP/1.1 301 Moved Permanently');
+    $this->assertRedirect('es/testing', 'es/node', 301);
 
     // Visit multiple non existing pages to test the Redirect 404 View.
     $this->drupalGet('testing1');
@@ -197,7 +197,7 @@ class Fix404RedirectUILanguageTest extends Redirect404TestBase {
     $this->assertLanguageInTableBody('Spanish');
     $this->assertLanguageInTableBody('English');
     // Check if the redirect works as expected.
-    $this->assertRedirect('/testing1', '/node', 'HTTP/1.1 301 Moved Permanently');
+    $this->assertRedirect('/testing1', '/node', 301);
   }
 
 }
diff --git a/modules/redirect_404/src/Tests/Fix404RedirectUITest.php b/modules/redirect_404/tests/src/Functional/Fix404RedirectUITest.php
similarity index 88%
rename from modules/redirect_404/src/Tests/Fix404RedirectUITest.php
rename to modules/redirect_404/tests/src/Functional/Fix404RedirectUITest.php
index fb8dd73..2045bf8 100644
--- a/modules/redirect_404/src/Tests/Fix404RedirectUITest.php
+++ b/modules/redirect_404/tests/src/Functional/Fix404RedirectUITest.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Drupal\redirect_404\Tests;
+namespace Drupal\Tests\redirect_404\Functional;
 
 use Drupal\Component\Utility\UrlHelper;
 use Drupal\Core\Url;
@@ -158,10 +158,9 @@ class Fix404RedirectUITest extends Redirect404TestBase {
     $this->clickLink('Ignore');
     $this->assertUrl('admin/config/search/redirect/settings?ignore=' . $path_to_ignore . $destination);
     $this->assertText('Resolved the path ' . $path_to_ignore . ' in the database. Please check the ignored list and save the settings.');
-    $xpath = $this->xpath('//*[@id="edit-ignore-pages"]')[0]->asXML();
-    $this->assertTrue(strpos($xpath, $node_to_ignore), $node_to_ignore . " in 'Path to ignore' found");
-    $this->assertTrue(strpos($xpath, $terms_to_ignore), $terms_to_ignore . " in 'Path to ignore' found");
-    $this->assertTrue(strpos($xpath, $path_to_ignore), $path_to_ignore . " in 'Path to ignore' found");
+    $this->assertSession()->elementContains('css', '#edit-ignore-pages', $node_to_ignore);
+    $this->assertSession()->elementContains('css', '#edit-ignore-pages', $terms_to_ignore);
+    $this->assertSession()->elementContains('css', '#edit-ignore-pages', $path_to_ignore);
 
     // Save the path with wildcard, but omitting the leading slash.
     $nodes_to_ignore = 'node/*';
@@ -175,12 +174,12 @@ class Fix404RedirectUITest extends Redirect404TestBase {
 
     // Go back to the settings to check the 'Path to ignore' configurations.
     $this->drupalGet('admin/config/search/redirect/settings');
-    $xpath = $this->xpath('//*[@id="edit-ignore-pages"]')[0]->asXML();
+    $xpath = $this->xpath('//*[@id="edit-ignore-pages"]')[0]->getHtml();
     // Check that the new page to ignore has been saved with leading slash.
-    $this->assertTrue(strpos($xpath, '/' . $nodes_to_ignore), '/' . $nodes_to_ignore . " in 'Path to ignore' found");
-    $this->assertTrue(strpos($xpath, $terms_to_ignore), $terms_to_ignore . " in 'Path to ignore' found");
-    $this->assertFalse(strpos($xpath, $node_to_ignore), $node_to_ignore . " in 'Path to ignore' found");
-    $this->assertFalse(strpos($xpath, $path_to_ignore), $path_to_ignore . " in 'Path to ignore' found");
+    $this->assertSession()->elementContains('css', '#edit-ignore-pages', '/'. $nodes_to_ignore);
+    $this->assertSession()->elementContains('css', '#edit-ignore-pages', $terms_to_ignore);
+    $this->assertSession()->elementNotContains('css', '#edit-ignore-pages', $node_to_ignore);
+    $this->assertSession()->elementNotContains('css', '#edit-ignore-pages', $path_to_ignore);
   }
 
 }
diff --git a/modules/redirect_404/src/Tests/Redirect404LogSuppressorTest.php b/modules/redirect_404/tests/src/Functional/Redirect404LogSuppressorTest.php
similarity index 97%
rename from modules/redirect_404/src/Tests/Redirect404LogSuppressorTest.php
rename to modules/redirect_404/tests/src/Functional/Redirect404LogSuppressorTest.php
index ef021b5..2737f68 100644
--- a/modules/redirect_404/src/Tests/Redirect404LogSuppressorTest.php
+++ b/modules/redirect_404/tests/src/Functional/Redirect404LogSuppressorTest.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Drupal\redirect_404\Tests;
+namespace Drupal\Tests\redirect_404\Functional;
 
 /**
  * Tests suppressing 404 logs if the suppress_404 setting is enabled.
diff --git a/modules/redirect_404/tests/src/Functional/Redirect404TestBase.php b/modules/redirect_404/tests/src/Functional/Redirect404TestBase.php
new file mode 100644
index 0000000..df66959
--- /dev/null
+++ b/modules/redirect_404/tests/src/Functional/Redirect404TestBase.php
@@ -0,0 +1,84 @@
+<?php
+
+namespace Drupal\Tests\redirect_404\Functional;
+
+use Drupal\Component\Render\FormattableMarkup;
+use Drupal\Tests\BrowserTestBase;
+
+/**
+ * This class provides methods specifically for testing redirect 404 paths.
+ */
+abstract class Redirect404TestBase extends BrowserTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = [
+    'redirect_404',
+    'node',
+    'path',
+  ];
+
+  /**
+   * Permissions for the admin user.
+   *
+   * @var array
+   */
+  protected $adminPermissions = [
+    'administer redirects',
+    'administer redirect settings',
+    'access content',
+    'bypass node access',
+    'create url aliases',
+    'administer url aliases',
+  ];
+
+  /**
+   * A user with administrative permissions.
+   *
+   * @var \Drupal\user\UserInterface
+   */
+  protected $adminUser;
+
+  /**
+   * {@inheritdoc}
+   */
+  public function setUp() {
+    parent::setUp();
+
+    // Create an admin user.
+    $this->adminUser = $this->drupalCreateUser($this->adminPermissions);
+    $this->drupalLogin($this->adminUser);
+
+    $this->drupalCreateContentType(['type' => 'page', 'name' => 'Page']);
+  }
+
+  /**
+   * Passes if the language of the 404 path IS found on the loaded page.
+   *
+   * Because assertText() checks also in the Language select options, this
+   * specific assertion in the redirect 404 table body is needed.
+   *
+   * @param string $language
+   *   The language to assert in the redirect 404 table body.
+   */
+  protected function assertLanguageInTableBody($language) {
+    $this->assertSession()->elementContains('css', 'table tbody', $language);
+  }
+
+  /**
+   * Passes if the language of the 404 path is NOT found on the loaded page.
+   *
+   * Because assertText() checks also in the Language select options, this
+   * specific assertion in the redirect 404 table body is needed.
+   *
+   * @param string $language
+   *   The language to assert in the redirect 404 table body.
+   */
+  protected function assertNoLanguageInTableBody($language) {
+    $this->assertSession()->elementNotContains('css', 'table tbody', $language);
+  }
+
+}
diff --git a/modules/redirect_domain/src/Tests/RedirectDomainUITest.php b/modules/redirect_domain/tests/src/FunctionalJavascript/RedirectDomainUITest.php
similarity index 66%
rename from modules/redirect_domain/src/Tests/RedirectDomainUITest.php
rename to modules/redirect_domain/tests/src/FunctionalJavascript/RedirectDomainUITest.php
index d50791f..dec1bab 100644
--- a/modules/redirect_domain/src/Tests/RedirectDomainUITest.php
+++ b/modules/redirect_domain/tests/src/FunctionalJavascript/RedirectDomainUITest.php
@@ -1,15 +1,15 @@
 <?php
 
-namespace Drupal\redirect_domain\Tests;
+namespace Drupal\Tests\redirect_domain\FunctionalJavascript;
 
-use Drupal\simpletest\WebTestBase;
+use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
 
 /**
  * Tests the UI for domain redirect.
  *
  * @group redirect_domain
  */
-class RedirectDomainUITest extends WebTestBase {
+class RedirectDomainUITest extends WebDriverTestBase {
 
   /**
    * Modules to enable.
@@ -38,18 +38,17 @@ class RedirectDomainUITest extends WebTestBase {
     $this->assertFieldByName('redirects[0][destination]');
 
     // Add another field for new domain redirect.
-    $this->drupalPostAjaxForm(NULL, [], ['op' => t('Add another')]);
+    $page = $this->getSession()->getPage();
+    $page->pressButton('Add another');
 
     // Add two new domain redirects.
-    $edit = [
-      'redirects[0][from]' => 'foo.example.org',
-      'redirects[0][sub_path]' => '//sub-path',
-      'redirects[0][destination]' => 'www.example.org/foo',
-      'redirects[1][from]' => 'bar.example.org',
-      'redirects[1][sub_path]' => '',
-      'redirects[1][destination]' => 'www.example.org/bar',
-    ];
-    $this->drupalPostForm(NULL, $edit, t('Save'));
+    $page->fillField('redirects[0][from]', 'foo.example.org');
+    $page->fillField('redirects[0][sub_path]', '//sub-path');
+    $page->fillField('redirects[0][destination]', 'www.example.org/foo');
+    $page->fillField('redirects[1][from]', 'bar.example.org');
+    $page->fillField('redirects[1][sub_path]', '');
+    $page->fillField('redirects[1][destination]', 'www.example.org/bar');
+    $page->pressButton('Save');
 
     // Check the new domain redirects.
     $this->assertFieldByName('redirects[0][from]', 'foo.example.org');
diff --git a/src/Plugin/Field/FieldWidget/RedirectSourceWidget.php b/src/Plugin/Field/FieldWidget/RedirectSourceWidget.php
index 3555912..0aae50c 100644
--- a/src/Plugin/Field/FieldWidget/RedirectSourceWidget.php
+++ b/src/Plugin/Field/FieldWidget/RedirectSourceWidget.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\redirect\Plugin\Field\FieldWidget;
 
+use Drupal\Component\Utility\NestedArray;
 use Drupal\Component\Utility\UrlHelper;
 use Drupal\Core\Field\FieldItemListInterface;
 use Drupal\Core\Field\WidgetBase;
diff --git a/src/Tests/AssertRedirectTrait.php b/tests/src/Functional/AssertRedirectTrait.php
similarity index 50%
rename from src/Tests/AssertRedirectTrait.php
rename to tests/src/Functional/AssertRedirectTrait.php
index ae93688..a83e6a4 100644
--- a/src/Tests/AssertRedirectTrait.php
+++ b/tests/src/Functional/AssertRedirectTrait.php
@@ -1,9 +1,9 @@
 <?php
 
-namespace Drupal\redirect\Tests;
+namespace Drupal\Tests\redirect\Functional;
 
-use Drupal\Component\Utility\SafeMarkup;
 use Drupal\Core\Url;
+use GuzzleHttp\Exception\ClientException;
 
 /**
  * Asserts the redirect from a given path to the expected destination path.
@@ -18,19 +18,30 @@ trait AssertRedirectTrait {
    * @param $expected_ending_url
    *   The path where we expect it to redirect. If NULL value provided, no
    *   redirect is expected.
-   * @param string $expected_ending_status
+   * @param int $expected_ending_status
    *   The status we expect to get with the first request.
+   *
+   * @return \Psr\Http\Message\ResponseInterface
+   *   The HTTP response.
    */
-  public function assertRedirect($path, $expected_ending_url, $expected_ending_status = 'HTTP/1.1 301 Moved Permanently') {
-    $this->drupalHead($path);
-    $headers = $this->drupalGetHeaders(TRUE);
+  public function assertRedirect($path, $expected_ending_url, $expected_ending_status = 301) {
+    $client = $this->getSession()->getDriver()->getClient()->getClient();
+    /** @var \Psr\Http\Message\ResponseInterface $response */
+    $url = $this->getAbsoluteUrl($path);
+    //$url = $this->getAbsoluteUrl($path) . '?XDEBUG_SESSION_START=1';
+    //echo "$path: $url\n";
+    try {
+      $response = $client->request('GET', $url, ['allow_redirects' => false]);
+    } catch (ClientException $e) {
+      $this->assertEquals($expected_ending_status, $e->getResponse()->getStatusCode());
+      return $e->getResponse();
+    }
 
-    $ending_url = isset($headers[0]['location']) ? $headers[0]['location'] : NULL;
-    $message = SafeMarkup::format('Testing redirect from %from to %to. Ending url: %url', [
-      '%from' => $path,
-      '%to' => $expected_ending_url,
-      '%url' => $ending_url,
-    ]);
+    $this->assertEquals($expected_ending_status, $response->getStatusCode());
+
+    $ending_url = $response->getHeader('location');
+    $ending_url = $ending_url ? $ending_url[0] : NULL;
+    $message = "Testing redirect from $path to $expected_ending_url. Ending url: $ending_url";
 
     if ($expected_ending_url == '<front>') {
       $expected_ending_url = Url::fromUri('base:')->setAbsolute()->toString();
@@ -46,8 +57,7 @@ trait AssertRedirectTrait {
     }
 
     $this->assertEqual($expected_ending_url, $ending_url, $message);
-
-    $this->assertEqual($headers[0][':status'], $expected_ending_status);
+    return $response;
   }
 
 }
diff --git a/src/Tests/GlobalRedirectTest.php b/tests/src/Functional/GlobalRedirectTest.php
similarity index 73%
rename from src/Tests/GlobalRedirectTest.php
rename to tests/src/Functional/GlobalRedirectTest.php
index 67dce83..f3299b0 100644
--- a/src/Tests/GlobalRedirectTest.php
+++ b/tests/src/Functional/GlobalRedirectTest.php
@@ -1,20 +1,22 @@
 <?php
 
-namespace Drupal\redirect\Tests;
+namespace Drupal\Tests\redirect\Functional;
 
 use Drupal\Component\Utility\SafeMarkup;
 use Drupal\Core\Language\Language;
-use Drupal\simpletest\WebTestBase;
 use Drupal\language\Entity\ConfigurableLanguage;
 use Drupal\taxonomy\Entity\Term;
 use Drupal\taxonomy\Entity\Vocabulary;
+use Drupal\Tests\BrowserTestBase;
 
 /**
  * Global redirect test cases.
  *
  * @group redirect
  */
-class GlobalRedirectTest extends WebTestBase {
+class GlobalRedirectTest extends BrowserTestBase {
+
+  use AssertRedirectTrait;
 
   /**
    * Modules to enable.
@@ -137,10 +139,10 @@ class GlobalRedirectTest extends WebTestBase {
 
     // First test that the good stuff can be switched off.
     $this->config->set('route_normalizer_enabled', FALSE)->save();
-    $this->assertRedirect('index.php/node/' . $this->node->id(), NULL, 'HTTP/1.1 200 OK');
-    $this->assertRedirect('index.php/test-node', NULL, 'HTTP/1.1 200 OK');
-    $this->assertRedirect('test-node/', NULL, 'HTTP/1.1 200 OK');
-    $this->assertRedirect('Test-node/', NULL, 'HTTP/1.1 200 OK');
+    $this->assertRedirect('index.php/node/' . $this->node->id(), NULL, 200);
+    $this->assertRedirect('index.php/test-node', NULL, 200);
+    $this->assertRedirect('test-node/', NULL, 200);
+    $this->assertRedirect('Test-node/', NULL, 200);
 
     $this->config->set('route_normalizer_enabled', TRUE)->save();
 
@@ -170,7 +172,7 @@ class GlobalRedirectTest extends WebTestBase {
 
     // Test the access checking.
     $this->config->set('access_check', TRUE)->save();
-    $this->assertRedirect('admin/config/system/site-information', NULL, 'HTTP/1.1 403 Forbidden');
+    $this->assertRedirect('admin/config/system/site-information', NULL, 403);
 
     $this->config->set('access_check', FALSE)->save();
     // @todo - here it seems that the access check runs prior to our redirecting
@@ -185,7 +187,7 @@ class GlobalRedirectTest extends WebTestBase {
     $this->assertRedirect('Test-node?', 'test-node');
 
     // Test alias normalization still works without trailing ?.
-    $this->assertRedirect('test-node', NULL, 'HTTP/1.1 200 OK');
+    $this->assertRedirect('test-node', NULL, 200);
     $this->assertRedirect('Test-node', 'test-node');
 
     // Login as user with admin privileges.
@@ -199,7 +201,7 @@ class GlobalRedirectTest extends WebTestBase {
     $this->assertRedirect('Test-node', 'test-node');
 
     $this->config->set('ignore_admin_path', TRUE)->save();
-    $this->assertRedirect('admin/config/system/site-information', NULL, 'HTTP/1.1 200 OK');
+    $this->assertRedirect('admin/config/system/site-information', NULL, 200);
 
     // Test alias normalization again with ignore_admin_path true.
     $this->assertRedirect('Test-node', 'test-node');
@@ -238,61 +240,4 @@ class GlobalRedirectTest extends WebTestBase {
     $this->assertRedirect('es/node/' . $spanish_node->id(), 'es/spanish-test-node');
   }
 
-  /**
-   * Asserts the redirect from $path to the $expected_ending_url.
-   *
-   * @param string $path
-   *   The request path.
-   * @param $expected_ending_url
-   *   The path where we expect it to redirect. If NULL value provided, no
-   *   redirect is expected.
-   * @param string $expected_ending_status
-   *   The status we expect to get with the first request.
-   */
-  public function assertRedirect($path, $expected_ending_url, $expected_ending_status = 'HTTP/1.1 301 Moved Permanently') {
-    $this->drupalHead($GLOBALS['base_url'] . '/' . $path);
-    $headers = $this->drupalGetHeaders(TRUE);
-
-    $ending_url = isset($headers[0]['location']) ? $headers[0]['location'] : NULL;
-    $message = SafeMarkup::format('Testing redirect from %from to %to. Ending url: %url', [
-      '%from' => $path,
-      '%to' => $expected_ending_url,
-      '%url' => $ending_url,
-    ]);
-
-
-    if ($expected_ending_url == '<front>') {
-      $expected_ending_url = $GLOBALS['base_url'] . '/';
-    }
-    elseif (!empty($expected_ending_url)) {
-      $expected_ending_url = $GLOBALS['base_url'] . '/' . $expected_ending_url;
-    }
-    else {
-      $expected_ending_url = NULL;
-    }
-
-    $this->assertEqual($expected_ending_url, $ending_url);
-
-    $this->assertEqual($headers[0][':status'], $expected_ending_status);
-  }
-
-  /**
-   * @inheritdoc}
-   */
-  protected function drupalHead($path, array $options = [], array $headers = []) {
-    // Always just use getAbsolutePath() so that generating the link does not
-    // alter special requests.
-    $url = $this->getAbsoluteUrl($path);
-    $out = $this->curlExec([CURLOPT_NOBODY => TRUE, CURLOPT_URL => $url, CURLOPT_HTTPHEADER => $headers]);
-    // Ensure that any changes to variables in the other thread are picked up.
-    $this->refreshVariables();
-
-    if ($this->dumpHeaders) {
-      $this->verbose('GET request to: ' . $path .
-        '<hr />Ending URL: ' . $this->getUrl() .
-        '<hr />Headers: <pre>' . Html::escape(var_export(array_map('trim', $this->headers), TRUE)) . '</pre>');
-    }
-
-    return $out;
-  }
 }
diff --git a/src/Tests/RedirectUILanguageTest.php b/tests/src/Functional/RedirectUILanguageTest.php
similarity index 81%
rename from src/Tests/RedirectUILanguageTest.php
rename to tests/src/Functional/RedirectUILanguageTest.php
index 4f9770c..372576c 100644
--- a/src/Tests/RedirectUILanguageTest.php
+++ b/tests/src/Functional/RedirectUILanguageTest.php
@@ -1,6 +1,7 @@
 <?php
 
-namespace Drupal\redirect\Tests;
+namespace Drupal\Tests\redirect\Functional;
+
 use Drupal\language\Entity\ConfigurableLanguage;
 
 /**
@@ -62,13 +63,13 @@ class RedirectUILanguageTest extends RedirectUITest {
     ], t('Save'));
 
     // Check redirect for english.
-    $this->assertRedirect('langpath', '/user', 'HTTP/1.1 301 Moved Permanently');
+    $this->assertRedirect('langpath', '/user', 301);
 
     // Check redirect for germany.
-    $this->assertRedirect('de/langpath', '/de', 'HTTP/1.1 301 Moved Permanently');
+    $this->assertRedirect('de/langpath', '/de', 301);
 
     // Check no redirect for spanish.
-    $this->assertRedirect('es/langpath', NULL, 'HTTP/1.1 404 Not Found');
+    $this->assertRedirect('es/langpath', NULL, 404);
   }
 
   /**
@@ -85,10 +86,10 @@ class RedirectUILanguageTest extends RedirectUITest {
     ], t('Save'));
 
     // Check redirect for english.
-    $this->assertRedirect('langpath', '/user', 'HTTP/1.1 301 Moved Permanently');
+    $this->assertRedirect('langpath', '/user', 301);
 
     // Check redirect for spanish.
-    $this->assertRedirect('es/langpath', '/es/user', 'HTTP/1.1 301 Moved Permanently');
+    $this->assertRedirect('es/langpath', '/es/user', 301);
   }
 
   /**
@@ -105,10 +106,10 @@ class RedirectUILanguageTest extends RedirectUITest {
     ], t('Save'));
 
     // Check redirect for english.
-    $this->assertRedirect('langpath', '/user', 'HTTP/1.1 301 Moved Permanently');
+    $this->assertRedirect('langpath', '/user', 301);
 
     // Check that redirect for Germany is not working.
-    $this->assertRedirect('de/langpath', NULL, 'HTTP/1.1 404 Not Found');
+    $this->assertRedirect('de/langpath', NULL, 404);
 
     // Edit the redirect and change the language.
     $this->drupalGet('admin/config/search/redirect');
@@ -116,10 +117,10 @@ class RedirectUILanguageTest extends RedirectUITest {
     $this->drupalPostForm(NULL, ['language[0][value]' => 'de'], t('Save'));
 
     // Check redirect for english is NOT working now.
-    $this->assertRedirect('langpath', NULL, 'HTTP/1.1 404 Not Found');
+    $this->assertRedirect('langpath', NULL, 404);
 
     // Check that redirect for Germany is now working.
-    $this->assertRedirect('de/langpath', '/de/user', 'HTTP/1.1 301 Moved Permanently');
+    $this->assertRedirect('de/langpath', '/de/user', 301);
   }
 
 }
diff --git a/src/Tests/RedirectUITest.php b/tests/src/Functional/RedirectUITest.php
similarity index 53%
rename from src/Tests/RedirectUITest.php
rename to tests/src/Functional/RedirectUITest.php
index 794565e..08fad49 100644
--- a/src/Tests/RedirectUITest.php
+++ b/tests/src/Functional/RedirectUITest.php
@@ -1,21 +1,21 @@
 <?php
 
-namespace Drupal\redirect\Tests;
+namespace Drupal\Tests\redirect\Functional;
 
 use Drupal\Component\Utility\SafeMarkup;
 use Drupal\Core\Language\Language;
 use Drupal\Core\Logger\RfcLogLevel;
 use Drupal\Core\Url;
-use Drupal\simpletest\WebTestBase;
 use Drupal\taxonomy\Entity\Term;
 use Drupal\taxonomy\Entity\Vocabulary;
+use Drupal\Tests\BrowserTestBase;
 
 /**
  * UI tests for redirect module.
  *
  * @group redirect
  */
-class RedirectUITest extends WebTestBase {
+class RedirectUITest extends BrowserTestBase {
 
   use AssertRedirectTrait;
 
@@ -58,183 +58,7 @@ class RedirectUITest extends WebTestBase {
 
     $this->repository = \Drupal::service('redirect.repository');
 
-    $this->storage = $this->container->get('entity.manager')->getStorage('redirect');
-  }
-
-  /**
-   * Test the redirect UI.
-   */
-  public function testRedirectUI() {
-    $this->drupalLogin($this->adminUser);
-
-    // Test populating the redirect form with predefined values.
-    $this->drupalGet('admin/config/search/redirect/add', ['query' => [
-      'source' => 'non-existing',
-      'source_query' => ['key' => 'val', 'key1' => 'val1'],
-      'redirect' => 'node',
-      'redirect_options' => ['query' => ['key' => 'val', 'key1' => 'val1']],
-    ]]);
-    $this->assertFieldByName('redirect_source[0][path]', 'non-existing?key=val&key1=val1');
-    $this->assertFieldByName('redirect_redirect[0][uri]', '/node?key=val&key1=val1');
-
-    // Test creating a new redirect via UI.
-    $this->drupalPostForm('admin/config/search/redirect/add', [
-      'redirect_source[0][path]' => 'non-existing',
-      'redirect_redirect[0][uri]' => '/node',
-    ], t('Save'));
-
-    // Try to find the redirect we just created.
-    $redirect = $this->repository->findMatchingRedirect('non-existing');
-    $this->assertEqual($redirect->getSourceUrl(), Url::fromUri('base:non-existing')->toString());
-    $this->assertEqual($redirect->getRedirectUrl()->toString(), Url::fromUri('base:node')->toString());
-
-    // After adding the redirect we should end up in the list. Check if the
-    // redirect is listed.
-    $this->assertUrl('admin/config/search/redirect');
-    $this->assertText('non-existing');
-    $this->assertLink(Url::fromUri('base:node')->toString());
-    $this->assertText(t('Not specified'));
-
-    // Test the edit form and update action.
-    $this->clickLink(t('Edit'));
-    $this->assertFieldByName('redirect_source[0][path]', 'non-existing');
-    $this->assertFieldByName('redirect_redirect[0][uri]', '/node');
-    $this->assertFieldByName('status_code', $redirect->getStatusCode());
-
-    // Append a query string to see if we handle query data properly.
-    $this->drupalPostForm(NULL, [
-      'redirect_source[0][path]' => 'non-existing?key=value',
-    ], t('Save'));
-
-    // Check the location after update and check if the value has been updated
-    // in the list.
-    $this->assertUrl('admin/config/search/redirect');
-    $this->assertText('non-existing?key=value');
-
-    // The path field should not contain the query string and therefore we
-    // should be able to load the redirect using only the url part without
-    // query.
-    $this->storage->resetCache();
-    $redirects = $this->repository->findBySourcePath('non-existing');
-    $redirect = array_shift($redirects);
-    $this->assertEqual($redirect->getSourceUrl(), Url::fromUri('base:non-existing', ['query' => ['key' => 'value']])->toString());
-
-    // Test the source url hints.
-    // The hint about an existing base path.
-    $this->drupalPostAjaxForm('admin/config/search/redirect/add', [
-      'redirect_source[0][path]' => 'non-existing?key=value',
-    ], 'redirect_source[0][path]');
-    $this->assertRaw(t('The base source path %source is already being redirected. Do you want to <a href="@edit-page">edit the existing redirect</a>?',
-      ['%source' => 'non-existing?key=value', '@edit-page' => $redirect->url('edit-form')]));
-
-    // The hint about a valid path.
-    $this->drupalPostAjaxForm('admin/config/search/redirect/add', [
-      'redirect_source[0][path]' => 'node',
-    ], 'redirect_source[0][path]');
-    $this->assertRaw(t('The source path %path is likely a valid path. It is preferred to <a href="@url-alias">create URL aliases</a> for existing paths rather than redirects.',
-      ['%path' => 'node', '@url-alias' => Url::fromRoute('path.admin_add')->toString()]));
-
-    // Test validation.
-    // Duplicate redirect.
-    $this->drupalPostForm('admin/config/search/redirect/add', [
-      'redirect_source[0][path]' => 'non-existing?key=value',
-      'redirect_redirect[0][uri]' => '/node',
-    ], t('Save'));
-    $this->assertRaw(t('The source path %source is already being redirected. Do you want to <a href="@edit-page">edit the existing redirect</a>?',
-      ['%source' => 'non-existing?key=value', '@edit-page' => $redirect->url('edit-form')]));
-
-    // Redirecting to itself.
-    $this->drupalPostForm('admin/config/search/redirect/add', [
-      'redirect_source[0][path]' => 'node',
-      'redirect_redirect[0][uri]' => '/node',
-    ], t('Save'));
-    $this->assertRaw(t('You are attempting to redirect the page to itself. This will result in an infinite loop.'));
-
-    // Redirecting the front page.
-    $this->drupalPostForm('admin/config/search/redirect/add', [
-      'redirect_source[0][path]' => '<front>',
-      'redirect_redirect[0][uri]' => '/node',
-    ], t('Save'));
-    $this->assertRaw(t('It is not allowed to create a redirect from the front page.'));
-
-    // Redirecting a url with fragment.
-    $this->drupalPostForm('admin/config/search/redirect/add', [
-      'redirect_source[0][path]' => 'page-to-redirect#content',
-      'redirect_redirect[0][uri]' => '/node',
-    ], t('Save'));
-    $this->assertRaw(t('The anchor fragments are not allowed.'));
-
-    // Adding path that starts with /
-    $this->drupalPostForm('admin/config/search/redirect/add', [
-      'redirect_source[0][path]' => '/page-to-redirect',
-      'redirect_redirect[0][uri]' => '/node',
-    ], t('Save'));
-    $this->assertRaw(t('The url to redirect from should not start with a forward slash (/).'));
-
-    // Test filters.
-    // Add a new redirect.
-    $this->drupalPostForm('admin/config/search/redirect/add', [
-      'redirect_source[0][path]' => 'test27',
-      'redirect_redirect[0][uri]' => '/node',
-    ], t('Save'));
-
-    // Filter  with non existing value.
-    $this->drupalGet('admin/config/search/redirect', [
-      'query' => [
-        'status_code' => '3',
-      ],
-    ]);
-
-    $rows = $this->xpath('//tbody/tr');
-    // Check if the list has no rows.
-    $this->assertTrue(count($rows) == 0);
-
-    // Filter with existing values.
-    $this->drupalGet('admin/config/search/redirect', [
-      'query' => [
-        'redirect_source__path' => 'test',
-        'status_code' => '2',
-      ],
-    ]);
-
-    $rows = $this->xpath('//tbody/tr');
-    // Check if the list has 1 row.
-    $this->assertTrue(count($rows) == 1);
-
-    $this->drupalGet('admin/config/search/redirect', [
-      'query' => [
-        'redirect_redirect__uri' => 'nod',
-      ],
-    ]);
-
-    $rows = $this->xpath('//tbody/tr');
-    // Check if the list has 2 rows.
-    $this->assertTrue(count($rows) == 2);
-
-    // Test the plural form of the bulk delete action.
-    $this->drupalGet('admin/config/search/redirect');
-    $edit = [
-      'redirect_bulk_form[0]' => TRUE,
-      'redirect_bulk_form[1]' => TRUE,
-    ];
-    $this->drupalPostForm(NULL, $edit, t('Apply to selected items'));
-    $this->assertText('Are you sure you want to delete these redirects?');
-    $this->clickLink('Cancel');
-
-    // Test the delete action.
-    $this->clickLink(t('Delete'));
-    $this->assertRaw(t('Are you sure you want to delete the URL redirect from %source to %redirect?',
-      ['%source' => Url::fromUri('base:non-existing', ['query' => ['key' => 'value']])->toString(), '%redirect' => Url::fromUri('base:node')->toString()]));
-    $this->drupalPostForm(NULL, [], t('Delete'));
-    $this->assertUrl('admin/config/search/redirect');
-
-    // Test the bulk delete action.
-    $this->drupalPostForm(NULL, ['redirect_bulk_form[0]' => TRUE], t('Apply to selected items'));
-    $this->assertText('Are you sure you want to delete this redirect?');
-    $this->assertText('test27');
-    $this->drupalPostForm(NULL, [], t('Delete'));
-
-    $this->assertText(t('There is no redirect yet.'));
+    $this->storage = \Drupal::entityTypeManager()->getStorage('redirect');
   }
 
   /**
@@ -400,20 +224,18 @@ class RedirectUITest extends WebTestBase {
     $redirect1->setStatusCode(301);
     $redirect1->save();
 
-    $this->assertRedirect('test-redirect', 'node');
-    $headers = $this->drupalGetHeaders(TRUE);
+    $response = $this->assertRedirect('test-redirect', 'node');
     // Note, self::assertCacheTag() cannot be used here since it only looks at
     // the final set of headers.
     $expected = 'http_response ' . implode(' ', $redirect1->getCacheTags());
-    $this->assertEqual($expected, $headers[0]['x-drupal-cache-tags'], 'Redirect cache tags properly set.');
+    $this->assertEqual($expected, $response->getHeader('x-drupal-cache-tags')[0], 'Redirect cache tags properly set.');
 
     // First request should be a cache MISS.
-    $this->assertEqual($headers[0]['x-drupal-cache'], 'MISS', 'First request to the redirect was not cached.');
+    $this->assertEqual($response->getHeader('x-drupal-cache')[0], 'MISS', 'First request to the redirect was not cached.');
 
     // Second request should be cached.
-    $this->assertRedirect('test-redirect', 'node');
-    $headers = $this->drupalGetHeaders(TRUE);
-    $this->assertEqual($headers[0]['x-drupal-cache'], 'HIT', 'The second request to the redirect was cached.');
+    $response = $this->assertRedirect('test-redirect', 'node');
+    $this->assertEqual($response->getHeader('x-drupal-cache')[0], 'HIT', 'The second request to the redirect was cached.');
 
     // Ensure that the redirect has been cleared from cache when deleted.
     $redirect1->delete();
diff --git a/tests/src/FunctionalJavascript/RedirectJavascriptTest.php b/tests/src/FunctionalJavascript/RedirectJavascriptTest.php
new file mode 100644
index 0000000..ffc38fd
--- /dev/null
+++ b/tests/src/FunctionalJavascript/RedirectJavascriptTest.php
@@ -0,0 +1,280 @@
+<?php
+
+namespace Drupal\Tests\redirect\FunctionalJavascript;
+
+use Drupal\Core\Url;
+use Drupal\FunctionalJavascriptTests\WebDriverTestBase;
+
+/**
+ * UI tests for redirect module.
+ *
+ * @group redirect
+ */
+class RedirectJavascriptTest extends WebDriverTestBase {
+
+  /**
+   * @var \Drupal\Core\Session\AccountInterface
+   */
+  protected $adminUser;
+
+  /**
+   * @var \Drupal\redirect\RedirectRepository
+   */
+  protected $repository;
+
+  /**
+   * @var \Drupal\Core\Entity\Sql\SqlContentEntityStorage
+   */
+  protected $storage;
+
+  /**
+   * {@inheritdoc}
+   */
+  public static $modules = ['redirect', 'node', 'path', 'dblog', 'views', 'taxonomy'];
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    parent::setUp();
+
+    $this->drupalCreateContentType(['type' => 'article', 'name' => 'Article']);
+    $this->adminUser = $this->drupalCreateUser(
+      [
+        'administer redirects',
+        'administer redirect settings',
+        'access content',
+        'bypass node access',
+        'create url aliases',
+        'administer taxonomy',
+        'administer url aliases',
+      ]
+    );
+
+    $this->repository = \Drupal::service('redirect.repository');
+
+    $this->storage = $this->container->get('entity.manager')->getStorage('redirect');
+  }
+
+  /**
+   * Test the redirect UI.
+   */
+  public function testRedirectUI() {
+    $this->drupalLogin($this->adminUser);
+
+    // Test populating the redirect form with predefined values.
+    $this->drupalGet(
+      'admin/config/search/redirect/add', [
+      'query' => [
+        'source' => 'non-existing',
+        'source_query' => ['key' => 'val', 'key1' => 'val1'],
+        'redirect' => 'node',
+        'redirect_options' => ['query' => ['key' => 'val', 'key1' => 'val1']],
+      ]
+    ]
+    );
+    $this->assertFieldByName('redirect_source[0][path]', 'non-existing?key=val&key1=val1');
+    $this->assertFieldByName('redirect_redirect[0][uri]', '/node?key=val&key1=val1');
+
+    $this->drupalGet('admin/config/search/redirect/add');
+    $page = $this->getSession()->getPage();
+    $page->fillField('redirect_source[0][path]', 'non-existing');
+    $page->fillField('redirect_redirect[0][uri]', '/node');
+    $this->assertSession()->assertWaitOnAjaxRequest();
+    $page->pressButton('Save');
+
+    // Try to find the redirect we just created.
+    $redirect = $this->repository->findMatchingRedirect('non-existing');
+    $this->assertEqual($redirect->getSourceUrl(), Url::fromUri('base:non-existing')->toString());
+    $this->assertEqual($redirect->getRedirectUrl()->toString(), Url::fromUri('base:node')->toString());
+
+    // After adding the redirect we should end up in the list. Check if the
+    // redirect is listed.
+    $this->assertUrl('admin/config/search/redirect');
+    $this->assertSession()->pageTextContains('non-existing');
+    $this->assertLink(Url::fromUri('base:node')->toString());
+    $this->assertSession()->pageTextContains(t('Not specified'));
+
+    // Test the edit form and update action.
+    $this->clickLink(t('Edit'));
+    $this->assertFieldByName('redirect_source[0][path]', 'non-existing');
+    $this->assertFieldByName('redirect_redirect[0][uri]', '/node');
+    $this->assertFieldByName('status_code', $redirect->getStatusCode());
+
+    // Append a query string to see if we handle query data properly.
+    $this->drupalPostForm(
+      NULL, [
+      'redirect_source[0][path]' => 'non-existing?key=value',
+    ], t('Save')
+    );
+
+    // Check the location after update and check if the value has been updated
+    // in the list.
+    $this->assertUrl('admin/config/search/redirect');
+    $this->assertSession()->pageTextContains('non-existing?key=value');
+
+    // The path field should not contain the query string and therefore we
+    // should be able to load the redirect using only the url part without
+    // query.
+    $this->storage->resetCache();
+    $redirects = $this->repository->findBySourcePath('non-existing');
+    $redirect = array_shift($redirects);
+    $this->assertEqual($redirect->getSourceUrl(), Url::fromUri('base:non-existing', ['query' => ['key' => 'value']])->toString());
+
+    // Test the source url hints.
+    // The hint about an existing base path.
+    $this->drupalGet('admin/config/search/redirect/add');
+    $page->fillField('redirect_source[0][path]', 'non-existing?key=value');
+    $page->fillField('redirect_redirect[0][uri]', '');
+    $this->assertSession()->assertWaitOnAjaxRequest();
+    $this->assertRaw(
+      t(
+        'The base source path %source is already being redirected. Do you want to <a href="@edit-page">edit the existing redirect</a>?',
+        ['%source' => 'non-existing?key=value', '@edit-page' => $redirect->url('edit-form')]
+      )
+    );
+
+    // The hint about a valid path.
+    $this->drupalGet('admin/config/search/redirect/add');
+    $page->fillField('redirect_source[0][path]', 'node');
+    $page->fillField('redirect_redirect[0][uri]', '');
+    $this->assertSession()->assertWaitOnAjaxRequest();
+    $this->assertRaw(
+      t(
+        'The source path %path is likely a valid path. It is preferred to <a href="@url-alias">create URL aliases</a> for existing paths rather than redirects.',
+        ['%path' => 'node', '@url-alias' => Url::fromRoute('path.admin_add')->toString()]
+      )
+    );
+
+    // Test validation.
+    // Duplicate redirect.
+    $this->drupalGet('admin/config/search/redirect/add');
+    $page = $this->getSession()->getPage();
+    $page->fillField('redirect_source[0][path]', 'non-existing?key=value');
+    $page->fillField('redirect_redirect[0][uri]', '/node');
+    $this->assertSession()->assertWaitOnAjaxRequest();
+    $page->pressButton('Save');
+    $this->assertRaw(
+      t(
+        'The source path %source is already being redirected. Do you want to <a href="@edit-page">edit the existing redirect</a>?',
+        ['%source' => 'non-existing?key=value', '@edit-page' => $redirect->url('edit-form')]
+      )
+    );
+
+    // Redirecting to itself.
+    $this->drupalGet('admin/config/search/redirect/add');
+    $page = $this->getSession()->getPage();
+    $page->fillField('redirect_source[0][path]', 'node');
+    $page->fillField('redirect_redirect[0][uri]', '/node');
+    $this->assertSession()->assertWaitOnAjaxRequest();
+    $page->pressButton('Save');
+    $this->assertRaw(t('You are attempting to redirect the page to itself. This will result in an infinite loop.'));
+
+    // Redirecting the front page.
+    $this->drupalGet('admin/config/search/redirect/add');
+    $page = $this->getSession()->getPage();
+    $page->fillField('redirect_source[0][path]', '<front>');
+    $page->fillField('redirect_redirect[0][uri]', '/node');
+    $this->assertSession()->assertWaitOnAjaxRequest();
+    $page->pressButton('Save');
+    $this->assertRaw(t('It is not allowed to create a redirect from the front page.'));
+
+    // Redirecting a url with fragment.
+    $this->drupalGet('admin/config/search/redirect/add');
+    $page = $this->getSession()->getPage();
+    $page->fillField('redirect_source[0][path]', 'page-to-redirect#content');
+    $page->fillField('redirect_redirect[0][uri]', '/node');
+    $this->assertSession()->assertWaitOnAjaxRequest();
+    $page->pressButton('Save');
+    $this->assertRaw(t('The anchor fragments are not allowed.'));
+
+    // Adding path that starts with /
+    $this->drupalGet('admin/config/search/redirect/add');
+    $page = $this->getSession()->getPage();
+    $page->fillField('redirect_source[0][path]', '/page-to-redirect');
+    $page->fillField('redirect_redirect[0][uri]', '/node');
+    // Wait on ajax is unpredictable, wait for one second.
+    sleep(1);
+    $page->pressButton('Save');
+    $this->assertRaw(t('The url to redirect from should not start with a forward slash (/).'));
+
+    // Test filters.
+    // Add a new redirect.
+    $this->drupalGet('admin/config/search/redirect/add');
+    $page = $this->getSession()->getPage();
+    $page->fillField('redirect_source[0][path]', 'test27');
+    $page->fillField('redirect_redirect[0][uri]', '/node');
+    $this->assertSession()->assertWaitOnAjaxRequest();
+    $page->pressButton('Save');
+
+    // Filter  with non existing value.
+    $this->drupalGet(
+      'admin/config/search/redirect', [
+      'query' => [
+        'status_code' => '3',
+      ],
+    ]
+    );
+
+    $rows = $this->xpath('//tbody/tr');
+    // Check if the list has no rows.
+    $this->assertTrue(count($rows) == 0);
+
+    // Filter with existing values.
+    $this->drupalGet(
+      'admin/config/search/redirect', [
+      'query' => [
+        'redirect_source__path' => 'test',
+        'status_code' => '2',
+      ],
+    ]
+    );
+
+    $rows = $this->xpath('//tbody/tr');
+    // Check if the list has 1 row.
+    $this->assertTrue(count($rows) == 1);
+
+    $this->drupalGet(
+      'admin/config/search/redirect', [
+      'query' => [
+        'redirect_redirect__uri' => 'nod',
+      ],
+    ]
+    );
+
+    $rows = $this->xpath('//tbody/tr');
+    // Check if the list has 2 rows.
+    $this->assertTrue(count($rows) == 2);
+
+    // Test the plural form of the bulk delete action.
+    $this->drupalGet('admin/config/search/redirect');
+    $edit = [
+      'redirect_bulk_form[0]' => TRUE,
+      'redirect_bulk_form[1]' => TRUE,
+    ];
+    $this->drupalPostForm(NULL, $edit, t('Apply to selected items'));
+    $this->assertSession()->pageTextContains('Are you sure you want to delete these redirects?');
+    $this->clickLink('Cancel');
+
+    // Test the delete action.
+    $page->find('css', '.dropbutton-toggle button')->press();
+    $this->clickLink(t('Delete'));
+    $this->assertRaw(
+      t(
+        'Are you sure you want to delete the URL redirect from %source to %redirect?',
+        ['%source' => Url::fromUri('base:non-existing', ['query' => ['key' => 'value']])->toString(), '%redirect' => Url::fromUri('base:node')->toString()]
+      )
+    );
+    $this->drupalPostForm(NULL, [], t('Delete'));
+    $this->assertUrl('admin/config/search/redirect');
+
+    // Test the bulk delete action.
+    $this->drupalPostForm(NULL, ['redirect_bulk_form[0]' => TRUE], t('Apply to selected items'));
+    $this->assertSession()->pageTextContains('Are you sure you want to delete this redirect?');
+    $this->assertSession()->pageTextContains('test27');
+    $this->drupalPostForm(NULL, [], t('Delete'));
+
+    $this->assertSession()->pageTextContains(t('There is no redirect yet.'));
+  }
+
+}
