diff --git a/modules/search_api_db/src/Tests/IntegrationTest.php b/modules/search_api_db/tests/src/Functional/IntegrationTest.php
similarity index 50%
rename from modules/search_api_db/src/Tests/IntegrationTest.php
rename to modules/search_api_db/tests/src/Functional/IntegrationTest.php
index a4fbdd7..2955116 100644
--- a/modules/search_api_db/src/Tests/IntegrationTest.php
+++ b/modules/search_api_db/tests/src/Functional/IntegrationTest.php
@@ -3,38 +3,38 @@
 namespace Drupal\search_api_db\Tests;
 
 use Drupal\Core\StringTranslation\StringTranslationTrait;
-use Drupal\simpletest\WebTestBase;
+use Drupal\Tests\search_api\Functional\SearchApiBrowserTestBase;
 
 /**
  * Tests that using the DB backend via the UI works as expected.
  *
  * @group search_api
  */
-class IntegrationTest extends WebTestBase {
+class IntegrationTest extends SearchApiBrowserTestBase {
 
   use StringTranslationTrait;
 
   /**
    * {@inheritdoc}
    */
-  public static $modules = array(
+  public static $modules = [
     'search_api',
     'search_api_db',
-  );
+  ];
 
   /**
    * Tests that adding a server works.
    */
   public function testAddingServer() {
-    $admin_user = $this->drupalCreateUser(array('administer search_api', 'access administration pages'));
+    $admin_user = $this->drupalCreateUser(['administer search_api', 'access administration pages']);
     $this->drupalLogin($admin_user);
 
     $this->drupalGet('admin/config/search/search-api/add-server');
-    $this->assertResponse(200);
+    $this->assertSession()->statusCodeEquals(200);
 
-    $edit = array('name' => ' ~`Test Server', 'id' => '_test');
-    $this->drupalPostForm(NULL, $edit, $this->t('Save'));
-    $this->assertUrl('admin/config/search/search-api/server/_test');
+    $edit = ['name' => ' ~`Test Server', 'id' => '_test'];
+    $this->submitForm($edit, $this->t('Save'));
+    $this->assertSession()->addressEquals('admin/config/search/search-api/server/_test');
   }
 
 }
diff --git a/src/Form/IndexStatusForm.php b/src/Form/IndexStatusForm.php
index 1bbf17b..33c0db6 100644
--- a/src/Form/IndexStatusForm.php
+++ b/src/Form/IndexStatusForm.php
@@ -54,7 +54,7 @@ class IndexStatusForm extends FormBase {
       // Add the "Index now" form.
       $form['index'] = array(
         '#type' => 'details',
-        '#title' => $this->t('Index now'),
+        '#title' => $this->t('Start indexing now'),
         '#open' => TRUE,
         '#attributes' => array(
           'class' => array('container-inline'),
diff --git a/src/Tests/LocalActionsWebTest.php b/src/Tests/LocalActionsWebTest.php
deleted file mode 100644
index 523d77e..0000000
--- a/src/Tests/LocalActionsWebTest.php
+++ /dev/null
@@ -1,73 +0,0 @@
-<?php
-
-namespace Drupal\search_api\Tests;
-
-use Drupal\Core\Url;
-use Drupal\system\Tests\Menu\LocalActionTest;
-
-/**
- * Tests that local actions are available.
- *
- * @group search_api
- */
-class LocalActionsWebTest extends LocalActionTest {
-
-  /**
-   * Modules to enable for this test.
-   *
-   * @var string[]
-   */
-  public static $modules = array('search_api');
-
-  /**
-   * The administrator account to use for the tests.
-   *
-   * @var \Drupal\user\UserInterface
-   */
-  protected $adminUser;
-
-  /**
-   * {@inheritdoc}
-   */
-  public function setUp() {
-    parent::setUp();
-    // Create users.
-    $this->adminUser = $this->drupalCreateUser(array('administer search_api', 'access administration pages'));
-    $this->drupalLogin($this->adminUser);
-
-    // Do not use a batch for tracking the initial items after creating an
-    // index when running the tests via the GUI. Otherwise, it seems Drupal's
-    // Batch API gets confused and the test fails.
-    if (php_sapi_name() != 'cli') {
-      \Drupal::state()->set('search_api_use_tracking_batch', FALSE);
-    }
-  }
-
-  /**
-   * {@inheritdoc}
-   */
-  public function testLocalAction() {
-    // @todo Merge into OverviewPageTest or IntegrationTest? Or get rid of the
-    //   triple loop, or do something useful with it.
-    foreach ($this->getSearchApiPageRoutes() as $routes) {
-      foreach ($routes as $route) {
-        $actions = array(
-          [Url::fromRoute('entity.search_api_server.add_form'), 'Add server'],
-          [Url::fromRoute('entity.search_api_index.add_form'), 'Add index'],
-        );
-        $this->drupalGet($route);
-        $this->assertLocalAction($actions);
-      }
-    }
-  }
-
-  /**
-   * Provides a list of routes to test.
-   */
-  public function getSearchApiPageRoutes() {
-    return array(
-      array('/admin/config/search/search-api'),
-    );
-  }
-
-}
diff --git a/src/Tests/CacheabilityTest.php b/tests/src/Functional/CacheabilityTest.php
similarity index 81%
rename from src/Tests/CacheabilityTest.php
rename to tests/src/Functional/CacheabilityTest.php
index e4e1077..6ad1557 100644
--- a/src/Tests/CacheabilityTest.php
+++ b/tests/src/Functional/CacheabilityTest.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Drupal\search_api\Tests;
+namespace Drupal\Tests\search_api\Functional;
 
 use Drupal\search_api\Entity\Index;
 
@@ -9,7 +9,7 @@ use Drupal\search_api\Entity\Index;
  *
  * @group search_api
  */
-class CacheabilityTest extends WebTestBase {
+class CacheabilityTest extends SearchApiBrowserTestBase {
 
   use ExampleContentTrait;
 
@@ -23,12 +23,12 @@ class CacheabilityTest extends WebTestBase {
   /**
    * {@inheritdoc}
    */
-  public static $modules = array(
+  public static $modules = [
     'rest',
     'search_api',
     'search_api_test',
     'search_api_test_views',
-  );
+  ];
 
   /**
    * {@inheritdoc}
@@ -58,8 +58,8 @@ class CacheabilityTest extends WebTestBase {
 
     // Verify that the search results are marked as uncacheable.
     $this->drupalGet('search-api-test');
-    $this->assertResponse(200);
-    $this->assertHeader('x-drupal-dynamic-cache', 'UNCACHEABLE');
+    $this->assertSession()->statusCodeEquals(200);
+    $this->assertSession()->responseHeaderEquals('x-drupal-dynamic-cache', 'UNCACHEABLE');
     $this->assertTrue(strpos($this->drupalGetHeader('cache-control'), 'no-cache'));
   }
 
diff --git a/src/Tests/ExampleContentTrait.php b/tests/src/Functional/ExampleContentTrait.php
similarity index 83%
rename from src/Tests/ExampleContentTrait.php
rename to tests/src/Functional/ExampleContentTrait.php
index f20988b..84798df 100644
--- a/src/Tests/ExampleContentTrait.php
+++ b/tests/src/Functional/ExampleContentTrait.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Drupal\search_api\Tests;
+namespace Drupal\Tests\search_api\Functional;
 
 use Drupal\search_api\Entity\Index;
 use Drupal\search_api\Utility\Utility;
@@ -15,7 +15,7 @@ trait ExampleContentTrait {
    *
    * @var \Drupal\entity_test\Entity\EntityTestMulRevChanged[]
    */
-  protected $entities = array();
+  protected $entities = [];
 
   /**
    * Sets up the necessary bundles on the test entity type.
@@ -31,43 +31,43 @@ trait ExampleContentTrait {
   protected function insertExampleContent() {
     // To test Unicode compliance, include all kind of strange characters here.
     $smiley = json_decode('"\u1F601"');
-    $this->addTestEntity(1, array(
+    $this->addTestEntity(1, [
       'name' => 'foo bar baz foobaz föö smile' . $smiley,
       'body' => 'test test case Case casE',
       'type' => 'item',
-      'keywords' => array('Orange', 'orange', 'örange', 'Orange', $smiley),
+      'keywords' => ['Orange', 'orange', 'örange', 'Orange', $smiley],
       'category' => 'item_category',
-    ));
-    $this->addTestEntity(2, array(
+    ]);
+    $this->addTestEntity(2, [
       'name' => 'foo test foobuz',
       'body' => 'bar test casE',
       'type' => 'item',
-      'keywords' => array('orange', 'apple', 'grape'),
+      'keywords' => ['orange', 'apple', 'grape'],
       'category' => 'item_category',
-    ));
-    $this->addTestEntity(3, array(
+    ]);
+    $this->addTestEntity(3, [
       'name' => 'bar',
       'body' => 'test foobar Case',
       'type' => 'item',
-    ));
-    $this->addTestEntity(4, array(
+    ]);
+    $this->addTestEntity(4, [
       'name' => 'foo baz',
       'body' => 'test test test',
       'type' => 'article',
-      'keywords' => array('apple', 'strawberry', 'grape'),
+      'keywords' => ['apple', 'strawberry', 'grape'],
       'category' => 'article_category',
       'width' => '1.0',
-    ));
-    $this->addTestEntity(5, array(
+    ]);
+    $this->addTestEntity(5, [
       'name' => 'bar baz',
       'body' => 'foo',
       'type' => 'article',
-      'keywords' => array('orange', 'strawberry', 'grape', 'banana'),
+      'keywords' => ['orange', 'strawberry', 'grape', 'banana'],
       'category' => 'article_category',
       'width' => '2.0',
-    ));
+    ]);
     $count = \Drupal::entityQuery('entity_test_mulrev_changed')->count()->execute();
-    $this->assertEqual($count, 5, "$count items inserted.");
+    $this->assertEquals($count, 5, "$count items inserted.");
   }
 
   /**
diff --git a/src/Tests/HooksTest.php b/tests/src/Functional/HooksTest.php
similarity index 65%
rename from src/Tests/HooksTest.php
rename to tests/src/Functional/HooksTest.php
index 3a3c31e..d597cb1 100644
--- a/src/Tests/HooksTest.php
+++ b/tests/src/Functional/HooksTest.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Drupal\search_api\Tests;
+namespace Drupal\Tests\search_api\Functional;
 
 use Drupal\search_api\Entity\Index;
 use Drupal\search_api_test\PluginTestTrait;
@@ -10,21 +10,21 @@ use Drupal\search_api_test\PluginTestTrait;
  *
  * @group search_api
  */
-class HooksTest extends WebTestBase {
+class HooksTest extends SearchApiBrowserTestBase {
 
   use PluginTestTrait;
 
   /**
    * {@inheritdoc}
    */
-  public static $modules = array(
+  public static $modules = [
     'node',
     'rest',
     'search_api',
     'search_api_test',
     'search_api_test_views',
     'search_api_test_hooks',
-  );
+  ];
 
   /**
    * The test server.
@@ -40,10 +40,10 @@ class HooksTest extends WebTestBase {
     parent::setUp();
 
     // Create some nodes.
-    $this->drupalCreateNode(array('type' => 'page', 'title' => 'node - 1'));
-    $this->drupalCreateNode(array('type' => 'page', 'title' => 'node - 2'));
-    $this->drupalCreateNode(array('type' => 'page', 'title' => 'node - 3'));
-    $this->drupalCreateNode(array('type' => 'page', 'title' => 'node - 4'));
+    $this->drupalCreateNode(['type' => 'page', 'title' => 'node - 1']);
+    $this->drupalCreateNode(['type' => 'page', 'title' => 'node - 2']);
+    $this->drupalCreateNode(['type' => 'page', 'title' => 'node - 3']);
+    $this->drupalCreateNode(['type' => 'page', 'title' => 'node - 4']);
 
     // Create an index and server to work with.
     $this->server = $this->getTestServer();
@@ -75,71 +75,73 @@ class HooksTest extends WebTestBase {
   public function testHooks() {
     // hook_search_api_backend_info_alter() was invoked.
     $this->drupalGet('admin/config/search/search-api/add-server');
-    $this->assertText('Slims return');
+    $this->assertSession()->pageTextContains('Slims return');
 
     // hook_search_api_datasource_info_alter() was invoked.
     $this->drupalGet('admin/config/search/search-api/add-index');
-    $this->assertText('Distant land');
+    $this->assertSession()->pageTextContains('Distant land');
 
     // hook_search_api_processor_info_alter() was invoked.
     $this->drupalGet($this->getIndexPath('processors'));
-    $this->assertText('Mystic bounce');
+    $this->assertSession()->pageTextContains('Mystic bounce');
 
     // hook_search_api_parse_mode_info_alter was invoked.
     $definition = \Drupal::getContainer()
       ->get('plugin.manager.search_api.parse_mode')
       ->getDefinition('direct');
-    $this->assertEqual('Song for My Father', $definition['label']);
+    $this->assertEquals('Song for My Father', $definition['label']);
 
     // Saving the index should trigger the processor's preIndexSave() method.
-    $this->drupalPostForm(NULL, array(), $this->t('Save'));
+    $this->submitForm([], $this->t('Save'));
     $processor_methods = $this->getCalledMethods('processor');
-    $this->assertEqual(array('preIndexSave'), $processor_methods);
+    $this->assertEquals(['preIndexSave'], $processor_methods);
 
     $this->drupalGet($this->getIndexPath());
-    $this->drupalPostForm(NULL, array(), $this->t('Index now'));
+    // Duplication on value $this->t('Index now')) with summary.
+    $this->submitForm([], $this->t('Index now'));
+    $this->checkForMetaRefresh();
+    $this->assertSession()->pageTextContains('Successfully indexed 4 items.');
 
     // During indexing, alterIndexedItems() and preprocessIndexItems() should be
     // called on the processor.
     $processor_methods = $this->getCalledMethods('processor');
-    $expected = array('alterIndexedItems', 'preprocessIndexItems');
-    $this->assertEqual($expected, $processor_methods);
+    $expected = ['alterIndexedItems', 'preprocessIndexItems'];
+    $this->assertEquals($expected, $processor_methods);
 
     // hook_search_api_index_items_alter() was invoked, this removed node:1.
     // hook_search_api_query_TAG_alter() was invoked, this removed node:3.
-    $this->assertText('There are 2 items indexed on the server for this index.');
-    $this->assertText('Successfully indexed 4 items.');
-    $this->assertText('Stormy');
+    $this->assertSession()->pageTextContains('There are 2 items indexed on the server for this index.');
+    $this->assertSession()->pageTextContains('Stormy');
 
     // hook_search_api_items_indexed() was invoked.
-    $this->assertText('Please set me at ease');
+    $this->assertSession()->pageTextContains('Please set me at ease');
 
     // hook_search_api_index_reindex() was invoked.
     $this->drupalGet($this->getIndexPath('reindex'));
-    $this->drupalPostForm(NULL, array(), $this->t('Confirm'));
-    $this->assertText('Montara');
+    $this->submitForm([], $this->t('Confirm'));
+    $this->assertSession()->pageTextContains('Montara');
 
     // hook_search_api_data_type_info_alter() was invoked.
     $this->drupalGet($this->getIndexPath('fields'));
-    $this->assertText('Peace/Dolphin dance');
+    $this->assertSession()->pageTextContains('Peace/Dolphin dance');
     // The implementation of hook_search_api_field_type_mapping_alter() has
     // removed all dates, so we can't see any timestamp anymore in the page.
     $url_options['query']['datasource'] = 'entity:node';
     $this->drupalGet($this->getIndexPath('fields/add'), $url_options);
-    $this->assertNoText('timestamp');
+    $this->assertSession()->pageTextNotContains('timestamp');
 
     $this->drupalGet('search-api-test');
     // hook_search_api_query_alter() was invoked.
-    $this->assertText('Funky blue note');
+    $this->assertSession()->pageTextContains('Funky blue note');
     // hook_search_api_results_alter() was invoked.
-    $this->assertText('Stepping into tomorrow');
+    $this->assertSession()->pageTextContains('Stepping into tomorrow');
     // hook_search_api_results_TAG_alter() was invoked.
-    $this->assertText('Llama');
+    $this->assertSession()->pageTextContains('Llama');
 
     // The query alter methods of the processor were called.
     $processor_methods = $this->getCalledMethods('processor');
-    $expected = array('preprocessSearchQuery', 'postprocessSearchResults');
-    $this->assertEqual($expected, $processor_methods);
+    $expected = ['preprocessSearchQuery', 'postprocessSearchResults'];
+    $this->assertEquals($expected, $processor_methods);
 
     // hook_search_api_server_features_alter() is triggered.
     $this->assertTrue($this->server->supportsFeature('welcome_to_the_jungle'));
diff --git a/src/Tests/IntegrationTest.php b/tests/src/Functional/IntegrationTest.php
similarity index 59%
rename from src/Tests/IntegrationTest.php
rename to tests/src/Functional/IntegrationTest.php
index cef9daa..497569e 100644
--- a/src/Tests/IntegrationTest.php
+++ b/tests/src/Functional/IntegrationTest.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Drupal\search_api\Tests;
+namespace Drupal\Tests\search_api\Functional;
 
 use Drupal\Component\Utility\Html;
 use Drupal\Core\Entity\Entity\EntityFormDisplay;
@@ -9,6 +9,7 @@ use Drupal\field\Entity\FieldConfig;
 use Drupal\field\Entity\FieldStorageConfig;
 use Drupal\node\Entity\Node;
 use Drupal\search_api\Entity\Server;
+use Drupal\search_api\IndexInterface;
 use Drupal\search_api\Plugin\search_api\tracker\Basic;
 use Drupal\search_api\SearchApiException;
 use Drupal\search_api\Utility\Utility;
@@ -20,7 +21,7 @@ use Drupal\search_api_test\PluginTestTrait;
  *
  * @group search_api
  */
-class IntegrationTest extends WebTestBase {
+class IntegrationTest extends SearchApiBrowserTestBase {
 
   use PluginTestTrait;
 
@@ -48,14 +49,14 @@ class IntegrationTest extends WebTestBase {
   /**
    * {@inheritdoc}
    */
-  public static $modules = array(
+  public static $modules = [
     'node',
     'search_api',
     'search_api_test',
     'field_ui',
     'link',
     'image',
-  );
+  ];
 
   /**
    * {@inheritdoc}
@@ -64,14 +65,14 @@ class IntegrationTest extends WebTestBase {
     parent::setUp();
     $this->indexStorage = \Drupal::entityTypeManager()->getStorage('search_api_index');
 
-    $permissions = array(
+    $permissions = [
       'administer search_api',
       'access administration pages',
       'administer nodes',
       'bypass node access',
       'administer content types',
       'administer node fields',
-    );
+    ];
     $this->adminUser = $this->drupalCreateUser($permissions);
     $this->adminUser2 = $this->drupalCreateUser($permissions);
     $this->drupalLogin($this->adminUser);
@@ -121,36 +122,37 @@ class IntegrationTest extends WebTestBase {
    * server also has an integer as id/label.
    */
   public function testIntegerIndex() {
-    Server::create(array(
+    Server::create([
       'id' => 456,
       'name' => 789,
       'description' => 'WebTest server' . ' description',
       'backend' => 'search_api_test',
-      'backend_config' => array(),
-    ))->save();
+      'backend_config' => [],
+    ])->save();
 
-    $this->drupalCreateNode(array('type' => 'article'));
-    $this->drupalCreateNode(array('type' => 'article'));
+    $this->drupalCreateNode(['type' => 'article']);
+    $this->drupalCreateNode(['type' => 'article']);
 
     $this->drupalGet('admin/config/search/search-api/add-index');
 
     $this->indexId = 123;
-    $edit = array(
+    $edit = [
       'name' => $this->indexId,
       'id' => $this->indexId,
       'status' => 1,
       'description' => 'test Index:: 123~',
       'server' => 456,
       'datasources[entity:node]' => TRUE,
-    );
-    $this->drupalPostForm(NULL, $edit, $this->t('Save'));
-    $this->assertResponse(200);
-    $this->assertText($this->t('Please configure the used datasources.'));
-    $this->drupalPostForm(NULL, array(), $this->t('Save'));
-    $this->assertResponse(200);
-    $this->assertText($this->t('The index was successfully saved.'));
-    $this->assertText($this->t('Successfully executed @count pending task.', array('@count' => 1)));
-    $this->assertEqual(2, $this->countTrackedItems());
+    ];
+    $this->submitForm($edit, $this->t('Save'));
+    $this->assertSession()->statusCodeEquals(200);
+    $this->assertSession()->pageTextContains($this->t('Please configure the used datasources.'));
+    $this->submitForm([], $this->t('Save'));
+    $this->checkForMetaRefresh();
+    $this->assertSession()->statusCodeEquals(200);
+    $this->assertSession()->pageTextContains($this->t('The index was successfully saved.'));
+    $this->assertSession()->pageTextContains($this->t('Successfully executed @count pending task.', ['@count' => 1]));
+    $this->assertEquals(2, $this->countTrackedItems());
 
     $this->enableAllProcessors();
     $this->checkFieldLabels();
@@ -185,39 +187,41 @@ class IntegrationTest extends WebTestBase {
     $settings_path = 'admin/config/search/search-api/add-server';
 
     $this->drupalGet($settings_path);
-    $this->assertResponse(200, 'Server add page exists');
+    $this->assertSession()->statusCodeEquals(200);
 
-    $edit = array(
+    $edit = [
       'name' => '',
       'status' => 1,
       'description' => 'A server used for testing.',
       'backend' => 'search_api_test',
-    );
+    ];
 
-    $this->drupalPostForm($settings_path, $edit, $this->t('Save'));
-    $this->assertText($this->t('@name field is required.', array('@name' => $this->t('Server name'))));
+    $this->drupalGet($settings_path);
+    $this->submitForm($edit, $this->t('Save'));
+    $this->assertSession()->pageTextContains($this->t('@name field is required.', ['@name' => $this->t('Server name')]));
 
-    $edit = array(
+    $edit = [
       'name' => $server_name,
       'status' => 1,
       'description' => $server_description,
       'backend' => 'search_api_test',
-    );
-    $this->drupalPostForm($settings_path, $edit, $this->t('Save'));
-    $this->assertText($this->t('@name field is required.', array('@name' => $this->t('Machine-readable name'))));
+    ];
+    $this->drupalGet($settings_path);
+    $this->submitForm($edit, $this->t('Save'));
+    $this->assertSession()->pageTextContains($this->t('@name field is required.', ['@name' => $this->t('Machine-readable name')]));
 
-    $edit = array(
+    $edit = [
       'name' => $server_name,
       'id' => $this->serverId,
       'status' => 1,
       'description' => $server_description,
       'backend' => 'search_api_test',
-    );
+    ];
 
-    $this->drupalPostForm(NULL, $edit, $this->t('Save'));
+    $this->submitForm($edit, $this->t('Save'));
 
-    $this->assertText($this->t('The server was successfully saved.'));
-    $this->assertUrl('admin/config/search/search-api/server/' . $this->serverId, array(), 'Correct redirect to server page.');
+    $this->assertSession()->pageTextContains($this->t('The server was successfully saved.'));
+    $this->assertSession()->addressEquals('admin/config/search/search-api/server/' . $this->serverId);
     $this->assertHtmlEscaped($server_name);
     $this->assertHtmlEscaped($server_description);
 
@@ -233,15 +237,15 @@ class IntegrationTest extends WebTestBase {
     $server_add_page = 'admin/config/search/search-api/add-server';
     $this->drupalGet($server_add_page);
 
-    $edit = array(
+    $edit = [
       'name' => $this->serverId,
       'id' => $this->serverId,
       'backend' => 'search_api_test',
-    );
+    ];
 
     // Try to submit an server with a duplicate machine name.
-    $this->drupalPostForm(NULL, $edit, $this->t('Save'));
-    $this->assertText($this->t('The machine-readable name is already in use. It must be unique.'));
+    $this->submitForm($edit, $this->t('Save'));
+    $this->assertSession()->pageTextContains($this->t('The machine-readable name is already in use. It must be unique.'));
   }
 
   /**
@@ -255,33 +259,35 @@ class IntegrationTest extends WebTestBase {
     $index_datasource = 'entity:node';
 
     $this->drupalGet($settings_path);
-    $this->assertResponse(200);
-    $edit = array(
+    $this->assertSession()->statusCodeEquals(200);
+    $edit = [
       'status' => 1,
       'description' => $index_description,
-    );
+    ];
 
-    $this->drupalPostForm(NULL, $edit, $this->t('Save'));
-    $this->assertText($this->t('@name field is required.', array('@name' => $this->t('Index name'))));
-    $this->assertText($this->t('@name field is required.', array('@name' => $this->t('Machine-readable name'))));
-    $this->assertText($this->t('@name field is required.', array('@name' => $this->t('Data sources'))));
+    $this->submitForm($edit, $this->t('Save'));
+    $this->assertSession()->pageTextContains($this->t('@name field is required.', ['@name' => $this->t('Index name')]));
+    $this->assertSession()->pageTextContains($this->t('@name field is required.', ['@name' => $this->t('Machine-readable name')]));
+    $this->assertSession()->pageTextContains($this->t('@name field is required.', ['@name' => $this->t('Data sources')]));
 
-    $edit = array(
+    $edit = [
       'name' => $index_name,
       'id' => $this->indexId,
       'status' => 1,
       'description' => $index_description,
       'server' => $this->serverId,
       'datasources[' . $index_datasource . ']' => TRUE,
-    );
+    ];
+
+    $this->submitForm($edit, $this->t('Save'));
+    $this->assertSession()->pageTextContains($this->t('Please configure the used datasources.'));
 
-    $this->drupalPostForm(NULL, $edit, $this->t('Save'));
-    $this->assertText($this->t('Please configure the used datasources.'));
+    $this->submitForm([], $this->t('Save'));
+    $this->checkForMetaRefresh();
+    $this->assertSession()->pageTextContains($this->t('The index was successfully saved.'));
 
-    $this->drupalPostForm(NULL, array(), $this->t('Save'));
-    $this->assertText($this->t('The index was successfully saved.'));
     // @todo Make this work correctly.
-    // $this->assertUrl($this->getIndexPath('fields/add'), array(), 'Correct redirect to index page.');
+    // $this->assertUrl($this->getIndexPath('fields/add'), [], 'Correct redirect to index page.');
     $this->assertHtmlEscaped($index_name);
 
     $this->drupalGet($this->getIndexPath('edit'));
@@ -289,31 +295,27 @@ class IntegrationTest extends WebTestBase {
 
     $index = $this->getIndex(TRUE);
 
-    if ($this->assertTrue($index, 'Index was correctly created.')) {
-      $this->assertEqual($index->label(), $edit['name'], 'Name correctly inserted.');
-      $this->assertEqual($index->id(), $edit['id'], 'Index ID correctly inserted.');
-      $this->assertTrue($index->status(), 'Index status correctly inserted.');
-      $this->assertEqual($index->getDescription(), $edit['description'], 'Index ID correctly inserted.');
-      $this->assertEqual($index->getServerId(), $edit['server'], 'Index server ID correctly inserted.');
-      $this->assertEqual($index->getDatasourceIds()[0], $index_datasource, 'Index datasource id correctly inserted.');
-    }
-    else {
-      // Since none of the other tests would work, bail at this point.
-      throw new SearchApiException();
-    }
+    $this->assertTrue($index, 'Index was correctly created.');
+    $this->assertEquals($index->label(), $edit['name'], 'Name correctly inserted.');
+    $this->assertEquals($index->id(), $edit['id'], 'Index ID correctly inserted.');
+    $this->assertTrue($index->status(), 'Index status correctly inserted.');
+    $this->assertEquals($index->getDescription(), $edit['description'], 'Index ID correctly inserted.');
+    $this->assertEquals($index->getServerId(), $edit['server'], 'Index server ID correctly inserted.');
+    $this->assertEquals($index->getDatasourceIds()[0], $index_datasource, 'Index datasource id correctly inserted.');
 
     // Test the "Save and add fields" button.
     $index2_id = 'test_index2';
     $edit['id'] = $index2_id;
     unset($edit['server']);
-    $this->drupalPostForm($settings_path, $edit, $this->t('Save and add fields'));
-    $this->assertText($this->t('Please configure the used datasources.'));
+    $this->drupalGet($settings_path);
+    $this->submitForm($edit, $this->t('Save and add fields'));
+    $this->assertSession()->pageTextContains($this->t('Please configure the used datasources.'));
 
-    $this->drupalPostForm(NULL, array(), $this->t('Save and add fields'));
-    $this->assertText($this->t('The index was successfully saved.'));
-    $this->indexStorage->resetCache(array($index2_id));
+    $this->submitForm([], $this->t('Save and add fields'));
+    $this->assertSession()->pageTextContains($this->t('The index was successfully saved.'));
+    $this->indexStorage->resetCache([$index2_id]);
     $index = $this->indexStorage->load($index2_id);
-    $this->assertUrl($index->toUrl('add-fields'), array(), 'Correct redirect to index fields page.');
+    $this->assertSession()->addressEquals($index->toUrl('add-fields'));
 
     $this->drupalGet('admin/config/search/search-api');
     $this->assertHtmlEscaped($index_name);
@@ -327,28 +329,28 @@ class IntegrationTest extends WebTestBase {
     $index_add_page = 'admin/config/search/search-api/add-index';
     $this->drupalGet($index_add_page);
 
-    $edit = array(
+    $edit = [
       'name' => $this->indexId,
       'id' => $this->indexId,
       'server' => $this->serverId,
       'datasources[entity:node]' => TRUE,
-    );
+    ];
 
     // Try to submit an index with a duplicate machine name.
-    $this->drupalPostForm(NULL, $edit, $this->t('Save'));
-    $this->assertText($this->t('The machine-readable name is already in use. It must be unique.'));
+    $this->submitForm($edit, $this->t('Save'));
+    $this->assertSession()->pageTextContains($this->t('The machine-readable name is already in use. It must be unique.'));
 
     // Try to submit an index with a duplicate machine name after form
     // rebuilding via datasource submit.
-    $this->drupalPostForm(NULL, $edit, array('path' => $index_add_page, 'triggering_element' => array('datasources_configure' => t('Configure'))));
-    $this->drupalPostForm(NULL, $edit, $this->t('Save'));
-    $this->assertText($this->t('The machine-readable name is already in use. It must be unique.'));
+    $this->submitForm($edit, 'datasources_configure');
+    $this->submitForm($edit, $this->t('Save'));
+    $this->assertSession()->pageTextContains($this->t('The machine-readable name is already in use. It must be unique.'));
 
     // Try to submit an index with a duplicate machine name after form
     // rebuilding via datasource submit using AJAX.
-    $this->drupalPostAjaxForm(NULL, $edit, array('datasources_configure' => t('Configure')));
-    $this->drupalPostForm(NULL, $edit, $this->t('Save'));
-    $this->assertText($this->t('The machine-readable name is already in use. It must be unique.'));
+    $this->submitForm($edit, 'datasources_configure');
+    $this->submitForm($edit, $this->t('Save'));
+    $this->assertSession()->pageTextContains($this->t('The machine-readable name is already in use. It must be unique.'));
   }
 
   /**
@@ -360,27 +362,28 @@ class IntegrationTest extends WebTestBase {
 
     // Check if it's possible to change the machine name.
     $elements = $this->xpath('//form[@id="search-api-server-edit-form"]/div[contains(@class, "form-item-id")]/input[@disabled]');
-    $this->assertEqual(count($elements), 1, 'Machine name cannot be changed.');
+    $this->assertEquals(count($elements), 1, 'Machine name cannot be changed.');
 
     $tracked_items_before = $this->countTrackedItems();
 
-    $edit = array(
+    $edit = [
       'name' => 'Test server',
-    );
-    $this->drupalPostForm(NULL, $edit, $this->t('Save'));
+    ];
+    $this->submitForm($edit, $this->t('Save'));
 
     /** @var $index \Drupal\search_api\IndexInterface */
     $index = $this->indexStorage->load($this->indexId);
     $remaining = $index->getTrackerInstance()->getRemainingItemsCount();
-    $this->assertEqual(0, $remaining, 'Index was not scheduled for re-indexing when saving its server.');
+    $this->assertEquals(0, $remaining, 'Index was not scheduled for re-indexing when saving its server.');
 
     $this->setReturnValue('backend', 'postUpdate', TRUE);
-    $this->drupalPostForm($path, $edit, $this->t('Save'));
+    $this->drupalGet($path);
+    $this->submitForm($edit, $this->t('Save'));
 
     $tracked_items = $this->countTrackedItems();
     $remaining = $index->getTrackerInstance()->getRemainingItemsCount();
-    $this->assertEqual($tracked_items, $remaining, 'Backend could trigger re-indexing upon save.');
-    $this->assertEqual($tracked_items_before, $tracked_items, 'Items are still tracked after re-indexing was triggered.');
+    $this->assertEquals($tracked_items, $remaining, 'Backend could trigger re-indexing upon save.');
+    $this->assertEquals($tracked_items_before, $tracked_items, 'Items are still tracked after re-indexing was triggered.');
   }
 
   /**
@@ -393,32 +396,36 @@ class IntegrationTest extends WebTestBase {
 
     // Check if it's possible to change the machine name.
     $elements = $this->xpath('//form[@id="search-api-index-edit-form"]/div[contains(@class, "form-item-id")]/input[@disabled]');
-    $this->assertEqual(count($elements), 1, 'Machine name cannot be changed.');
+    $this->assertEquals(count($elements), 1, 'Machine name cannot be changed.');
 
     // Test the AJAX functionality for configuring the tracker.
-    $edit = array('tracker' => 'search_api_test');
-    $this->drupalPostAjaxForm(NULL, $edit, 'tracker_configure');
+    $edit = ['tracker' => 'search_api_test'];
+    $this->submitForm($edit, 'tracker_configure');
     $edit['tracker_config[foo]'] = 'foobar';
-    $this->drupalPostForm(NULL, $edit, $this->t('Save'));
-    $this->assertResponse(200);
-    $this->assertText($this->t('The index was successfully saved.'));
+    $this->submitForm($edit, $this->t('Save'));
+    $this->checkForMetaRefresh();
+    $this->assertSession()->statusCodeEquals(200);
+    $this->assertSession()->pageTextContains($this->t('The index was successfully saved.'));
 
     // Verify that everything was changed correctly.
     $index = $this->getIndex(TRUE);
     $tracker = $index->getTrackerInstance();
+    $this->assertTrue($tracker instanceof TestTracker, get_class($tracker));
     $this->assertTrue($tracker instanceof TestTracker, 'Tracker was successfully switched.');
-    $configuration = array(
+    $configuration = [
       'foo' => 'foobar',
-      'dependencies' => array(),
-    );
-    $this->assertEqual($tracker->getConfiguration(), $configuration, 'Tracker config was successfully saved.');
-    $this->assertEqual($this->countTrackedItems(), $tracked_items, 'Items are still correctly tracked.');
+      'dependencies' => [],
+    ];
+    $this->assertEquals($tracker->getConfiguration(), $configuration, 'Tracker config was successfully saved.');
+    $this->assertEquals($this->countTrackedItems(), $tracked_items, 'Items are still correctly tracked.');
 
     // Revert back to the default tracker for the rest of the test.
-    $edit = array('tracker' => 'default');
-    $this->drupalPostForm($edit_path, $edit, $this->t('Save'));
-    $this->assertResponse(200);
-    $this->assertText($this->t('The index was successfully saved.'));
+    $edit = ['tracker' => 'default'];
+    $this->drupalGet($edit_path);
+    $this->submitForm($edit, $this->t('Save'));
+    $this->checkForMetaRefresh();
+    $this->assertSession()->statusCodeEquals(200);
+    $this->assertSession()->pageTextContains($this->t('The index was successfully saved.'));
     $index = $this->getIndex(TRUE);
     $tracker = $index->getTrackerInstance();
     $this->assertTrue($tracker instanceof Basic, 'Tracker was successfully switched.');
@@ -428,20 +435,21 @@ class IntegrationTest extends WebTestBase {
    * Tests that an entity without bundles can be used as a data source.
    */
   protected function checkUserIndexCreation() {
-    $edit = array(
+    $edit = [
       'name' => 'IndexName',
       'id' => 'user_index',
       'datasources[entity:user]' => TRUE,
-    );
-
-    $this->drupalPostForm('admin/config/search/search-api/add-index', $edit, $this->t('Save'));
-    $this->assertResponse(200);
-    $this->assertText($this->t('Please configure the used datasources.'));
+    ];
 
-    $this->drupalPostForm(NULL, array(), $this->t('Save'));
-    $this->assertResponse(200);
-    $this->assertText($this->t('The index was successfully saved.'));
-    $this->assertText($edit['name']);
+    $this->drupalGet('admin/config/search/search-api/add-index');
+    $this->submitForm($edit, $this->t('Save'));
+    $this->assertSession()->statusCodeEquals(200);
+    $this->assertSession()->pageTextContains($this->t('Please configure the used datasources.'));
+
+    $this->submitForm([], $this->t('Save'));
+    $this->assertSession()->statusCodeEquals(200);
+    $this->assertSession()->pageTextContains($this->t('The index was successfully saved.'));
+    $this->assertSession()->pageTextContains($edit['name']);
   }
 
   /**
@@ -451,13 +459,13 @@ class IntegrationTest extends WebTestBase {
     $this->drupalGet('admin/config/search/search-api/server/' . $this->serverId . '/edit');
 
     $this->drupalGet('admin/config/search/search-api');
-    $this->assertResponse(200);
-    $this->assertRaw($this->t('Enabled'));
+    $this->assertSession()->statusCodeEquals(200);
+    $this->assertSession()->responseContains($this->t('Enabled'));
 
     $this->setReturnValue('backend', 'isAvailable', FALSE);
     $this->drupalGet('admin/config/search/search-api');
-    $this->assertResponse(200);
-    $this->assertRaw($this->t('Unavailable'));
+    $this->assertSession()->statusCodeEquals(200);
+    $this->assertSession()->responseContains($this->t('Unavailable'));
 
     $this->setReturnValue('backend', 'isAvailable', TRUE);
   }
@@ -470,153 +478,164 @@ class IntegrationTest extends WebTestBase {
   protected function checkContentEntityTracking() {
     // Initially there should be no tracked items, because there are no nodes.
     $tracked_items = $this->countTrackedItems();
-    $this->assertEqual($tracked_items, 0, 'No items are tracked yet.');
+    $this->assertEquals($tracked_items, 0, 'No items are tracked yet.');
 
     // Add two articles and two pages (one of them "invisible" to Search API).
-    $article1 = $this->drupalCreateNode(array('type' => 'article'));
-    $this->drupalCreateNode(array('type' => 'article'));
-    $this->drupalCreateNode(array('type' => 'page'));
-    $page2 = Node::create(array(
-      'body' => array(
-        array(
+    $article1 = $this->drupalCreateNode(['type' => 'article']);
+    $this->drupalCreateNode(['type' => 'article']);
+    $this->drupalCreateNode(['type' => 'page']);
+    $page2 = Node::create([
+      'body' => [
+        [
           'value' => $this->randomMachineName(32),
           'format' => filter_default_format(),
-        )
-      ),
+        ],
+      ],
       'title' => $this->randomMachineName(8),
       'type' => 'page',
       'uid' => \Drupal::currentUser()->id(),
-    ));
+    ]);
     $page2->search_api_skip_tracking = TRUE;
     $page2->save();
 
     // The 3 new nodes without "search_api_skip_tracking" property set should
     // have been added to the tracking table immediately.
     $tracked_items = $this->countTrackedItems();
-    $this->assertEqual($tracked_items, 3, 'Three items are tracked.');
+    $this->assertEquals($tracked_items, 3, 'Three items are tracked.');
 
     $this->getCalledMethods('backend');
     $page2->delete();
     $methods = $this->getCalledMethods('backend');
-    $this->assertEqual($methods, array(), 'Tracking of a delete operation could successfully be prevented.');
+    $this->assertEquals($methods, [], 'Tracking of a delete operation could successfully be prevented.');
 
     // Test disabling the index.
     $settings_path = $this->getIndexPath('edit');
     $this->drupalGet($settings_path);
-    $edit = array(
+    $edit = [
       'status' => FALSE,
       'datasource_configs[entity:node][bundles][default]' => 0,
       'datasource_configs[entity:node][bundles][selected][article]' => FALSE,
       'datasource_configs[entity:node][bundles][selected][page]' => FALSE,
-    );
-    $this->drupalPostForm(NULL, $edit, $this->t('Save'));
-    $this->assertText($this->t('The index was successfully saved.'));
+    ];
+    $this->submitForm($edit, $this->t('Save'));
+    $this->assertSession()->pageTextContains($this->t('The index was successfully saved.'));
 
     $tracked_items = $this->countTrackedItems();
-    $this->assertEqual($tracked_items, 0, 'No items are tracked.');
+    $this->assertEquals($tracked_items, 0, 'No items are tracked.');
 
     // Test re-enabling the index.
     $this->drupalGet($settings_path);
 
-    $edit = array(
+    $edit = [
       'status' => TRUE,
       'datasource_configs[entity:node][bundles][default]' => 0,
       'datasource_configs[entity:node][bundles][selected][article]' => TRUE,
       'datasource_configs[entity:node][bundles][selected][page]' => TRUE,
-    );
-    $this->drupalPostForm(NULL, $edit, $this->t('Save'));
-    $this->assertText($this->t('The index was successfully saved.'));
+    ];
+    $this->submitForm($edit, $this->t('Save'));
+    $this->checkForMetaRefresh();
+    $this->assertSession()->pageTextContains($this->t('The index was successfully saved.'));
 
     $tracked_items = $this->countTrackedItems();
-    $this->assertEqual($tracked_items, 3, 'Three items are tracked.');
+    $this->assertEquals($tracked_items, 3, 'Three items are tracked.');
 
     // Uncheck "default" and don't select any bundles. This should remove all
     // items from the tracking table.
-    $edit = array(
+    $edit = [
       'status' => TRUE,
       'datasource_configs[entity:node][bundles][default]' => 0,
       'datasource_configs[entity:node][bundles][selected][article]' => FALSE,
       'datasource_configs[entity:node][bundles][selected][page]' => FALSE,
-    );
-    $this->drupalPostForm($settings_path, $edit, $this->t('Save'));
-    $this->assertText($this->t('The index was successfully saved.'));
+    ];
+    $this->drupalGet($settings_path);
+    $this->submitForm($edit, $this->t('Save'));
+    $this->checkForMetaRefresh();
+    $this->assertSession()->pageTextContains($this->t('The index was successfully saved.'));
 
     $tracked_items = $this->countTrackedItems();
-    $this->assertEqual($tracked_items, 0, 'No items are tracked.');
+    $this->assertEquals($tracked_items, 0, 'No items are tracked.');
 
     // Leave "default" unchecked and select the "article" bundle. This should
     // re-add the two articles to the tracking table.
-    $edit = array(
+    $edit = [
       'status' => TRUE,
       'datasource_configs[entity:node][bundles][default]' => 0,
       'datasource_configs[entity:node][bundles][selected][article]' => TRUE,
       'datasource_configs[entity:node][bundles][selected][page]' => FALSE,
-    );
-    $this->drupalPostForm($settings_path, $edit, $this->t('Save'));
-    $this->assertText($this->t('The index was successfully saved.'));
+    ];
+    $this->drupalGet($settings_path);
+    $this->submitForm($edit, $this->t('Save'));
+    $this->checkForMetaRefresh();
+    $this->assertSession()->pageTextContains($this->t('The index was successfully saved.'));
 
     $tracked_items = $this->countTrackedItems();
-    $this->assertEqual($tracked_items, 2, 'Two items are tracked.');
+    $this->assertEquals($tracked_items, 2, 'Two items are tracked.');
 
     // Leave "default" unchecked and select only the "page" bundle. This should
     // result in only the page being present in the tracking table.
-    $edit = array(
+    $edit = [
       'status' => TRUE,
       'datasource_configs[entity:node][bundles][default]' => 0,
       'datasource_configs[entity:node][bundles][selected][article]' => FALSE,
       'datasource_configs[entity:node][bundles][selected][page]' => TRUE,
-    );
-    $this->drupalPostForm($settings_path, $edit, $this->t('Save'));
-    $this->assertText($this->t('The index was successfully saved.'));
+    ];
+    $this->drupalGet($settings_path);
+    $this->submitForm($edit, $this->t('Save'));
+    $this->checkForMetaRefresh();
+    $this->assertSession()->pageTextContains($this->t('The index was successfully saved.'));
 
     $tracked_items = $this->countTrackedItems();
-    $this->assertEqual($tracked_items, 1, 'One item is tracked.');
+    $this->assertEquals($tracked_items, 1, 'One item is tracked.');
 
     // Check "default" again and select the "article" bundle. This shouldn't
     // change the tracking table, which should still only contain the page.
-    $edit = array(
+    $edit = [
       'status' => TRUE,
       'datasource_configs[entity:node][bundles][default]' => 1,
       'datasource_configs[entity:node][bundles][selected][article]' => TRUE,
       'datasource_configs[entity:node][bundles][selected][page]' => FALSE,
-    );
-    $this->drupalPostForm($settings_path, $edit, $this->t('Save'));
-    $this->assertText($this->t('The index was successfully saved.'));
+    ];
+    $this->drupalGet($settings_path);
+    $this->submitForm($edit, $this->t('Save'));
+    $this->checkForMetaRefresh();
+    $this->assertSession()->pageTextContains($this->t('The index was successfully saved.'));
 
     $tracked_items = $this->countTrackedItems();
-    $this->assertEqual($tracked_items, 1, 'One item is tracked.');
+    $this->assertEquals($tracked_items, 1, 'One item is tracked.');
 
     // Leave "default" checked but now select only the "page" bundle. This
     // should result in only the articles being tracked.
-    $edit = array(
+    $edit = [
       'status' => TRUE,
       'datasource_configs[entity:node][bundles][default]' => 1,
       'datasource_configs[entity:node][bundles][selected][article]' => FALSE,
       'datasource_configs[entity:node][bundles][selected][page]' => TRUE,
-    );
-    $this->drupalPostForm($settings_path, $edit, $this->t('Save'));
-    $this->assertText($this->t('The index was successfully saved.'));
+    ];
+    $this->drupalGet($settings_path);
+    $this->submitForm($edit, $this->t('Save'));
+    $this->checkForMetaRefresh();
+    $this->assertSession()->pageTextContains($this->t('The index was successfully saved.'));
 
     $tracked_items = $this->countTrackedItems();
-    $this->assertEqual($tracked_items, 2, 'Two items are tracked.');
+    $this->assertEquals($tracked_items, 2, 'Two items are tracked.');
 
     // Index items, then check whether updating an article is handled correctly.
     $this->getCalledMethods('backend');
     $article1->save();
     $methods = $this->getCalledMethods('backend');
-    $this->assertEqual($methods, array('indexItems'), 'Update successfully tracked.');
+    $this->assertEquals($methods, ['indexItems'], 'Update successfully tracked.');
 
     $article1->search_api_skip_tracking = TRUE;
     $article1->save();
     $methods = $this->getCalledMethods('backend');
-    $this->assertEqual($methods, array(), 'Tracking of entity update successfully prevented.');
+    $this->assertEquals($methods, [], 'Tracking of entity update successfully prevented.');
     unset($article1->search_api_skip_tracking);
 
     // Delete an article. That should remove it from the item table.
     $article1->delete();
 
     $tracked_items = $this->countTrackedItems();
-    $this->assertEqual($tracked_items, 1, 'One item is tracked.');
+    $this->assertEquals($tracked_items, 1, 'One item is tracked.');
   }
 
   /**
@@ -645,7 +664,7 @@ class IntegrationTest extends WebTestBase {
   public function enableAllProcessors() {
     $this->drupalGet($this->getIndexPath('processors'));
 
-    $edit = array(
+    $edit = [
       'status[content_access]' => 1,
       'status[highlight]' => 1,
       'status[html_filter]' => 1,
@@ -655,9 +674,9 @@ class IntegrationTest extends WebTestBase {
       'status[stopwords]' => 1,
       'status[tokenizer]' => 1,
       'status[transliteration]' => 1,
-    );
-    $this->drupalPostForm(NULL, $edit, $this->t('Save'));
-    $this->assertText($this->t('The indexing workflow was successfully edited.'));
+    ];
+    $this->submitForm($edit, $this->t('Save'));
+    $this->assertSession()->pageTextContains($this->t('The indexing workflow was successfully edited.'));
   }
 
   /**
@@ -667,27 +686,27 @@ class IntegrationTest extends WebTestBase {
     $content_type_name = '&%@Content()_=';
 
     // Add a new content type with funky chars.
-    $edit = array(
+    $edit = [
       'name' => $content_type_name,
       'type' => '_content_',
-    );
+    ];
     $this->drupalGet('admin/structure/types/add');
-    $this->assertResponse(200);
-    $this->drupalPostForm(NULL, $edit, $this->t('Save and manage fields'));
+    $this->assertSession()->statusCodeEquals(200);
+    $this->submitForm($edit, $this->t('Save and manage fields'));
 
     // Add a field to that content type with funky chars.
     $field_name = '^6%{[*>.<"field';
-    FieldStorageConfig::create(array(
+    FieldStorageConfig::create([
       'field_name' => 'field__field_',
       'type' => 'string',
       'entity_type' => 'node',
-    ))->save();
-    FieldConfig::create(array(
+    ])->save();
+    FieldConfig::create([
       'field_name' => 'field__field_',
       'entity_type' => 'node',
       'bundle' => '_content_',
       'label' => $field_name,
-    ))->save();
+    ])->save();
 
     $url_options['query']['datasource'] = 'entity:node';
     $this->drupalGet($this->getIndexPath('fields/add'), $url_options);
@@ -698,13 +717,13 @@ class IntegrationTest extends WebTestBase {
     $this->drupalGet($this->getIndexPath('fields'));
     $this->assertHtmlEscaped($field_name);
 
-    $edit = array(
+    $edit = [
       'datasource_configs[entity:node][bundles][default]' => 1,
-    );
+    ];
     $this->drupalGet($this->getIndexPath('edit'));
     $this->assertHtmlEscaped($content_type_name);
-    $this->drupalPostForm(NULL, $edit, $this->t('Save'));
-    $this->assertText($this->t('The index was successfully saved.'));
+    $this->submitForm($edit, $this->t('Save'));
+    $this->assertSession()->pageTextContains($this->t('The index was successfully saved.'));
 
     $this->addField(NULL, 'rendered_item');
     $this->assertHtmlEscaped($content_type_name);
@@ -717,14 +736,14 @@ class IntegrationTest extends WebTestBase {
     // Make sure that hidden properties are not displayed.
     $url_options['query']['datasource'] = '';
     $this->drupalGet($this->getIndexPath('fields/add'), $url_options);
-    $this->assertNoText($this->t('Node access information'));
+    $this->assertSession()->pageTextNotContains($this->t('Node access information'));
 
-    $fields = array(
+    $fields = [
       'nid' => $this->t('ID'),
       'title' => $this->t('Title'),
       'body' => $this->t('Body'),
       'revision_log' => $this->t('Revision log message'),
-    );
+    ];
     foreach ($fields as $property_path => $label) {
       $this->addField('entity:node', $property_path, $label);
     }
@@ -733,8 +752,9 @@ class IntegrationTest extends WebTestBase {
     $fields = $index->getFields();
 
     $this->assertTrue(empty($fields['nid']), 'Field changes have not been persisted.');
-    $this->drupalPostForm($this->getIndexPath('fields'), array(), $this->t('Save changes'));
-    $this->assertText($this->t('The changes were successfully saved.'));
+    $this->drupalGet($this->getIndexPath('fields'));
+    $this->submitForm([], $this->t('Save changes'));
+    $this->assertSession()->pageTextContains($this->t('The changes were successfully saved.'));
 
     $index = $this->getIndex(TRUE);
     $fields = $index->getFields();
@@ -744,59 +764,69 @@ class IntegrationTest extends WebTestBase {
     // Ensure that we aren't offered to index properties of the "Content type"
     // property.
     $path = $this->getIndexPath('fields/add');
-    $url_options = array('query' => array('datasource' => 'entity:node'));
+    $url_options = ['query' => ['datasource' => 'entity:node']];
     $this->drupalGet($path, $url_options);
-    $this->assertNoRaw('property_path=type', 'Properties of the content type object cannot be indexed.');
+    $this->assertSession()->responseNotContains('property_path=type');
 
     // The "Content access" processor correctly marked fields as locked.
-    if ($this->assertTrue(!empty($fields['uid']), 'uid field is indexed.')) {
+    $this->assertTrue(!empty($fields['uid']), 'uid field is indexed.');
+    // @todo Do we need this if?
+    if (!empty($fields['uid'])) {
       $this->assertTrue($fields['uid']->isIndexedLocked(), 'uid field is locked.');
       $this->assertTrue($fields['uid']->isTypeLocked(), 'uid field is type-locked.');
-      $this->assertEqual($fields['uid']->getType(), 'integer', 'uid field has type integer.');
+      $this->assertEquals($fields['uid']->getType(), 'integer', 'uid field has type integer.');
     }
-    if ($this->assertTrue(!empty($fields['status']), 'status field is indexed.')) {
+    $this->assertTrue(!empty($fields['status']), 'status field is indexed.');
+    // @todo Do we need this if?
+    if (!empty($fields['status'])) {
       $this->assertTrue($fields['status']->isIndexedLocked(), 'status field is locked.');
       $this->assertTrue($fields['status']->isTypeLocked(), 'status field is type-locked.');
-      $this->assertEqual($fields['status']->getType(), 'boolean', 'status field has type boolean.');
+      $this->assertEquals($fields['status']->getType(), 'boolean', 'status field has type boolean.');
     }
 
     // Check that a 'parent_data_type.data_type' Search API field type => data
     // type mapping relationship works.
-    if ($this->assertTrue(!empty($fields['body']), 'body field is indexed.')) {
-      $this->assertEqual($fields['body']->getType(), 'text', 'Complex field mapping relationship works.');
+    $this->assertTrue(!empty($fields['body']), 'body field is indexed.');
+    // @todo Do we need this if?
+    if (!empty($fields['body'])) {
+      $this->assertEquals($fields['body']->getType(), 'text', 'Complex field mapping relationship works.');
     }
 
-    $edit = array(
+    $edit = [
       'fields[title][title]' => 'new_title',
       'fields[title][id]' => 'new_id',
       'fields[title][type]' => 'text',
       'fields[title][boost]' => '21.0',
       'fields[revision_log][type]' => 'search_api_test',
-    );
-    $this->drupalPostForm($this->getIndexPath('fields'), $edit, $this->t('Save changes'));
-    $this->assertText($this->t('The changes were successfully saved.'));
+    ];
+    $this->drupalGet($this->getIndexPath('fields'));
+    $this->submitForm($edit, $this->t('Save changes'));
+    $this->assertSession()->pageTextContains($this->t('The changes were successfully saved.'));
 
     $index = $this->getIndex(TRUE);
     $fields = $index->getFields();
 
-    if ($this->assertTrue(!empty($fields['new_id']), 'title field is indexed.')) {
-      $this->assertEqual($fields['new_id']->getLabel(), $edit['fields[title][title]'], 'title field title is saved.');
-      $this->assertEqual($fields['new_id']->getFieldIdentifier(), $edit['fields[title][id]'], 'title field id value is saved.');
-      $this->assertEqual($fields['new_id']->getType(), $edit['fields[title][type]'], 'title field type is text.');
-      $this->assertEqual($fields['new_id']->getBoost(), $edit['fields[title][boost]'], 'title field boost value is 21.');
+    $this->assertTrue(!empty($fields['new_id']), 'title field is indexed.');
+    if (!empty($fields['new_id'])) {
+      $this->assertEquals($fields['new_id']->getLabel(), $edit['fields[title][title]'], 'title field title is saved.');
+      $this->assertEquals($fields['new_id']->getFieldIdentifier(), $edit['fields[title][id]'], 'title field id value is saved.');
+      $this->assertEquals($fields['new_id']->getType(), $edit['fields[title][type]'], 'title field type is text.');
+      $this->assertEquals($fields['new_id']->getBoost(), $edit['fields[title][boost]'], 'title field boost value is 21.');
     }
 
-    if ($this->assertTrue(!empty($fields['revision_log']), 'revision_log field is indexed.')) {
-      $this->assertEqual($fields['revision_log']->getType(), $edit['fields[revision_log][type]'], 'revision_log field type is search_api_test.');
+    $this->assertTrue(!empty($fields['revision_log']), 'revision_log field is indexed.');
+    if (!empty($fields['revision_log'])) {
+      $this->assertEquals($fields['revision_log']->getType(), $edit['fields[revision_log][type]'], 'revision_log field type is search_api_test.');
     }
 
     // Reset field values to original.
-    $edit = array(
+    $edit = [
       'fields[new_id][title]' => 'Title',
       'fields[new_id][id]' => 'title',
-    );
-    $this->drupalPostForm($this->getIndexPath('fields'), $edit, $this->t('Save changes'));
-    $this->assertText($this->t('The changes were successfully saved.'));
+    ];
+    $this->drupalGet($this->getIndexPath('fields'));
+    $this->submitForm($edit, $this->t('Save changes'));
+    $this->assertSession()->pageTextContains($this->t('The changes were successfully saved.'));
   }
 
   /**
@@ -807,20 +837,21 @@ class IntegrationTest extends WebTestBase {
     $rows = $this->xpath('//*[@id="search-api-data-types-table"]/*/table/tbody/tr');
     $this->assertTrue(is_array($rows) && !empty($rows), 'Found a datatype listing.');
 
-    /** @var \SimpleXMLElement $row */
+    /** @var \Behat\Mink\Element\NodeElement $row */
     foreach ($rows as $row) {
-      $label = (string) $row->td[0];
-      $icon = basename($row->td[2]->img['src']);
-      $fallback = (string) $row->td[3];
+      $columns = $row->findAll('xpath', '/td');
+      $label = $columns[0]->getText();
+      $icon = basename($columns[2]->find('xpath', '/img')->getAttribute('src'));
+      $fallback = $columns[3]->getText();
 
       // Make sure we display the right icon and fallback column.
       if (strpos($label, 'Unsupported') === 0) {
-        $this->assertEqual($icon, 'error.svg', 'An error icon is shown for unsupported data types.');
-        $this->assertNotEqual($fallback, '', 'The fallback data type label is not empty for unsupported data types.');
+        $this->assertEquals($icon, 'error.svg', 'An error icon is shown for unsupported data types.');
+        $this->assertNotEquals($fallback, '', 'The fallback data type label is not empty for unsupported data types.');
       }
       else {
-        $this->assertEqual($icon, 'check.svg', 'A check icon is shown for supported data types.');
-        $this->assertEqual($fallback, '', 'The fallback data type label is empty for supported data types.');
+        $this->assertEquals($icon, 'check.svg', 'A check icon is shown for supported data types.');
+        $this->assertEquals($fallback, '', 'The fallback data type label is empty for supported data types.');
       }
     }
   }
@@ -838,7 +869,7 @@ class IntegrationTest extends WebTestBase {
    */
   protected function addField($datasource_id, $property_path, $label = NULL) {
     $path = $this->getIndexPath('fields/add');
-    $url_options = array('query' => array('datasource' => $datasource_id));
+    $url_options = ['query' => ['datasource' => $datasource_id]];
     list($parent_path) = Utility::splitPropertyPath($property_path);
     if ($parent_path) {
       $url_options['query']['property_path'] = $parent_path;
@@ -850,12 +881,11 @@ class IntegrationTest extends WebTestBase {
     // Unfortunately it doesn't seem possible to specify the clicked button by
     // anything other than label, so we have to pass it as extra POST data.
     $combined_property_path = Utility::createCombinedId($datasource_id, $property_path);
-    $this->assertRaw('name="' . $combined_property_path . '"');
-    $post = '&' . $this->serializePostValues(array($combined_property_path => $this->t('Add')));
-    $this->drupalPostForm(NULL, array(), NULL, array(), array(), NULL, $post);
+    $this->assertSession()->responseContains('name="' . $combined_property_path . '"');
+    $this->submitForm([], $combined_property_path);
     if ($label) {
       $args['%label'] = $label;
-      $this->assertRaw($this->t('Field %label was added to the index.', $args));
+      $this->assertSession()->responseContains($this->t('Field %label was added to the index.', $args));
     }
   }
 
@@ -864,45 +894,46 @@ class IntegrationTest extends WebTestBase {
    */
   protected function addFieldsWithDependenciesToIndex() {
     // Add a new link field.
-    FieldStorageConfig::create(array(
+    FieldStorageConfig::create([
       'field_name' => 'field_link',
       'type' => 'link',
       'entity_type' => 'node',
-    ))->save();
-    FieldConfig::create(array(
+    ])->save();
+    FieldConfig::create([
       'field_name' => 'field_link',
       'entity_type' => 'node',
       'bundle' => 'article',
       'label' => 'Link',
-    ))->save();
+    ])->save();
 
     // Add a new image field, for both articles and basic pages.
-    FieldStorageConfig::create(array(
+    FieldStorageConfig::create([
       'field_name' => 'field_image',
       'type' => 'image',
       'entity_type' => 'node',
-    ))->save();
-    FieldConfig::create(array(
+    ])->save();
+    FieldConfig::create([
       'field_name' => 'field_image',
       'entity_type' => 'node',
       'bundle' => 'article',
       'label' => 'Image',
-    ))->save();
-    FieldConfig::create(array(
+    ])->save();
+    FieldConfig::create([
       'field_name' => 'field_image',
       'entity_type' => 'node',
       'bundle' => 'page',
       'label' => 'Image',
-    ))->save();
+    ])->save();
 
-    $fields = array(
+    $fields = [
       'field_link' => $this->t('Link'),
       'field_image' => $this->t('Image'),
-    );
+    ];
     foreach ($fields as $property_path => $label) {
       $this->addField('entity:node', $property_path, $label);
     }
-    $this->drupalPostForm($this->getIndexPath('fields'), array(), $this->t('Save changes'));
+    $this->drupalGet($this->getIndexPath('fields'));
+    $this->submitForm([], $this->t('Save changes'));
 
     // Check that index configuration is updated with dependencies.
     $field_dependencies = (array) \Drupal::config('search_api.index.' . $this->indexId)->get('dependencies.config');
@@ -917,21 +948,25 @@ class IntegrationTest extends WebTestBase {
     // Remove a field and make sure that doing so does not remove the search
     // index.
     $this->drupalGet('admin/structure/types/manage/article/fields/node.article.field_link/delete');
-    $this->assertNoText(t('The listed configuration will be deleted.'));
-    $this->assertText(t('Search index'));
+    $this->assertSession()->pageTextNotContains(t('The listed configuration will be deleted.'));
+    $this->assertSession()->pageTextContains(t('Search index'));
 
-    $this->drupalPostForm(NULL, array(), t('Delete'));
+    $this->submitForm([], t('Delete'));
     $this->drupalGet('admin/structure/types/manage/article/fields/node.article.field_image/delete');
-    $this->drupalPostForm(NULL, array(), t('Delete'));
+    $this->submitForm([], t('Delete'));
 
-    if (!$this->assertNotNull($this->getIndex(), 'Index was not deleted.')) {
+    $this->assertNotNull($this->getIndex(), 'Index was not deleted.');
+    // @todo Do we need this if?
+    if (!$this->getIndex()) {
       throw new \Exception('Index deleted, test aborted.');
     }
 
     $this->drupalGet($this->getIndexPath('fields'));
-    $this->assertResponse(200);
-    $this->assertNoText('field_link', 'The Link field was removed from the index.');
-    $this->assertFieldByName('fields[field_image][id]', 'field_image', 'The Image field was not removed from the index.');
+    $this->assertSession()->statusCodeEquals(200);
+    $this->assertSession()->pageTextNotContains('field_link');
+    $this->assertSession()->fieldExists('fields[field_image][id]');
+    $this->assertSession()
+      ->fieldValueEquals('fields[field_image][id]', 'field_image');
 
     $field_dependencies = \Drupal::config('search_api.index.' . $this->indexId)->get('dependencies.config');
     $this->assertFalse(in_array('field.storage.node.field_link', (array) $field_dependencies), "The link field has been removed from the index's dependencies.");
@@ -943,12 +978,15 @@ class IntegrationTest extends WebTestBase {
    */
   protected function removeFieldsFromIndex() {
     // Find the "Remove" link for the "body" field.
-    $links = $this->xpath('//a[@data-drupal-selector=:id]', array(':id' => 'edit-fields-body-remove'));
-    if ($this->assertTrue($links, 'Found "Remove" link for body field')) {
-      $url_target = $this->getAbsoluteUrl($links[0]['href']);
+    $links = $this->xpath('//a[@data-drupal-selector=:id]', [':id' => 'edit-fields-body-remove']);
+    $this->assertNotEmpty($links, 'Found "Remove" link for body field');
+    // @todo do we need this if?
+    if (is_array($links) && !empty($links)) {
+      $url_target = $this->getAbsoluteUrl($links[0]->getAttribute('href'));
       $this->drupalGet($url_target);
-      $this->drupalPostForm($this->getIndexPath('fields'), array(), $this->t('Save changes'));
     }
+    $this->drupalGet($this->getIndexPath('fields'));
+    $this->submitForm([], $this->t('Save changes'));
 
     $index = $this->getIndex(TRUE);
     $fields = $index->getFields();
@@ -961,43 +999,47 @@ class IntegrationTest extends WebTestBase {
   protected function checkUnsavedChanges() {
     $this->addField('entity:node', 'changed', $this->t('Changed'));
     $this->drupalGet($this->getIndexPath('fields'));
-    $this->assertText($this->t('You have unsaved changes.'));
+    $this->assertSession()->pageTextContains($this->t('You have unsaved changes.'));
 
     // Log in a different admin user.
     $this->drupalLogin($this->adminUser2);
 
     // Construct the message that should be displayed.
-    $username = array(
+    $username = [
       '#theme' => 'username',
       '#account' => $this->adminUser,
-    );
-    $args = array(
+    ];
+    $args = [
       '@user' => \Drupal::getContainer()->get('renderer')->renderPlain($username),
       ':url' => $this->getIndex()->toUrl('break-lock-form')->toString(),
-    );
+    ];
     $message = (string) $this->t('This index is being edited by user @user, and is therefore locked from editing by others. This lock is @age old. Click here to <a href=":url">break this lock</a>.', $args);
     // Since we can't predict the age that will be shown, just check for
     // everything else.
     $message_parts = explode('@age', $message);
 
     $this->drupalGet($this->getIndexPath('fields/add'));
-    $this->assertRaw($message_parts[0]);
-    $this->assertRaw($message_parts[1]);
+    $this->assertSession()->responseContains($message_parts[0]);
+    $this->assertSession()->responseContains($message_parts[1]);
     $this->assertFalse($this->xpath('//input[not(@disabled)]'));
     $this->drupalGet($this->getIndexPath('fields/rendered_item/edit'));
-    $this->assertRaw($message_parts[0]);
-    $this->assertRaw($message_parts[1]);
+    $this->assertSession()->responseContains($message_parts[0]);
+    $this->assertSession()->responseContains($message_parts[1]);
     $this->assertFalse($this->xpath('//input[not(@disabled)]'));
     $this->drupalGet($this->getIndexPath('fields'));
-    $this->assertRaw($message_parts[0]);
-    $this->assertRaw($message_parts[1]);
+    $this->assertSession()->responseContains($message_parts[0]);
+    $this->assertSession()->responseContains($message_parts[1]);
     $this->assertFalse($this->xpath('//input[not(@disabled)]'));
-    if ($this->assertTrue(preg_match('#fields/break-lock">([^<>]*?)</a>#', $message, $m))) {
+    $match_result = preg_match('#fields/break-lock">([^<>]*?)</a>#', $message, $m);
+    $this->assertTrue($match_result);
+    // @todo Do we need this if?
+    if ($match_result) {
       $this->clickLink($m[1]);
     }
-    $this->assertRaw($this->t('By breaking this lock, any unsaved changes made by @user will be lost.', $args));
-    $this->drupalPostForm(NULL, array(), $this->t('Break lock'));
-    $this->assertText($this->t('The lock has been broken. You may now edit this search index.'));
+
+    $this->assertSession()->responseContains($this->t('By breaking this lock, any unsaved changes made by @user will be lost.', $args));
+    $this->submitForm([], $this->t('Break lock'));
+    $this->assertSession()->pageTextContains($this->t('The lock has been broken. You may now edit this search index.'));
     // Make sure the field has not been added to the index.
     $index = $this->getIndex(TRUE);
     $fields = $index->getFields();
@@ -1005,12 +1047,15 @@ class IntegrationTest extends WebTestBase {
 
     // Find the "Remove" link for the "title" field.
     $links = $this->xpath('//a[@data-drupal-selector=:id]', array(':id' => 'edit-fields-title-remove'));
-    if ($this->assertTrue($links, 'Found "Remove" link for title field')) {
-      $url_target = $this->getAbsoluteUrl($links[0]['href']);
+    $this->assertNotEmpty($links, 'Found "Remove" link for title field');
+    // @todo do we need this if?
+    if (is_array($links) && !empty($links)) {
+      $url_target = $this->getAbsoluteUrl($links[0]->getAttribute('href'));
       $this->drupalGet($url_target);
     }
-    $this->assertText($this->t('You have unsaved changes.'));
-    $this->drupalPostForm(NULL, array(), $this->t('Cancel'));
+
+    $this->assertSession()->pageTextContains($this->t('You have unsaved changes.'));
+    $this->submitForm([], $this->t('Cancel'));
 
     $this->assertTrue(!empty($fields['title']), 'The title field has not been removed from the index.');
   }
@@ -1021,101 +1066,105 @@ class IntegrationTest extends WebTestBase {
   protected function checkReferenceFieldsNonBaseFields() {
     // Add a new entity_reference field.
     $field_label = 'reference_field';
-    FieldStorageConfig::create(array(
+    FieldStorageConfig::create([
       'field_name' => 'field__reference_field_',
       'type' => 'entity_reference',
       'entity_type' => 'node',
       'cardinality' => FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED,
-      'settings' => array(
-        'allowed_values' => array(
-          array(
+      'settings' => [
+        'allowed_values' => [
+          [
             'target_type' => 'node',
-          ),
-        ),
-      ),
-    ))->save();
-    FieldConfig::create(array(
+          ],
+        ],
+      ],
+    ])->save();
+    FieldConfig::create([
       'field_name' => 'field__reference_field_',
       'entity_type' => 'node',
       'bundle' => 'article',
       'label' => $field_label,
-    ))->save();
+    ])->save();
     EntityFormDisplay::load('node.article.default')
-      ->setComponent('field__reference_field_', array(
+      ->setComponent('field__reference_field_', [
         'type' => 'entity_reference_autocomplete',
-      ))
+      ])
       ->save();
 
     $node_label = $this->getIndex()->getDatasource('entity:node')->label();
     $field_label = "$field_label » $node_label » $field_label";
     $this->addField('entity:node', 'field__reference_field_:entity:field__reference_field_', $field_label);
-    $this->drupalPostForm($this->getIndexPath('fields'), array(), $this->t('Save changes'));
+    $this->drupalGet($this->getIndexPath('fields'));
+    $this->submitForm([], $this->t('Save changes'));
 
     $this->drupalGet('node/2/edit');
-    $edit = array('field__reference_field_[0][target_id]' => 'Something (2)');
-    $this->drupalPostForm('node/2/edit', $edit, $this->t('Save and keep published'));
-    $indexed_values = \Drupal::state()->get("search_api_test.backend.indexed.{$this->indexId}", array());
-    $this->assertEqual(array(2), $indexed_values['entity:node/2:en']['field__reference_field_'], 'Correct value indexed for nested non-base field.');
+    $edit = ['field__reference_field_[0][target_id]' => 'Something (2)'];
+    $this->drupalGet('node/2/edit');
+    $this->submitForm($edit, $this->t('Save and keep published'));
+    $indexed_values = \Drupal::state()->get("search_api_test.backend.indexed.{$this->indexId}", []);
+    $this->assertEquals([2], $indexed_values['entity:node/2:en']['field__reference_field_'], 'Correct value indexed for nested non-base field.');
   }
 
   /**
    * Tests that configuring a processor works.
    */
   protected function configureFilter() {
-    $edit = array(
+    $edit = [
       'status[ignorecase]' => 1,
       'processors[ignorecase][settings][fields][title]' => 'title',
       'processors[ignorecase][settings][fields][field__field_]' => FALSE,
-    );
-    $this->drupalPostForm($this->getIndexPath('processors'), $edit, $this->t('Save'));
+    ];
+    $this->drupalGet($this->getIndexPath('processors'));
+    $this->submitForm($edit, $this->t('Save'));
     $index = $this->getIndex(TRUE);
     try {
       $configuration = $index->getProcessor('ignorecase')->getConfiguration();
       unset($configuration['weights']);
-      $expected = array(
-        'fields' => array(
+      $expected = [
+        'fields' => [
           'title',
-        ),
-      );
-      $this->assertEqual($expected, $configuration, 'Title field enabled for ignore case filter.');
+        ],
+      ];
+      $this->assertEquals($expected, $configuration, 'Title field enabled for ignore case filter.');
     }
     catch (SearchApiException $e) {
       $this->fail('"Ignore case" processor not enabled.');
     }
-    $this->assertText($this->t('The indexing workflow was successfully edited.'));
+    $this->assertSession()->pageTextContains($this->t('The indexing workflow was successfully edited.'));
   }
 
   /**
    * Tests that the "no values changed" message on the "Processors" tab works.
    */
   public function configureFilterPage() {
-    $edit = array();
-    $this->drupalPostForm($this->getIndexPath('processors'), $edit, $this->t('Save'));
-    $this->assertText('No values were changed.');
+    $this->drupalGet($this->getIndexPath('processors'));
+    $this->submitForm([], $this->t('Save'));
+    $this->assertSession()->pageTextContains('No values were changed.');
   }
 
   /**
    * Tests that changing or a processor doesn't always trigger reindexing.
    */
   protected function checkProcessorChanges() {
-    $edit = array(
+    $edit = [
       'status[ignorecase]' => 1,
       'processors[ignorecase][settings][fields][title]' => 'title',
-    );
+    ];
     // Enable just the ignore case processor, just to have a clean default state
     // before testing.
-    $this->drupalPostForm($this->getIndexPath('processors'), $edit, $this->t('Save'));
-    $this->assertResponse(200);
+    $this->drupalGet($this->getIndexPath('processors'));
+    $this->submitForm($edit, $this->t('Save'));
+    $this->assertSession()->statusCodeEquals(200);
 
-    $this->drupalPostForm(NULL, $edit, $this->t('Save'));
-    $this->assertResponse(200);
-    $this->assertText($this->t('No values were changed.'));
-    $this->assertNoText($this->t('All content was scheduled for reindexing so the new settings can take effect.'));
+    $this->submitForm($edit, $this->t('Save'));
+    $this->assertSession()->statusCodeEquals(200);
+    $this->assertSession()->pageTextContains($this->t('No values were changed.'));
+    $this->assertSession()->pageTextNotContains($this->t('All content was scheduled for reindexing so the new settings can take effect.'));
 
     $edit['processors[ignorecase][settings][fields][title]'] = FALSE;
-    $this->drupalPostForm(NULL, $edit, $this->t('Save'));
-    $this->assertResponse(200);
-    $this->assertText($this->t('All content was scheduled for reindexing so the new settings can take effect.'));
+    $this->submitForm($edit, $this->t('Save'));
+    $this->assertSession()->statusCodeEquals(200);
+    $this->assertSession()->pageTextContains($this->t('All content was scheduled for reindexing so the new settings can take effect.'));
   }
 
   /**
@@ -1131,13 +1180,15 @@ class IntegrationTest extends WebTestBase {
 
     // Change the boost of the field.
     $fields_path = $this->getIndexPath('fields');
-    $this->drupalPostForm($fields_path, array('fields[url][boost]' => '8.0'), $this->t('Save changes'));
-    $this->assertText('The changes were successfully saved.');
+    $this->drupalGet($fields_path);
+    $this->submitForm(['fields[url][boost]' => '8.0'], $this->t('Save changes'));
+    $this->assertSession()->pageTextContains('The changes were successfully saved.');
     $this->assertOptionSelected('edit-fields-url-boost', '8.0', 'Boost is correctly saved.');
 
     // Change the type of the field.
-    $this->drupalPostForm($fields_path, array('fields[url][type]' => 'text'), $this->t('Save changes'));
-    $this->assertText('The changes were successfully saved.');
+    $this->drupalGet($fields_path);
+    $this->submitForm(['fields[url][type]' => 'text'], $this->t('Save changes'));
+    $this->assertSession()->pageTextContains('The changes were successfully saved.');
     $this->assertOptionSelected('edit-fields-url-type', 'text', 'Type is correctly saved.');
   }
 
@@ -1156,43 +1207,49 @@ class IntegrationTest extends WebTestBase {
 
     // Re-enable tracking of all bundles. After this there should be two
     // unindexed items tracked by the index.
-    $edit = array(
+    $edit = [
       'status' => TRUE,
       'read_only' => TRUE,
       'datasource_configs[entity:node][bundles][default]' => 0,
       'datasource_configs[entity:node][bundles][selected][article]' => TRUE,
       'datasource_configs[entity:node][bundles][selected][page]' => TRUE,
-    );
-    $this->drupalPostForm($settings_path, $edit, $this->t('Save'));
-    $this->assertText($this->t('The index was successfully saved.'));
+    ];
+    $this->drupalGet($settings_path);
+    $this->submitForm($edit, $this->t('Save'));
+    $this->checkForMetaRefresh();
+    $this->assertSession()->pageTextContains($this->t('The index was successfully saved.'));
 
     $index = $this->getIndex(TRUE);
     $remaining_before = $this->countRemainingItems();
 
     $this->drupalGet($index_path);
 
-    $this->assertNoText($this->t('Index now'), 'The "Index now" button is not displayed.');
+    $this->assertSession()->pageTextNotContains($this->t('Index now'));
 
     // Also try indexing via the API to make sure it is really not possible.
     $indexed = $index->indexItems();
-    $this->assertEqual(0, $indexed, 'No items were indexed after setting the index to "read only".');
+    $this->assertEquals(0, $indexed, 'No items were indexed after setting the index to "read only".');
     $remaining_after = $this->countRemainingItems();
-    $this->assertEqual($remaining_before, $remaining_after, 'No items were indexed after setting the index to "read only".');
+    $this->assertEquals($remaining_before, $remaining_after, 'No items were indexed after setting the index to "read only".');
 
     // Disable "read only" and verify indexing now works again.
-    $edit = array(
+    $edit = [
       'read_only' => FALSE,
       'datasource_configs[entity:node][bundles][default]' => 1,
       'datasource_configs[entity:node][bundles][selected][article]' => FALSE,
       'datasource_configs[entity:node][bundles][selected][page]' => FALSE,
-    );
-    $this->drupalPostForm($settings_path, $edit, $this->t('Save'));
-    $this->assertText($this->t('The index was successfully saved.'));
+    ];
+    $this->drupalGet($settings_path);
+    $this->submitForm($edit, $this->t('Save'));
+    $this->checkForMetaRefresh();
+    $this->assertSession()->pageTextContains($this->t('The index was successfully saved.'));
 
-    $this->drupalPostForm($index_path, array(), $this->t('Index now'));
+    $this->drupalGet($index_path);
+    $this->submitForm([], $this->t('Index now'));
+    $this->checkForMetaRefresh();
 
     $remaining_after = $index->getTrackerInstance()->getRemainingItemsCount();
-    $this->assertEqual(0, $remaining_after, 'Items were indexed after removing the "read only" flag.');
+    $this->assertEquals(0, $remaining_after, 'Items were indexed after removing the "read only" flag.');
   }
 
   /**
@@ -1206,28 +1263,31 @@ class IntegrationTest extends WebTestBase {
   protected function disableEnableIndex() {
     // Disable the index and check that no items are tracked.
     $settings_path = $this->getIndexPath('edit');
-    $edit = array(
+    $edit = [
       'status' => FALSE,
-    );
-    $this->drupalPostForm($settings_path, $edit, $this->t('Save'));
-    $this->assertText($this->t('The index was successfully saved.'));
+    ];
+    $this->drupalGet($settings_path);
+    $this->submitForm($edit, $this->t('Save'));
+    $this->assertSession()->pageTextContains($this->t('The index was successfully saved.'));
 
     $tracked_items = $this->countTrackedItems();
-    $this->assertEqual(0, $tracked_items, 'No items are tracked after disabling the index.');
+    $this->assertEquals(0, $tracked_items, 'No items are tracked after disabling the index.');
     $tracked_items = \Drupal::database()->select('search_api_item', 'i')->countQuery()->execute()->fetchField();
-    $this->assertEqual(0, $tracked_items, 'No items left in tracking table.');
+    $this->assertEquals(0, $tracked_items, 'No items left in tracking table.');
 
     // @todo Also try to verify whether the items got deleted from the server.
 
     // Re-enable the index and check that the items are tracked again.
-    $edit = array(
+    $edit = [
       'status' => TRUE,
-    );
-    $this->drupalPostForm($settings_path, $edit, $this->t('Save'));
-    $this->assertText($this->t('The index was successfully saved.'));
+    ];
+    $this->drupalGet($settings_path);
+    $this->submitForm($edit, $this->t('Save'));
+    $this->checkForMetaRefresh();
+    $this->assertSession()->pageTextContains($this->t('The index was successfully saved.'));
 
     $tracked_items = $this->countTrackedItems();
-    $this->assertEqual(2, $tracked_items, 'After enabling the index, 2 items are tracked.');
+    $this->assertEquals(2, $tracked_items, 'After enabling the index, 2 items are tracked.');
   }
 
   /**
@@ -1246,30 +1306,33 @@ class IntegrationTest extends WebTestBase {
 
     // Enable indexing of users.
     $settings_path = $this->getIndexPath('edit');
-    $edit = array(
+    $edit = [
       'datasources[entity:user]' => TRUE,
       'datasources[entity:node]' => TRUE,
-    );
-    $this->drupalPostForm($settings_path, $edit, $this->t('Save'));
-    $this->assertText($this->t('Please configure the used datasources.'));
-    $this->drupalPostForm(NULL, array(), $this->t('Save'));
-    $this->assertText($this->t('The index was successfully saved.'));
+    ];
+    $this->drupalGet($settings_path);
+    $this->submitForm($edit, $this->t('Save'));
+    $this->assertSession()->pageTextContains($this->t('Please configure the used datasources.'));
+    $this->submitForm([], $this->t('Save'));
+    $this->checkForMetaRefresh();
+    $this->assertSession()->pageTextContains($this->t('The index was successfully saved.'));
 
     $tracked_items = $this->countTrackedItems();
-    $this->assertEqual($tracked_items, $user_count + $node_count, 'Correct number of items tracked after enabling the "User" datasource.');
+    $this->assertEquals($tracked_items, $user_count + $node_count, 'Correct number of items tracked after enabling the "User" datasource.');
 
     // Disable indexing of users again.
-    $edit = array(
+    $edit = [
       'datasources[entity:user]' => FALSE,
       'datasources[entity:node]' => TRUE,
-    );
-    $this->drupalPostForm($settings_path, $edit, $this->t('Save'));
-    $this->assertText($this->t('The index was successfully saved.'));
+    ];
+    $this->drupalGet($settings_path);
+    $this->submitForm($edit, $this->t('Save'));
+    $this->assertSession()->pageTextContains($this->t('The index was successfully saved.'));
 
     $this->executeTasks();
 
     $tracked_items = $this->countTrackedItems();
-    $this->assertEqual($tracked_items, $node_count, 'Correct number of items tracked after disabling the "User" datasource.');
+    $this->assertEquals($tracked_items, $node_count, 'Correct number of items tracked after disabling the "User" datasource.');
   }
 
   /**
@@ -1283,28 +1346,29 @@ class IntegrationTest extends WebTestBase {
     $index = $this->getIndex(TRUE);
 
     $node_count = \Drupal::entityQuery('node')->count()->execute();
-    $this->assertEqual($node_count, $this->countTrackedItems(), 'All nodes are correctly tracked by the index.');
+    $this->assertEquals($node_count, $this->countTrackedItems(), 'All nodes are correctly tracked by the index.');
 
     // Index all remaining items on the index.
     $index->indexItems();
 
     $remaining_items = $this->countRemainingItems();
-    $this->assertEqual($remaining_items, 0, 'All items have been successfully indexed.');
+    $this->assertEquals($remaining_items, 0, 'All items have been successfully indexed.');
 
     // Create a second search server.
     $this->createServer('test_server_2');
 
     // Change the index's server to the new one.
     $settings_path = $this->getIndexPath('edit');
-    $edit = array(
+    $edit = [
       'server' => $this->serverId,
-    );
-    $this->drupalPostForm($settings_path, $edit, $this->t('Save'));
-    $this->assertText($this->t('The index was successfully saved.'));
+    ];
+    $this->drupalGet($settings_path);
+    $this->submitForm($edit, $this->t('Save'));
+    $this->assertSession()->pageTextContains($this->t('The index was successfully saved.'));
 
     // After saving the new index, we should have called reindex.
     $remaining_items = $this->countRemainingItems();
-    $this->assertEqual($remaining_items, $node_count, 'All items still need to be indexed.');
+    $this->assertEquals($remaining_items, $node_count, 'All items still need to be indexed.');
   }
 
   /**
@@ -1315,21 +1379,23 @@ class IntegrationTest extends WebTestBase {
 
     // Load confirmation form.
     $this->drupalGet('admin/config/search/search-api/server/' . $this->serverId . '/delete');
-    $this->assertResponse(200, 'Server delete page exists');
-    $this->assertRaw(t('Are you sure you want to delete the search server %name?', array('%name' => $server->label())), 'Deleting a server sks for confirmation.');
-    $this->assertText(t('Deleting a server will disable all its indexes and their searches.'), 'Correct warning is displayed when deleting a server.');
+    $this->assertSession()->statusCodeEquals(200);
+    $this->assertSession()->responseContains(t('Are you sure you want to delete the search server %name?', ['%name' => $server->label()]));
+    $this->assertSession()->pageTextContains(t('Deleting a server will disable all its indexes and their searches.'));
 
     // Confirm deletion.
-    $this->drupalPostForm(NULL, NULL, t('Delete'));
-    $this->assertRaw(t('The search server %name has been deleted.', array('%name' => $server->label())), 'The server was deleted.');
+    $this->submitForm([], t('Delete'));
+    $this->assertSession()->responseContains(t('The search server %name has been deleted.', ['%name' => $server->label()]));
     $this->assertFalse(Server::load($this->serverId), 'Server could not be found anymore.');
-    $this->assertUrl('admin/config/search/search-api', array(), 'Correct redirect to search api overview page.');
+    $this->assertSession()->addressEquals('admin/config/search/search-api');
 
     // Confirm that the index hasn't been deleted.
-    $this->indexStorage->resetCache(array($this->indexId));
+    $this->indexStorage->resetCache([$this->indexId]);
     /** @var $index \Drupal\search_api\IndexInterface */
     $index = $this->indexStorage->load($this->indexId);
-    if ($this->assertTrue($index, 'The index associated with the server was not deleted.')) {
+    $this->assertTrue($index, 'The index associated with the server was not deleted.');
+    // @todo do we need this if?
+    if ($index instanceof IndexInterface) {
       $this->assertFalse($index->status(), 'The index associated with the server was disabled.');
       $this->assertNull($index->getServerId(), 'The index was removed from the server.');
     }
@@ -1346,7 +1412,7 @@ class IntegrationTest extends WebTestBase {
    */
   protected function getIndex($reset = FALSE) {
     if ($reset) {
-      $this->indexStorage->resetCache(array($this->indexId));
+      $this->indexStorage->resetCache([$this->indexId]);
     }
     return $this->indexStorage->load($this->indexId);
   }
@@ -1361,9 +1427,9 @@ class IntegrationTest extends WebTestBase {
    *   The raw string to check for.
    */
   protected function assertHtmlEscaped($string) {
-    $this->assertRaw(Html::escape($string));
-    $this->assertNoRaw(Html::escape(Html::escape($string)));
-    $this->assertNoRaw($string);
+    $this->assertSession()->responseContains(Html::escape($string));
+    $this->assertSession()->responseNotContains(Html::escape(Html::escape($string)));
+    $this->assertSession()->responseNotContains($string);
   }
 
 }
diff --git a/src/Tests/LanguageIntegrationTest.php b/tests/src/Functional/LanguageIntegrationTest.php
similarity index 62%
rename from src/Tests/LanguageIntegrationTest.php
rename to tests/src/Functional/LanguageIntegrationTest.php
index 7a02c9f..1d675ed 100644
--- a/src/Tests/LanguageIntegrationTest.php
+++ b/tests/src/Functional/LanguageIntegrationTest.php
@@ -1,6 +1,6 @@
 <?php
 
-namespace Drupal\search_api\Tests;
+namespace Drupal\Tests\search_api\Functional;
 
 use Drupal\language\Entity\ConfigurableLanguage;
 use Drupal\search_api\Entity\Index;
@@ -10,17 +10,17 @@ use Drupal\search_api\Entity\Index;
  *
  * @group search_api
  */
-class LanguageIntegrationTest extends WebTestBase {
+class LanguageIntegrationTest extends SearchApiBrowserTestBase {
 
   /**
    * {@inheritdoc}
    */
-  public static $modules = array(
+  public static $modules = [
     'node',
     'search_api',
     'search_api_test',
     'language',
-  );
+  ];
 
   /**
    * {@inheritdoc}
@@ -45,69 +45,75 @@ class LanguageIntegrationTest extends WebTestBase {
    */
   public function testIndexSettings() {
     // Create 2 articles.
-    $article1 = $this->drupalCreateNode(array('type' => 'article'));
-    $article2 = $this->drupalCreateNode(array('type' => 'article'));
+    $article1 = $this->drupalCreateNode(['type' => 'article']);
+    $article2 = $this->drupalCreateNode(['type' => 'article']);
 
     // Those 2 new nodes should be added to the tracking table immediately.
     $tracked_items = $this->countTrackedItems();
-    $this->assertEqual($tracked_items, 2, 'Two items are tracked.');
+    $this->assertEquals($tracked_items, 2, 'Two items are tracked.');
 
     // Add translations.
-    $translation = array('title' => 'test NL', 'body' => 'NL body');
+    $translation = ['title' => 'test NL', 'body' => 'NL body'];
     $article1->addTranslation('nl', $translation)->save();
-    $translation = array('title' => 'test2 NL', 'body' => 'NL body2');
+    $translation = ['title' => 'test2 NL', 'body' => 'NL body2'];
     $article2->addTranslation('nl', $translation)->save();
-    $translation = array('title' => 'cats', 'body' => 'Cats test');
+    $translation = ['title' => 'cats', 'body' => 'Cats test'];
     $article1->addTranslation('xx-lolspeak', $translation)->save();
 
     // The translations should be tracked as well, so we have a total of 5
     // indexed items.
     $tracked_items = $this->countTrackedItems();
-    $this->assertEqual($tracked_items, 5, 'Five items are tracked.');
+    $this->assertEquals($tracked_items, 5, 'Five items are tracked.');
 
     // Clear index.
     $this->drupalGet($this->getIndexPath());
-    $this->drupalPostForm(NULL, array(), $this->t('Clear all indexed data'));
-    $this->drupalPostForm(NULL, array(), $this->t('Confirm'));
+    $this->submitForm([], $this->t('Clear all indexed data'));
+    $this->submitForm([], $this->t('Confirm'));
 
     // Make sure all 5 items are successfully indexed.
     $this->drupalGet($this->getIndexPath());
-    $this->drupalPostForm(NULL, array(), $this->t('Index now'));
-    $this->assertText($this->t('Successfully indexed 5 items'));
+    $this->submitForm([], $this->t('Index now'));
+    $this->checkForMetaRefresh();
+    $this->assertSession()->pageTextContains($this->t('Successfully indexed 5 items'));
 
     // Change the datasource to disallow indexing of dutch.
-    $form_values = array(
+    $form_values = [
       'datasource_configs[entity:node][languages][default]' => 1,
       'datasource_configs[entity:node][languages][selected][nl]' => 1,
-    );
+    ];
     $this->drupalGet($this->getIndexPath('edit'));
-    $this->drupalPostForm(NULL, $form_values, $this->t('Save'));
-    $this->assertResponse(200);
-    $this->assertText('The index was successfully saved.');
+    $this->submitForm($form_values, $this->t('Save'));
+    $this->checkForMetaRefresh();
+    $this->assertSession()->statusCodeEquals(200);
+    $this->assertSession()->pageTextContains('The index was successfully saved.');
 
     // Make sure that we only have 3 indexed items now. The 2 original nodes
     // + 1 translation in lolspeak, the 2 dutch translations should be ignored.
     $this->drupalGet($this->getIndexPath());
-    $this->drupalPostForm(NULL, array(), $this->t('Index now'));
-    $this->assertText($this->t('Successfully indexed 3 items'));
+    $this->submitForm([], $this->t('Index now'));
+    $this->checkForMetaRefresh();
+    $this->assertSession()->pageTextContains($this->t('Successfully indexed 3 items'));
 
     // Change the datasource to only allow indexing of dutch.
-    $form_values = array(
+    $form_values = [
       'datasource_configs[entity:node][languages][default]' => 0,
       'datasource_configs[entity:node][languages][selected][nl]' => 1,
       'datasource_configs[entity:node][bundles][default]' => 0,
       'datasource_configs[entity:node][bundles][selected][article]' => 1,
-    );
+    ];
     $this->drupalGet($this->getIndexPath('edit'));
-    $this->drupalPostForm(NULL, $form_values, $this->t('Save'));
-    $this->assertResponse(200);
-    $this->assertText('The index was successfully saved.');
+    $this->submitForm($form_values, $this->t('Save'));
+    $this->checkForMetaRefresh();
+    $this->assertSession()->statusCodeEquals(200);
+    //Completed 1 of 1.
+    $this->assertSession()->pageTextContains('The index was successfully saved.');
 
     // Make sure that we only have 2 index items. The only indexed items should
     // be the dutch translations.
     $this->drupalGet($this->getIndexPath());
-    $this->drupalPostForm(NULL, array(), $this->t('Index now'));
-    $this->assertText($this->t('Successfully indexed 2 items'));
+    $this->submitForm([], $this->t('Index now'));
+    $this->checkForMetaRefresh();
+    $this->assertSession()->pageTextContains($this->t('Successfully indexed 2 items'));
   }
 
   /**
diff --git a/src/Tests/OverviewPageTest.php b/tests/src/Functional/OverviewPageTest.php
similarity index 53%
rename from src/Tests/OverviewPageTest.php
rename to tests/src/Functional/OverviewPageTest.php
index ebcab79..cec7323 100644
--- a/src/Tests/OverviewPageTest.php
+++ b/tests/src/Functional/OverviewPageTest.php
@@ -1,9 +1,10 @@
 <?php
 
-namespace Drupal\search_api\Tests;
+namespace Drupal\Tests\search_api\Functional;
 
 use Drupal\block\Entity\Block;
 use Drupal\Component\Utility\Html;
+use Drupal\Core\Url;
 use Drupal\search_api\Entity\Index;
 use Drupal\search_api\ServerInterface;
 
@@ -12,14 +13,14 @@ use Drupal\search_api\ServerInterface;
  *
  * @group search_api
  */
-class OverviewPageTest extends WebTestBase {
+class OverviewPageTest extends SearchApiBrowserTestBase {
 
   /**
    * {@inheritdoc}
    */
-  public static $modules = array(
+  public static $modules = [
     'block',
-  );
+  ];
 
   /**
    * The path of the overview page.
@@ -57,53 +58,62 @@ class OverviewPageTest extends WebTestBase {
   protected function checkServerAndIndexCreation() {
     $server_name = 'WebTest server';
     $index_name = 'WebTest index';
+    $actions = [
+      [Url::fromRoute('entity.search_api_server.add_form'), 'Add server'],
+      [Url::fromRoute('entity.search_api_index.add_form'), 'Add index'],
+    ];
 
     // Enable the "Local actions" block so we can verify which local actions are
     // displayed.
-    Block::create(array(
+    Block::create([
       'id' => 'classy_local_actions',
       'theme' => 'classy',
       'weight' => -20,
       'plugin' => 'local_actions_block',
       'region' => 'content',
-    ))->save();
+    ])->save();
 
     // Make sure the overview is empty.
     $this->drupalGet($this->overviewPageUrl);
+    $this->assertLocalAction($actions);
 
-    $this->assertNoText($server_name);
-    $this->assertNoText($index_name);
+    $this->assertSession()->pageTextNotContains($server_name);
+    $this->assertSession()->pageTextNotContains($index_name);
 
     // Test whether a newly created server appears on the overview page.
     $server = $this->getTestServer();
 
     $this->drupalGet($this->overviewPageUrl);
 
-    $this->assertText($server_name, 'Server present on overview page.');
-    $this->assertRaw($server->get('description'), 'Description is present');
-    $this->assertFieldByXPath('//tr[contains(@class,"' . Html::cleanCssIdentifier($server->getEntityTypeId() . '-' . $server->id()) . '") and contains(@class, "search-api-list-enabled")]', NULL, 'Server is in proper table');
+    $this->assertSession()->pageTextContains($server_name);
+    $this->assertSession()->responseContains($server->get('description'));
+    $server_class = Html::cleanCssIdentifier($server->getEntityTypeId() . '-' . $server->id());
+    $servers = $this->xpath('//tr[contains(@class,"' . $server_class. '") and contains(@class, "search-api-list-enabled")]');
+    $this->assertNotEmpty($servers, 'Server is in proper table');
 
     // Test whether a newly created index appears on the overview page.
     $index = $this->getTestIndex();
 
     $this->drupalGet($this->overviewPageUrl);
 
-    $this->assertText($index_name, 'Index present on overview page.');
-    $this->assertRaw($index->get('description'), 'Index description is present');
-    $this->assertFieldByXPath('//tr[contains(@class,"' . Html::cleanCssIdentifier($index->getEntityTypeId() . '-' . $index->id()) . '") and contains(@class, "search-api-list-enabled")]', NULL, 'Index is in proper table');
-    $this->assertNoLink($this->t('Execute pending tasks'), 'No pending tasks to execute.');
+    $this->assertSession()->pageTextContains($index_name);
+    $this->assertSession()->responseContains($index->get('description'));
+    $index_class = Html::cleanCssIdentifier($index->getEntityTypeId() . '-' . $index->id());
+    $fields = $this->xpath('//tr[contains(@class,"' . $index_class . '") and contains(@class, "search-api-list-enabled")]');
+    $this->assertNotEmpty($fields, 'Index is in proper table');
+    $this->assertSession()->linkNotExists($this->t('Execute pending tasks'), 'No pending tasks to execute.');
 
     // Tests that the "Execute pending tasks" local action is correctly
     // displayed when there are pending tasks.
     \Drupal::getContainer()
       ->get('search_api.task_manager')
-      ->addTask('deleteItems', $server, $index, array(''));
+      ->addTask('deleteItems', $server, $index, ['']);
     // Due to an (apparent) Core bug we need to clear the cache, otherwise the
     // "local actions" block gets displayed from cache (without the link). See
     // #2722237.
     \Drupal::cache('render')->invalidateAll();
     $this->drupalGet($this->overviewPageUrl);
-    $this->assertLink($this->t('Execute pending tasks'), 0);
+    $this->assertSession()->linkExists($this->t('Execute pending tasks'), 0);
   }
 
   /**
@@ -124,14 +134,17 @@ class OverviewPageTest extends WebTestBase {
     $this->drupalGet($this->overviewPageUrl);
     $server_class = Html::cleanCssIdentifier($server->getEntityTypeId() . '-' . $server->id());
     $index_class = Html::cleanCssIdentifier($index->getEntityTypeId() . '-' . $index->id());
-    $this->assertFieldByXPath('//tr[contains(@class,"' . $server_class . '") and contains(@class, "search-api-list-disabled")]', NULL, 'The server has been disabled.');
-    $this->assertFieldByXPath('//tr[contains(@class,"' . $index_class . '") and contains(@class, "search-api-list-disabled")]', NULL, 'The index has been disabled.');
+    $servers = $this->xpath('//tr[contains(@class,"' . $server_class . '") and contains(@class, "search-api-list-disabled")]');
+    $this->assertNotEmpty($servers, 'The server has been disabled.');
+    $indexes = $this->xpath('//tr[contains(@class,"' . $index_class . '") and contains(@class, "search-api-list-disabled")]');
+    $this->assertNotEmpty($indexes, 'The index has been disabled.');
 
     // Test that an index can't be enabled if its server is disabled.
     // @todo A non-working "Enable" link should not be displayed at all.
     $this->clickLink('Enable', 1);
     $this->drupalGet($this->overviewPageUrl);
-    $this->assertFieldByXPath('//tr[contains(@class,"' . $index_class . '") and contains(@class, "search-api-list-disabled")]', NULL, 'The index could not be enabled.');
+    $indexes = $this->xpath('//tr[contains(@class,"' . $index_class . '") and contains(@class, "search-api-list-disabled")]');
+    $this->assertNotEmpty($indexes, 'The index could not be enabled.');
 
     // Enable the server and try again.
     $server->setStatus(TRUE)->save();
@@ -141,21 +154,22 @@ class OverviewPageTest extends WebTestBase {
     // index.
     $this->clickLink('Enable');
     $this->drupalGet($this->overviewPageUrl);
-    $this->assertFieldByXPath('//tr[contains(@class,"' . $index_class . '") and contains(@class, "search-api-list-enabled")]', NULL, 'The index has been enabled.');
+    $indexes = $this->xpath('//tr[contains(@class,"' . $index_class . '") and contains(@class, "search-api-list-enabled")]');
+    $this->assertNotEmpty($indexes, 'The index has been enabled.');
 
     // Create a new index without a server assigned and test that it can't be
     // enabled. The overview UI is not very consistent at the moment, so test
     // using API functions for now.
-    $index2 = Index::create(array(
+    $index2 = Index::create([
       'id' => 'test_index_2',
       'name' => 'WebTest index 2',
-      'datasource_settings' => array(
-        'entity:node' => array(
+      'datasource_settings' => [
+        'entity:node' => [
           'plugin_id' => 'entity:node',
-          'settings' => array(),
-        ),
-      ),
-    ));
+          'settings' => [],
+        ],
+      ],
+    ]);
     $index2->save();
     $this->assertFalse($index2->status(), 'The newly created index without a server is disabled by default.');
 
@@ -172,20 +186,23 @@ class OverviewPageTest extends WebTestBase {
   protected function assertEntityStatusChange($entity) {
     $this->drupalGet($this->overviewPageUrl);
     $row_class = Html::cleanCssIdentifier($entity->getEntityTypeId() . '-' . $entity->id());
-    $this->assertFieldByXPath('//tr[contains(@class,"' . $row_class . '") and contains(@class, "search-api-list-enabled")]', NULL, 'The newly created entity is enabled by default.');
+    $rows = $this->xpath('//tr[contains(@class,"' .$row_class. '") and contains(@class, "search-api-list-enabled")]');
+    $this->assertNotEmpty($rows, 'The newly created entity is enabled by default.');
 
     // The first "Disable" link on the page belongs to our server, the second
     // one to our index.
     $this->clickLink('Disable', $entity instanceof ServerInterface ? 0 : 1);
 
     // Submit the confirmation form and test that the entity has been disabled.
-    $this->drupalPostForm(NULL, array(), 'Disable');
-    $this->assertFieldByXPath('//tr[contains(@class,"' . $row_class . '") and contains(@class, "search-api-list-disabled")]', NULL, 'The entity has been disabled.');
+    $this->submitForm([], 'Disable');
+    $rows = $this->xpath('//tr[contains(@class,"' .$row_class. '") and contains(@class, "search-api-list-disabled")]');
+    $this->assertNotEmpty($rows, 'The entity has been disabled.');
 
     // Now enable the entity and verify that the operation succeeded.
     $this->clickLink('Enable');
     $this->drupalGet($this->overviewPageUrl);
-    $this->assertFieldByXPath('//tr[contains(@class,"' . $row_class . '") and contains(@class, "search-api-list-enabled")]', NULL, 'The entity has benn enabled.');
+    $rows = $this->xpath('//tr[contains(@class,"' .$row_class. '") and contains(@class, "search-api-list-enabled")]');
+    $this->assertNotEmpty($rows, 'The entity has benn enabled.');
   }
 
   /**
@@ -195,19 +212,19 @@ class OverviewPageTest extends WebTestBase {
     $server = $this->getTestServer();
 
     $this->drupalGet($this->overviewPageUrl);
-    $basic_url = $this->urlGenerator->generateFromRoute('entity.search_api_server.canonical', array('search_api_server' => $server->id()));
-    $this->assertRaw('<a href="' . $basic_url . '/edit">Edit</a>', 'Edit operation presents');
-    $this->assertRaw('<a href="' . $basic_url . '/disable">Disable</a>', 'Disable operation presents');
-    $this->assertRaw('<a href="' . $basic_url . '/delete">Delete</a>', 'Delete operation presents');
-    $this->assertNoRaw('<a href="' . $basic_url . '/enable">Enable</a>', 'Enable operation is not present');
+    $basic_url = $this->urlGenerator->generateFromRoute('entity.search_api_server.canonical', ['search_api_server' => $server->id()]);
+    $this->assertSession()->responseContains('<a href="' . $basic_url . '/edit">Edit</a>');
+    $this->assertSession()->responseContains('<a href="' . $basic_url . '/disable">Disable</a>');
+    $this->assertSession()->responseContains('<a href="' . $basic_url . '/delete">Delete</a>');
+    $this->assertSession()->responseNotContains('<a href="' . $basic_url . '/enable">Enable</a>');
 
     $server->setStatus(FALSE)->save();
     $this->drupalGet($this->overviewPageUrl);
 
     // Since \Drupal\Core\Access\CsrfTokenGenerator uses the current session ID,
     // we cannot verify the validity of the token from here.
-    $this->assertRaw('<a href="' . $basic_url . '/enable?token=', 'Enable operation present');
-    $this->assertNoRaw('<a href="' . $basic_url . '/disable">Disable</a>', 'Disable operation  is not present');
+    $this->assertSession()->responseContains('<a href="' . $basic_url . '/enable?token=');
+    $this->assertSession()->responseNotContains('<a href="' . $basic_url . '/disable">Disable</a>');
   }
 
   /**
@@ -215,14 +232,38 @@ class OverviewPageTest extends WebTestBase {
    */
   protected function checkOverviewPermissions() {
     $this->drupalGet('admin/config');
-    $this->assertText('Search API', 'Search API menu link is displayed.');
+    $this->assertSession()->pageTextContains('Search API');
 
     $this->drupalGet($this->overviewPageUrl);
-    $this->assertResponse(200, 'Admin user can access the overview page.');
+    $this->assertSession()->statusCodeEquals(200);
 
     $this->drupalLogin($this->unauthorizedUser);
     $this->drupalGet($this->overviewPageUrl);
-    $this->assertResponse(403, "User without permissions doesn't have access to the overview page.");
+    $this->assertSession()->statusCodeEquals(403);
+  }
+
+  /**
+   * Asserts local actions in the page output.
+   *
+   * @param array $actions
+   *   A list of expected action link titles, keyed by the hrefs.
+   */
+  protected function assertLocalAction(array $actions) {
+    $elements = $this->xpath('//a[contains(@class, :class)]', [
+      ':class' => 'button-action',
+    ]);
+    $index = 0;
+    foreach ($actions as $action) {
+      /** @var \Drupal\Core\Url $url */
+      list($url, $title) = $action;
+      // SimpleXML gives us the unescaped text, not the actual escaped markup,
+      // so use a pattern instead to check the raw content.
+      // This behaviour is a bug in libxml, see
+      // https://bugs.php.net/bug.php?id=49437.
+      $this->assertSession()->responseMatches('@<a [^>]*class="[^"]*button-action[^"]*"[^>]*>' . preg_quote($title, '@') . '</@');
+      $this->assertEquals($elements[$index]->getAttribute('href'), $url->toString());
+      $index++;
+    }
   }
 
 }
diff --git a/src/Tests/ProcessorIntegrationTest.php b/tests/src/Functional/ProcessorIntegrationTest.php
similarity index 76%
rename from src/Tests/ProcessorIntegrationTest.php
rename to tests/src/Functional/ProcessorIntegrationTest.php
index 1729632..8e8ee48 100644
--- a/src/Tests/ProcessorIntegrationTest.php
+++ b/tests/src/Functional/ProcessorIntegrationTest.php
@@ -10,6 +10,7 @@ use Drupal\search_api\Entity\Server;
 use Drupal\search_api\Item\Field;
 use Drupal\search_api_test\PluginTestTrait;
 use Drupal\taxonomy\Tests\TaxonomyTestTrait;
+use Drupal\Tests\search_api\Functional\SearchApiBrowserTestBase;
 
 /**
  * Tests the admin UI for processors.
@@ -19,7 +20,7 @@ use Drupal\taxonomy\Tests\TaxonomyTestTrait;
  *
  * @group search_api
  */
-class ProcessorIntegrationTest extends WebTestBase {
+class ProcessorIntegrationTest extends SearchApiBrowserTestBase {
 
   use EntityReferenceTestTrait;
   use PluginTestTrait;
@@ -28,10 +29,10 @@ class ProcessorIntegrationTest extends WebTestBase {
   /**
    * {@inheritdoc}
    */
-  public static $modules = array(
+  public static $modules = [
     'filter',
     'taxonomy',
-  );
+  ];
 
   /**
    * {@inheritdoc}
@@ -41,17 +42,17 @@ class ProcessorIntegrationTest extends WebTestBase {
     $this->drupalLogin($this->adminUser);
 
     $this->indexId = 'test_index';
-    $index = Index::create(array(
+    $index = Index::create([
       'name' => 'Test index',
       'id' => $this->indexId,
       'status' => 1,
-      'datasource_settings' => array(
-        'entity:node' => array(
+      'datasource_settings' => [
+        'entity:node' => [
           'plugin_id' => 'entity:node',
-          'settings' => array(),
-        ),
-      ),
-    ));
+          'settings' => [],
+        ],
+      ],
+    ]);
 
     // Add a node and a taxonomy term reference, both of which could be used to
     // create a hierarchy.
@@ -62,7 +63,7 @@ class ProcessorIntegrationTest extends WebTestBase {
       NULL,
       'taxonomy_term',
       'default',
-      array(),
+      [],
       FieldStorageDefinitionInterface::CARDINALITY_UNLIMITED
     );
     $this->createEntityReferenceField(
@@ -72,7 +73,7 @@ class ProcessorIntegrationTest extends WebTestBase {
       NULL,
       'node',
       'default',
-      array('target_bundles' => array('page' => 'page'))
+      ['target_bundles' => ['page' => 'page']]
     );
 
     // Index the taxonomy and entity reference fields.
@@ -100,16 +101,16 @@ class ProcessorIntegrationTest extends WebTestBase {
    */
   public function testProcessorIntegration() {
     // Some processors are always enabled.
-    $enabled = array('add_url', 'aggregated_field', 'rendered_item');
+    $enabled = ['add_url', 'aggregated_field', 'rendered_item'];
     $actual_processors = array_keys($this->loadIndex()->getProcessors());
     sort($actual_processors);
-    $this->assertEqual($enabled, $actual_processors);
+    $this->assertEquals($enabled, $actual_processors);
 
     // Ensure the hidden processors aren't displayed in the UI.
     $this->loadProcessorsTab();
     $hidden = $enabled;
     foreach ($hidden as $processor_id) {
-      $this->assertNoRaw(Html::escape($processor_id), "The \"$processor_id\" processor is not displayed in the UI.");
+      $this->assertSession()->responseNotContains(Html::escape($processor_id));
     }
 
     $this->checkAggregatedFieldsIntegration();
@@ -119,42 +120,42 @@ class ProcessorIntegrationTest extends WebTestBase {
     sort($enabled);
     $actual_processors = array_keys($this->loadIndex()->getProcessors());
     sort($actual_processors);
-    $this->assertEqual($enabled, $actual_processors);
+    $this->assertEquals($enabled, $actual_processors);
 
     $this->checkHighlightIntegration();
     $enabled[] = 'highlight';
     sort($enabled);
     $actual_processors = array_keys($this->loadIndex()->getProcessors());
     sort($actual_processors);
-    $this->assertEqual($enabled, $actual_processors);
+    $this->assertEquals($enabled, $actual_processors);
 
     $this->checkHtmlFilterIntegration();
     $enabled[] = 'html_filter';
     sort($enabled);
     $actual_processors = array_keys($this->loadIndex()->getProcessors());
     sort($actual_processors);
-    $this->assertEqual($enabled, $actual_processors);
+    $this->assertEquals($enabled, $actual_processors);
 
     $this->checkIgnoreCaseIntegration();
     $enabled[] = 'ignorecase';
     sort($enabled);
     $actual_processors = array_keys($this->loadIndex()->getProcessors());
     sort($actual_processors);
-    $this->assertEqual($enabled, $actual_processors);
+    $this->assertEquals($enabled, $actual_processors);
 
     $this->checkIgnoreCharactersIntegration();
     $enabled[] = 'ignore_character';
     sort($enabled);
     $actual_processors = array_keys($this->loadIndex()->getProcessors());
     sort($actual_processors);
-    $this->assertEqual($enabled, $actual_processors);
+    $this->assertEquals($enabled, $actual_processors);
 
     $this->checkNodeStatusIntegration();
     $enabled[] = 'node_status';
     sort($enabled);
     $actual_processors = array_keys($this->loadIndex()->getProcessors());
     sort($actual_processors);
-    $this->assertEqual($enabled, $actual_processors);
+    $this->assertEquals($enabled, $actual_processors);
 
     $this->checkRenderedItemIntegration();
 
@@ -163,28 +164,28 @@ class ProcessorIntegrationTest extends WebTestBase {
     sort($enabled);
     $actual_processors = array_keys($this->loadIndex()->getProcessors());
     sort($actual_processors);
-    $this->assertEqual($enabled, $actual_processors);
+    $this->assertEquals($enabled, $actual_processors);
 
     $this->checkTokenizerIntegration();
     $enabled[] = 'tokenizer';
     sort($enabled);
     $actual_processors = array_keys($this->loadIndex()->getProcessors());
     sort($actual_processors);
-    $this->assertEqual($enabled, $actual_processors);
+    $this->assertEquals($enabled, $actual_processors);
 
     $this->checkTransliterationIntegration();
     $enabled[] = 'transliteration';
     sort($enabled);
     $actual_processors = array_keys($this->loadIndex()->getProcessors());
     sort($actual_processors);
-    $this->assertEqual($enabled, $actual_processors);
+    $this->assertEquals($enabled, $actual_processors);
 
     $this->checkAddHierarchyIntegration();
     $enabled[] = 'hierarchy';
     sort($enabled);
     $actual_processors = array_keys($this->loadIndex()->getProcessors());
     sort($actual_processors);
-    $this->assertEqual($enabled, $actual_processors);
+    $this->assertEquals($enabled, $actual_processors);
 
     // The 'add_url' processor is not available to be removed because it's
     // locked.
@@ -192,14 +193,14 @@ class ProcessorIntegrationTest extends WebTestBase {
 
     // Check whether disabling processors also works correctly.
     $this->loadProcessorsTab();
-    $edit = array(
+    $edit = [
       'status[stopwords]' => FALSE,
-    );
-    $this->drupalPostForm(NULL, $edit, $this->t('Save'));
-    $enabled = array_values(array_diff($enabled, array('stopwords')));
+    ];
+    $this->submitForm($edit, $this->t('Save'));
+    $enabled = array_values(array_diff($enabled, ['stopwords']));
     $actual_processors = array_keys($this->loadIndex()->getProcessors());
     sort($actual_processors);
-    $this->assertEqual($enabled, $actual_processors);
+    $this->assertEquals($enabled, $actual_processors);
   }
 
   /**
@@ -209,41 +210,41 @@ class ProcessorIntegrationTest extends WebTestBase {
    */
   public function testLimitProcessors() {
     $this->loadProcessorsTab();
-    $this->assertResponse(200);
-    $this->assertText($this->t('Highlight'));
-    $this->assertText($this->t('Ignore character'));
-    $this->assertText($this->t('Tokenizer'));
-    $this->assertText($this->t('Stopwords'));
+    $this->assertSession()->statusCodeEquals(200);
+    $this->assertSession()->pageTextContains($this->t('Highlight'));
+    $this->assertSession()->pageTextContains($this->t('Ignore character'));
+    $this->assertSession()->pageTextContains($this->t('Tokenizer'));
+    $this->assertSession()->pageTextContains($this->t('Stopwords'));
 
     // Create a new server with the "search_api_test" backend.
-    $server = Server::create(array(
+    $server = Server::create([
       'id' => 'webtest_server',
       'name' => 'WebTest server',
       'description' => 'WebTest server',
       'backend' => 'search_api_test',
-      'backend_config' => array(),
-    ));
+      'backend_config' => [],
+    ]);
     $server->save();
-    $processors = array(
+    $processors = [
       'highlight',
       'ignore_character',
       'tokenizer',
       'stopwords',
-    );
+    ];
     $this->setReturnValue('backend', 'getDiscouragedProcessors', $processors);
 
     // Use the newly created server.
     $settings_path = 'admin/config/search/search-api/index/' . $this->indexId . '/edit';
     $this->drupalGet($settings_path);
-    $this->drupalPostForm(NULL, array('server' => 'webtest_server'), $this->t('Save'));
+    $this->submitForm(['server' => 'webtest_server'], $this->t('Save'));
 
     // Load the processors again and check that they are not shown anymore.
     $this->loadProcessorsTab();
-    $this->assertResponse(200);
-    $this->assertNoText($this->t('Highlight'));
-    $this->assertNoText($this->t('Ignore character'));
-    $this->assertNoText($this->t('Tokenizer'));
-    $this->assertNoText($this->t('Stopwords'));
+    $this->assertSession()->statusCodeEquals(200);
+    $this->assertSession()->pageTextNotContains($this->t('Highlight'));
+    $this->assertSession()->pageTextNotContains($this->t('Ignore character'));
+    $this->assertSession()->pageTextNotContains($this->t('Tokenizer'));
+    $this->assertSession()->pageTextNotContains($this->t('Stopwords'));
   }
 
   /**
@@ -260,22 +261,21 @@ class ProcessorIntegrationTest extends WebTestBase {
     $this->drupalGet($this->getIndexPath('fields/add'), $options);
 
     // See \Drupal\search_api\Tests\IntegrationTest::addField().
-    $this->assertRaw('name="aggregated_field"');
-    $post = '&' . $this->serializePostValues(array('aggregated_field' => $this->t('Add')));
-    $this->drupalPostForm(NULL, array(), NULL, array(), array(), NULL, $post);
+    $this->assertSession()->responseContains('name="aggregated_field"');
+    $this->submitForm([], 'aggregated_field');
     $args['%label'] = $this->t('Aggregated field');
-    $this->assertRaw($this->t('Field %label was added to the index.', $args));
-    $this->assertUrl($this->getIndexPath('fields/aggregated_field/edit'));
-    $edit = array(
+    $this->assertSession()->responseContains($this->t('Field %label was added to the index.', $args));
+    $this->assertSession()->addressEquals($this->getIndexPath('fields/aggregated_field/edit'));
+    $edit = [
       'type' => 'first',
       'fields[entity:node/title]' => 'title',
       'fields[entity:node/type]' => 'type',
       'fields[entity:node/uid]' => 'uid',
-    );
-    $this->drupalPostForm(NULL, $edit, $this->t('Save'));
+    ];
+    $this->submitForm($edit, $this->t('Save'));
 
-    $this->assertUrl($this->getIndexPath('fields'));
-    $this->assertRaw($this->t('The field configuration was successfully saved.'));
+    $this->assertSession()->addressEquals($this->getIndexPath('fields'));
+    $this->assertSession()->responseContains($this->t('The field configuration was successfully saved.'));
   }
 
   /**
@@ -289,13 +289,13 @@ class ProcessorIntegrationTest extends WebTestBase {
    * Tests the UI for the "Highlight" processor.
    */
   public function checkHighlightIntegration() {
-    $configuration = array(
+    $configuration = [
       'highlight' => 'never',
       'excerpt' => FALSE,
       'excerpt_length' => 128,
       'prefix' => '<em>',
       'suffix' => '</em>',
-    );
+    ];
     $this->editSettingsForm($configuration, 'highlight');
   }
 
@@ -303,14 +303,14 @@ class ProcessorIntegrationTest extends WebTestBase {
    * Tests the UI for the "HTML filter" processor.
    */
   public function checkHtmlFilterIntegration() {
-    $configuration = array(
+    $configuration = [
       'tags' => <<<TAGS
 h1: 4
 foo bar
 TAGS
-    );
+    ];
     $this->checkValidationError($configuration, 'html_filter', $this->t('Tags is not a valid YAML map.'));
-    $configuration = array(
+    $configuration = [
       'tags' => <<<TAGS
 h1:
   - 1
@@ -318,21 +318,21 @@ h1:
 h2: foo
 h3: -1
 TAGS
-    );
-    $errors = array(
-      $this->t("Boost value for tag @tag can't be an array.", array('@tag' => '<h1>')),
-      $this->t('Boost value for tag @tag must be numeric.', array('@tag' => '<h2>')),
-      $this->t('Boost value for tag @tag must be non-negative.', array('@tag' => '<h3>')),
-    );
+    ];
+    $errors = [
+      $this->t("Boost value for tag @tag can't be an array.", ['@tag' => '<h1>']),
+      $this->t('Boost value for tag @tag must be numeric.', ['@tag' => '<h2>']),
+      $this->t('Boost value for tag @tag must be non-negative.', ['@tag' => '<h3>']),
+    ];
     $this->checkValidationError($configuration, 'html_filter', $errors);
 
-    $configuration = $form_values = array(
+    $configuration = $form_values = [
       'title' => FALSE,
       'alt' => FALSE,
-      'tags' => array(
+      'tags' => [
         'h1' => 10,
-      ),
-    );
+      ],
+    ];
     $form_values['tags'] = 'h1: 10';
     $this->editSettingsForm($configuration, 'html_filter', $form_values);
   }
@@ -341,22 +341,22 @@ TAGS
    * Tests the UI for the "Ignore case" processor.
    */
   public function checkIgnoreCaseIntegration() {
-    $this->editSettingsForm(array(), 'ignorecase');
+    $this->editSettingsForm([], 'ignorecase');
   }
 
   /**
    * Tests the UI for the "Ignore characters" processor.
    */
   public function checkIgnoreCharactersIntegration() {
-    $configuration = array(
+    $configuration = [
       'ignorable' => ':)',
-    );
+    ];
     $this->checkValidationError($configuration, 'ignore_character', $this->t('The entered text is no valid regular expression.'));
 
-    $configuration = $form_values = array(
+    $configuration = $form_values = [
       'ignorable' => '[¿¡!?,.]',
-      'strip' => array(
-        'character_sets' => array(
+      'strip' => [
+        'character_sets' => [
           'Pc' => 'Pc',
           'Pd' => 'Pd',
           'Pe' => 'Pe',
@@ -377,9 +377,9 @@ TAGS
           'Zl' => FALSE,
           'Zp' => FALSE,
           'Zs' => FALSE,
-        ),
-      ),
-    );
+        ],
+      ],
+    ];
     $this->editSettingsForm($configuration, 'ignore_character', $form_values);
   }
 
@@ -404,33 +404,32 @@ TAGS
     $this->drupalGet($this->getIndexPath('fields/add'), $options);
 
     // See \Drupal\search_api\Tests\IntegrationTest::addField().
-    $this->assertRaw('name="rendered_item"');
-    $post = '&' . $this->serializePostValues(array('rendered_item' => $this->t('Add')));
-    $this->drupalPostForm(NULL, array(), NULL, array(), array(), NULL, $post);
+    $this->assertSession()->responseContains('name="rendered_item"');
+    $this->submitForm([], 'rendered_item');
     $args['%label'] = $this->t('Rendered HTML output');
-    $this->assertRaw($this->t('Field %label was added to the index.', $args));
-    $this->assertUrl($this->getIndexPath('fields/rendered_item/edit'));
-    $edit = array(
-      'roles[]' => array('authenticated'),
+    $this->assertSession()->responseContains($this->t('Field %label was added to the index.', $args));
+    $this->assertSession()->addressEquals($this->getIndexPath('fields/rendered_item/edit'));
+    $edit = [
+      'roles[]' => ['authenticated'],
       'view_mode[entity:node][article]' => 'default',
       'view_mode[entity:node][page]' => 'teaser',
-    );
-    $this->drupalPostForm(NULL, $edit, $this->t('Save'));
+    ];
+    $this->submitForm($edit, $this->t('Save'));
 
-    $this->assertUrl($this->getIndexPath('fields'));
-    $this->assertRaw($this->t('The field configuration was successfully saved.'));
+    $this->assertSession()->addressEquals($this->getIndexPath('fields'));
+    $this->assertSession()->responseContains($this->t('The field configuration was successfully saved.'));
   }
 
   /**
    * Tests the UI for the "Stopwords" processor.
    */
   public function checkStopWordsIntegration() {
-    $configuration = array(
-      'stopwords' => array('the'),
-    );
-    $form_values = array(
+    $configuration = [
+      'stopwords' => ['the'],
+    ];
+    $form_values = [
       'stopwords' => 'the',
-    );
+    ];
     $this->editSettingsForm($configuration, 'stopwords', $form_values);
   }
 
@@ -438,16 +437,16 @@ TAGS
    * Tests the UI for the "Tokenizer" processor.
    */
   public function checkTokenizerIntegration() {
-    $configuration = array(
+    $configuration = [
       'spaces' => ':)',
-    );
+    ];
     $this->checkValidationError($configuration, 'tokenizer', $this->t('The entered text is no valid regular expression.'));
 
-    $configuration = array(
+    $configuration = [
       'spaces' => '',
       'overlap_cjk' => FALSE,
       'minimum_word_size' => 2,
-    );
+    ];
     $this->editSettingsForm($configuration, 'tokenizer');
   }
 
@@ -455,25 +454,25 @@ TAGS
    * Tests the UI for the "Transliteration" processor.
    */
   public function checkTransliterationIntegration() {
-    $this->editSettingsForm(array(), 'transliteration');
+    $this->editSettingsForm([], 'transliteration');
   }
 
   /**
    * Tests the hierarchy processor.
    */
   protected function checkAddHierarchyIntegration() {
-    $configuration = array(
-      'fields' => array(
+    $configuration = [
+      'fields' => [
         'term_field' => 'taxonomy_term-parent',
         'parent_reference' => 'node-parent_reference',
-      ),
-    );
-    $edit = array(
-      'fields' => array(
-        'term_field' => array('status' => 1),
-        'parent_reference' => array('status' => 1),
-      ),
-    );
+      ],
+    ];
+    $edit = [
+      'fields' => [
+        'term_field' => ['status' => 1],
+        'parent_reference' => ['status' => 1],
+      ],
+    ];
     $this->editSettingsForm($configuration, 'hierarchy', $edit, TRUE, FALSE);
   }
 
@@ -497,10 +496,10 @@ TAGS
   protected function enableProcessor($processor_id) {
     $this->loadProcessorsTab();
 
-    $edit = array(
+    $edit = [
       "status[$processor_id]" => 1,
-    );
-    $this->drupalPostForm(NULL, $edit, $this->t('Save'));
+    ];
+    $this->submitForm($edit, $this->t('Save'));
     $this->assertTrue($this->loadIndex()->isValidProcessor($processor_id), "Successfully enabled the '$processor_id' processor.'");
   }
 
@@ -534,7 +533,7 @@ TAGS
     if ($enable) {
       $edit["status[$processor_id]"] = 1;
     }
-    $this->drupalPostForm(NULL, $edit, $this->t('Save'));
+    $this->submitForm($edit, $this->t('Save'));
 
     $processor = $this->loadIndex()->getProcessor($processor_id);
     $this->assertTrue($processor, "Successfully enabled the '$processor_id' processor.'");
@@ -545,7 +544,7 @@ TAGS
         unset($actual_configuration['fields']);
       }
       $configuration += $processor->defaultConfiguration();
-      $this->assertEqual($configuration, $actual_configuration, "Processor configuration for processor '$processor_id' was set correctly.");
+      $this->assertEquals($configuration, $actual_configuration, "Processor configuration for processor '$processor_id' was set correctly.");
     }
   }
 
@@ -564,16 +563,16 @@ TAGS
 
     $edit = $this->getFormValues($form_values, "processors[$processor_id][settings]");
     $edit["status[$processor_id]"] = 1;
-    $this->drupalPostForm(NULL, $edit, $this->t('Save'));
+    $this->submitForm($edit, $this->t('Save'));
 
     if (!is_array($messages)) {
-      $messages = array($messages);
+      $messages = [$messages];
     }
     foreach ($messages as $message) {
-      $this->assertText($message);
+      $this->assertSession()->responseContains($message);
     }
-    $this->assertNoText($this->t('The indexing workflow was successfully edited.'));
-    $this->assertNoText($this->t('No values were changed.'));
+    $this->assertSession()->pageTextNotContains($this->t('The indexing workflow was successfully edited.'));
+    $this->assertSession()->pageTextNotContains($this->t('No values were changed.'));
 
     $this->loadProcessorsTab(TRUE);
   }
@@ -590,7 +589,7 @@ TAGS
    *   An array of form values ready for submission.
    */
   protected function getFormValues(array $configuration, $prefix) {
-    $edit = array();
+    $edit = [];
 
     foreach ($configuration as $key => $value) {
       $key = $prefix . "[$key]";
diff --git a/src/Tests/WebTestBase.php b/tests/src/Functional/SearchApiBrowserTestBase.php
similarity index 61%
rename from src/Tests/WebTestBase.php
rename to tests/src/Functional/SearchApiBrowserTestBase.php
index d4fea72..4b39bcf 100644
--- a/src/Tests/WebTestBase.php
+++ b/tests/src/Functional/SearchApiBrowserTestBase.php
@@ -1,16 +1,19 @@
 <?php
 
-namespace Drupal\search_api\Tests;
+namespace Drupal\Tests\search_api\Functional;
 
+use Drupal\Component\Utility\Html;
+use Drupal\Core\EventSubscriber\AjaxResponseSubscriber;
+use Drupal\Core\EventSubscriber\MainContentViewSubscriber;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
 use Drupal\search_api\Entity\Index;
 use Drupal\search_api\Entity\Server;
-use Drupal\simpletest\WebTestBase as SimpletestWebTestBase;
+use Drupal\Tests\BrowserTestBase;
 
 /**
  * Provides the base class for web tests for Search API.
  */
-abstract class WebTestBase extends SimpletestWebTestBase {
+abstract class SearchApiBrowserTestBase extends BrowserTestBase {
 
   use StringTranslationTrait;
 
@@ -19,11 +22,11 @@ abstract class WebTestBase extends SimpletestWebTestBase {
    *
    * @var string[]
    */
-  public static $modules = array(
+  public static $modules = [
     'node',
     'search_api',
     'search_api_test',
-  );
+  ];
 
   /**
    * An admin user used for this test.
@@ -32,15 +35,28 @@ abstract class WebTestBase extends SimpletestWebTestBase {
    */
   protected $adminUser;
 
+  /* 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;
+
   /**
    * The permissions of the admin user.
    *
    * @var string[]
    */
-  protected $adminUserPermissions = array(
+  protected $adminUserPermissions = [
     'administer search_api',
     'access administration pages',
-  );
+  ];
 
   /**
    * A user without Search API admin permission.
@@ -78,23 +94,23 @@ abstract class WebTestBase extends SimpletestWebTestBase {
 
     // Create the users used for the tests.
     $this->adminUser = $this->drupalCreateUser($this->adminUserPermissions);
-    $this->unauthorizedUser = $this->drupalCreateUser(array('access administration pages'));
+    $this->unauthorizedUser = $this->drupalCreateUser(['access administration pages']);
     $this->anonymousUser = $this->drupalCreateUser();
 
     // Get the URL generator.
     $this->urlGenerator = $this->container->get('url_generator');
 
     // Create a node article type.
-    $this->drupalCreateContentType(array(
+    $this->drupalCreateContentType([
       'type' => 'article',
       'name' => 'Article',
-    ));
+    ]);
 
     // Create a node page type.
-    $this->drupalCreateContentType(array(
+    $this->drupalCreateContentType([
       'type' => 'page',
       'name' => 'Page',
-    ));
+    ]);
 
     // Do not use a batch for tracking the initial items after creating an
     // index when running the tests via the GUI. Otherwise, it seems Drupal's
@@ -113,13 +129,13 @@ abstract class WebTestBase extends SimpletestWebTestBase {
   public function getTestServer() {
     $server = Server::load('webtest_server');
     if (!$server) {
-      $server = Server::create(array(
+      $server = Server::create([
         'id' => 'webtest_server',
         'name' => 'WebTest server',
         'description' => 'WebTest server' . ' description',
         'backend' => 'search_api_test',
-        'backend_config' => array(),
-      ));
+        'backend_config' => [],
+      ]);
       $server->save();
     }
 
@@ -136,18 +152,18 @@ abstract class WebTestBase extends SimpletestWebTestBase {
     $this->indexId = 'webtest_index';
     $index = Index::load($this->indexId);
     if (!$index) {
-      $index = Index::create(array(
+      $index = Index::create([
         'id' => $this->indexId,
         'name' => 'WebTest index',
         'description' => 'WebTest index' . ' description',
         'server' => 'webtest_server',
-        'datasource_settings' => array(
-          'entity:node' => array(
+        'datasource_settings' => [
+          'entity:node' => [
             'plugin_id' => 'entity:node',
-            'settings' => array(),
-          ),
-        ),
-      ));
+            'settings' => [],
+          ],
+        ],
+      ]);
       $index->save();
     }
 
@@ -177,7 +193,34 @@ abstract class WebTestBase extends SimpletestWebTestBase {
   protected function executeTasks() {
     $task_manager = \Drupal::getContainer()->get('search_api.task_manager');
     $task_manager->executeAllTasks();
-    $this->assertEqual(0, $task_manager->getTasksCount(), 'No more pending tasks.');
+    $this->assertEquals(0, $task_manager->getTasksCount(), 'No more pending tasks.');
+  }
+
+  /**
+   * 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.
+   *
+   * @see https://www.drupal.org/node/2757023#comment-11445519
+   *
+   * @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++;
+        $this->drupalGet($this->getAbsoluteUrl(Html::decodeEntities($match['url'])));
+        $this->checkForMetaRefresh();
+      }
+    }
+    // Flusch number of RefreshCount.
+    $this->metaRefreshCount = 0;
+    return FALSE;
   }
 
 }
diff --git a/src/Tests/ViewsTest.php b/tests/src/Functional/ViewsTest.php
similarity index 59%
rename from src/Tests/ViewsTest.php
rename to tests/src/Functional/ViewsTest.php
index b0b8d3e..0c15b0f 100644
--- a/src/Tests/ViewsTest.php
+++ b/tests/src/Functional/ViewsTest.php
@@ -1,16 +1,15 @@
 <?php
 
-namespace Drupal\search_api\Tests;
+namespace Drupal\Tests\search_api\Functional;
 
 use Drupal\Component\Utility\Html;
+use Drupal\Core\Language\Language;
 use Drupal\Core\StringTranslation\StringTranslationTrait;
 use Drupal\Core\Url;
 use Drupal\entity_test\Entity\EntityTestMulRevChanged;
 use Drupal\language\Entity\ConfigurableLanguage;
 use Drupal\search_api\Entity\Index;
 use Drupal\search_api\SearchApiException;
-use Drupal\simpletest\WebTestBase as SimpletestWebTestBase;
-use Symfony\Component\DependencyInjection\ContainerInterface;
 use Drupal\search_api\Utility\Utility;
 
 /**
@@ -18,7 +17,7 @@ use Drupal\search_api\Utility\Utility;
  *
  * @group search_api
  */
-class ViewsTest extends SimpletestWebTestBase {
+class ViewsTest extends SearchApiBrowserTestBase {
 
   use ExampleContentTrait;
   use StringTranslationTrait;
@@ -28,12 +27,12 @@ class ViewsTest extends SimpletestWebTestBase {
    *
    * @var string[]
    */
-  public static $modules = array(
+  public static $modules = [
     'search_api_test_views',
     'views_ui',
     'language',
     'rest',
-  );
+  ];
 
   /**
    * A search index ID.
@@ -51,10 +50,15 @@ class ViewsTest extends SimpletestWebTestBase {
     // Add a second language.
     ConfigurableLanguage::createFromLangcode('nl')->save();
 
+    entity_test_create_bundle('entity_test_mulrev_changed', 'Entity Test Bundle', 'entity_test_mulrev_changed');
+    entity_test_create_bundle('item', 'item', 'entity_test_mulrev_changed');
+    entity_test_create_bundle('article', 'article', 'entity_test_mulrev_changed');
+
+    $this->insertExampleContent();
+
     \Drupal::getContainer()
       ->get('search_api.index_task_manager')
       ->addItemsAll(Index::load($this->indexId));
-    $this->insertExampleContent();
     $this->indexItems($this->indexId);
 
     // Do not use a batch for tracking the initial items after creating an
@@ -66,250 +70,232 @@ class ViewsTest extends SimpletestWebTestBase {
   }
 
   /**
-   * {@inheritdoc}
-   */
-  protected function installModulesFromClassProperty(ContainerInterface $container) {
-    // This will just set the Drupal state to include the necessary bundles for
-    // our test entity type. Otherwise, fields from those bundles won't be found
-    // and thus removed from the test index. (We can't do it in setUp(), before
-    // calling the parent method, since the container isn't set up at that
-    // point.)
-    $bundles = array(
-      'entity_test_mulrev_changed' => array('label' => 'Entity Test Bundle'),
-      'item' => array('label' => 'item'),
-      'article' => array('label' => 'article'),
-    );
-    \Drupal::state()->set('entity_test_mulrev_changed.bundles', $bundles);
-
-    parent::installModulesFromClassProperty($container);
-  }
-
-  /**
    * Tests a view with exposed filters.
    */
   public function testView() {
-    $this->checkResults(array(), array_keys($this->entities), 'Unfiltered search');
+    $this->checkResults([], array_keys($this->entities), 'Unfiltered search');
+
 
     $this->checkResults(
-      array('search_api_fulltext' => 'foobar'),
-      array(3),
+      ['search_api_fulltext' => 'foobar'],
+      [3],
       'Search for a single word'
     );
     $this->checkResults(
-      array('search_api_fulltext' => 'foo test'),
-      array(1, 2, 4),
+      ['search_api_fulltext' => 'foo test'],
+      [1, 2, 4],
       'Search for multiple words'
     );
-    $query = array(
+    $query = [
       'search_api_fulltext' => 'foo test',
       'search_api_fulltext_op' => 'or',
-    );
-    $this->checkResults($query, array(1, 2, 3, 4, 5), 'OR search for multiple words');
-    $query = array(
+    ];
+    $this->checkResults($query, [1, 2, 3, 4, 5], 'OR search for multiple words');
+    $query = [
       'search_api_fulltext' => 'foobar',
       'search_api_fulltext_op' => 'not',
-    );
-    $this->checkResults($query, array(1, 2, 4, 5), 'Negated search');
-    $query = array(
+    ];
+    $this->checkResults($query, [1, 2, 4, 5], 'Negated search');
+    $query = [
       'search_api_fulltext' => 'foo test',
       'search_api_fulltext_op' => 'not',
-    );
-    $this->checkResults($query, array(), 'Negated search for multiple words');
-    $query = array(
+    ];
+    $this->checkResults($query, [], 'Negated search for multiple words');
+    $query = [
       'search_api_fulltext' => 'fo',
-    );
+    ];
     $label = 'Search for short word';
-    $this->checkResults($query, array(), $label);
-    $this->assertText('You must include at least one positive keyword with 3 characters or more', "$label displayed the correct warning.");
-    $query = array(
+    $this->checkResults($query, [], $label);
+    $this->assertSession()->pageTextContains('You must include at least one positive keyword with 3 characters or more');
+    $query = [
       'search_api_fulltext' => 'foo to test',
-    );
+    ];
     $label = 'Fulltext search including short word';
-    $this->checkResults($query, array(1, 2, 4), $label);
-    $this->assertNoText('You must include at least one positive keyword with 3 characters or more', "$label didn't display a warning.");
+    $this->checkResults($query, [1, 2, 4], $label);
+    $this->assertSession()->pageTextNotContains('You must include at least one positive keyword with 3 characters or more');
 
-    $this->checkResults(array('id[value]' => 2), array(2), 'Search with ID filter');
+    $this->checkResults(['id[value]' => 2], [2], 'Search with ID filter');
 
-    $query = array(
+    $query = [
       'id[min]' => 2,
       'id[max]' => 4,
       'id_op' => 'between',
-    );
-    $this->checkResults($query, array(2, 3, 4), 'Search with ID "in between" filter');
+    ];
+    $this->checkResults($query, [2, 3, 4], 'Search with ID "in between" filter');
 
-    $query = array(
+    $query = [
       'id[min]' => 2,
       'id[max]' => 4,
       'id_op' => 'not between',
-    );
-    $this->checkResults($query, array(1, 5), 'Search with ID "not in between" filter');
+    ];
+    $this->checkResults($query, [1, 5], 'Search with ID "not in between" filter');
 
-    $query = array(
+    $query = [
       'id[value]' => 2,
       'id_op' => '>',
-    );
-    $this->checkResults($query, array(3, 4, 5), 'Search with ID "greater than" filter');
-    $query = array(
+    ];
+    $this->checkResults($query, [3, 4, 5], 'Search with ID "greater than" filter');
+    $query = [
       'id[value]' => 2,
       'id_op' => '!=',
-    );
-    $this->checkResults($query, array(1, 3, 4, 5), 'Search with ID "not equal" filter');
-    $query = array(
+    ];
+    $this->checkResults($query, [1, 3, 4, 5], 'Search with ID "not equal" filter');
+    $query = [
       'id_op' => 'empty',
-    );
-    $this->checkResults($query, array(), 'Search with ID "empty" filter');
-    $query = array(
+    ];
+    $this->checkResults($query, [], 'Search with ID "empty" filter');
+    $query = [
       'id_op' => 'not empty',
-    );
-    $this->checkResults($query, array(1, 2, 3, 4, 5), 'Search with ID "not empty" filter');
+    ];
+    $this->checkResults($query, [1, 2, 3, 4, 5], 'Search with ID "not empty" filter');
 
     $yesterday = strtotime('-1DAY');
-    $query = array(
+    $query = [
       'created[value]' => date('Y-m-d', $yesterday),
       'created_op' => '>',
-    );
-    $this->checkResults($query, array(1, 2, 3, 4, 5), 'Search with "Created after" filter');
-    $query = array(
+    ];
+    $this->checkResults($query, [1, 2, 3, 4, 5], 'Search with "Created after" filter');
+    $query = [
       'created[value]' => date('Y-m-d', $yesterday),
       'created_op' => '<',
-    );
-    $this->checkResults($query, array(), 'Search with "Created before" filter');
-    $query = array(
+    ];
+    $this->checkResults($query, [], 'Search with "Created before" filter');
+    $query = [
       'created_op' => 'empty',
-    );
-    $this->checkResults($query, array(), 'Search with "empty creation date" filter');
-    $query = array(
+    ];
+    $this->checkResults($query, [], 'Search with "empty creation date" filter');
+    $query = [
       'created_op' => 'not empty',
-    );
-    $this->checkResults($query, array(1, 2, 3, 4, 5), 'Search with "not empty creation date" filter');
+    ];
+    $this->checkResults($query, [1, 2, 3, 4, 5], 'Search with "not empty creation date" filter');
 
-    $this->checkResults(array('keywords[value]' => 'apple'), array(2, 4), 'Search with Keywords filter');
-    $query = array(
+    $this->checkResults(['keywords[value]' => 'apple'], [2, 4], 'Search with Keywords filter');
+    $query = [
       'keywords[min]' => 'aardvark',
       'keywords[max]' => 'calypso',
       'keywords_op' => 'between',
-    );
-    $this->checkResults($query, array(2, 4, 5), 'Search with Keywords "in between" filter');
+    ];
+    $this->checkResults($query, [2, 4, 5], 'Search with Keywords "in between" filter');
 
     // For the keywords filters with comparison operators, exclude entity 1
     // since that contains all the uppercase and special characters weirdness.
-    $query = array(
+    $query = [
       'id[value]' => 1,
       'id_op' => '!=',
       'keywords[value]' => 'melon',
       'keywords_op' => '>=',
-    );
-    $this->checkResults($query, array(2, 4, 5), 'Search with Keywords "greater than or equal" filter');
-    $query = array(
+    ];
+    $this->checkResults($query, [2, 4, 5], 'Search with Keywords "greater than or equal" filter');
+    $query = [
       'id[value]' => 1,
       'id_op' => '!=',
       'keywords[value]' => 'banana',
       'keywords_op' => '<',
-    );
-    $this->checkResults($query, array(2, 4), 'Search with Keywords "less than" filter');
-    $query = array(
+    ];
+    $this->checkResults($query, [2, 4], 'Search with Keywords "less than" filter');
+    $query = [
       'keywords[value]' => 'orange',
       'keywords_op' => '!=',
-    );
-    $this->checkResults($query, array(3, 4), 'Search with Keywords "not equal" filter');
-    $query = array(
+    ];
+    $this->checkResults($query, [3, 4], 'Search with Keywords "not equal" filter');
+    $query = [
       'keywords_op' => 'empty',
-    );
+    ];
     $label = 'Search with Keywords "empty" filter';
-    $this->checkResults($query, array(3), $label, 'all/all/all');
-    $query = array(
+    $this->checkResults($query, [3], $label, 'all/all/all');
+    $query = [
       'keywords_op' => 'not empty',
-    );
-    $this->checkResults($query, array(1, 2, 4, 5), 'Search with Keywords "not empty" filter');
-
-    $query = array(
-      'language' => array('***LANGUAGE_site_default***'),
-    );
-    $this->checkResults($query, array(1, 2, 3, 4, 5), 'Search with "Page content language" filter');
-    $query = array(
-      'language' => array('en'),
-    );
-    $this->checkResults($query, array(1, 2, 3, 4, 5), 'Search with "English" language filter');
-    $query = array(
-      'language' => array('und'),
-    );
-    $this->checkResults($query, array(), 'Search with "Not specified" language filter');
-    $query = array(
-      'language' => array(
+    ];
+    $this->checkResults($query, [1, 2, 4, 5], 'Search with Keywords "not empty" filter');
+
+    $query = [
+      'language' => ['***LANGUAGE_site_default***'],
+    ];
+    $this->checkResults($query, [1, 2, 3, 4, 5], 'Search with "Page content language" filter');
+    $query = [
+      'language' => ['en'],
+    ];
+    $this->checkResults($query, [1, 2, 3, 4, 5], 'Search with "English" language filter');
+    $query = [
+      'language' => [Language::LANGCODE_NOT_SPECIFIED],
+    ];
+    $this->checkResults($query, [], 'Search with "Not specified" language filter');
+    $query = [
+      'language' => [
         '***LANGUAGE_language_interface***',
         'zxx',
-      ),
-    );
-    $this->checkResults($query, array(1, 2, 3, 4, 5), 'Search with multiple languages filter');
+      ],
+    ];
+    $this->checkResults($query, [1, 2, 3, 4, 5], 'Search with multiple languages filter');
 
-    $query = array(
+    $query = [
       'search_api_fulltext' => 'foo to test',
       'id[value]' => 2,
       'id_op' => '>',
       'keywords_op' => 'not empty',
-    );
-    $this->checkResults($query, array(4), 'Search with multiple filters');
+    ];
+    $this->checkResults($query, [4], 'Search with multiple filters');
 
     // Test contextual filters. Configured contextual filters are:
     // 1: datasource
     // 2: type (not = true)
     // 3: keywords (break_phrase = true)
-    $this->checkResults(array(), array(4, 5), 'Search with arguments', 'entity:entity_test_mulrev_changed/item/grape');
+    $this->checkResults([], [4, 5], 'Search with arguments', 'entity:entity_test_mulrev_changed/item/grape');
 
     // "Type" doesn't have "break_phrase" enabled, so the second argument won't
     // have any effect.
-    $this->checkResults(array(), array(2, 4, 5), 'Search with arguments', 'all/item+article/strawberry+apple');
+    $this->checkResults([], [2, 4, 5], 'Search with arguments', 'all/item+article/strawberry+apple');
 
-    $this->checkResults(array(), array(), 'Search with unknown datasource argument', 'entity:foobar/all/all');
+    $this->checkResults([], [], 'Search with unknown datasource argument', 'entity:foobar/all/all');
 
-    $query = array(
+    $query = [
       'id[value]' => 1,
       'id_op' => '!=',
       'keywords[value]' => 'melon',
       'keywords_op' => '>=',
-    );
-    $this->checkResults($query, array(2, 5), 'Search with arguments and filters', 'entity:entity_test_mulrev_changed/all/orange');
+    ];
+    $this->checkResults($query, [2, 5], 'Search with arguments and filters', 'entity:entity_test_mulrev_changed/all/orange');
 
     // Make sure the datasource filter works correctly with multiple selections.
     $index = Index::load($this->indexId);
     $index->addDatasource($index->createPlugin('datasource', 'entity:user'));
     $index->save();
 
-    $query = array(
-      'datasource' => array('entity:user', 'entity:entity_test_mulrev_changed'),
+    $query = [
+      'datasource' => ['entity:user', 'entity:entity_test_mulrev_changed'],
       'datasource_op' => 'or',
-    );
-    $this->checkResults($query, array(1, 2, 3, 4, 5), 'Search with multiple datasource filters (OR)');
+    ];
+    $this->checkResults($query, [1, 2, 3, 4, 5], 'Search with multiple datasource filters (OR)');
 
-    $query = array(
-      'datasource' => array('entity:user', 'entity:entity_test_mulrev_changed'),
+    $query = [
+      'datasource' => ['entity:user', 'entity:entity_test_mulrev_changed'],
       'datasource_op' => 'and',
-    );
-    $this->checkResults($query, array(), 'Search with multiple datasource filters (AND)');
+    ];
+    $this->checkResults($query, [], 'Search with multiple datasource filters (AND)');
 
-    $query = array(
-      'datasource' => array('entity:user'),
+    $query = [
+      'datasource' => ['entity:user'],
       'datasource_op' => 'not',
-    );
-    $this->checkResults($query, array(1, 2, 3, 4, 5), 'Search for non-user results');
+    ];
+    $this->checkResults($query, [1, 2, 3, 4, 5], 'Search for non-user results');
 
-    $query = array(
-      'datasource' => array('entity:entity_test_mulrev_changed'),
+    $query = [
+      'datasource' => ['entity:entity_test_mulrev_changed'],
       'datasource_op' => 'not',
-    );
-    $this->checkResults($query, array(), 'Search for non-test entity results');
+    ];
+    $this->checkResults($query, [], 'Search for non-test entity results');
 
-    $query = array(
-      'datasource' => array('entity:user', 'entity:entity_test_mulrev_changed'),
+    $query = [
+      'datasource' => ['entity:user', 'entity:entity_test_mulrev_changed'],
       'datasource_op' => 'not',
-    );
-    $this->checkResults($query, array(), 'Search for results of no available datasource');
+    ];
+    $this->checkResults($query, [], 'Search for results of no available datasource');
 
     // Make sure there was a display plugin created for this view.
     $displays = \Drupal::getContainer()->get('plugin.manager.search_api.display')
       ->getInstances();
 
-    if ($displays === array()) {
+    if ($displays === []) {
       throw new SearchApiException("No displays are loaded, tests will fail.");
     }
 
@@ -318,8 +304,8 @@ class ViewsTest extends SimpletestWebTestBase {
     $this->assertTrue(array_key_exists('views_block:search_api_test_view__block_1', $displays), 'A display plugin was created for the test view block display.');
     $this->assertTrue(array_key_exists('views_rest:search_api_test_view__rest_export_1', $displays), 'A display plugin was created for the test view block display.');
     $view_url = Url::fromUserInput('/search-api-test')->toString();
-    $this->assertEqual($view_url, $displays[$display_id]->getUrl()->toString(), 'Display returns the correct path.');
-    $this->assertEqual('database_search_index', $displays[$display_id]->getIndex()->id(), 'Display returns the correct search index.');
+    $this->assertEquals($view_url, $displays[$display_id]->getUrl()->toString(), 'Display returns the correct path.');
+    $this->assertEquals('database_search_index', $displays[$display_id]->getIndex()->id(), 'Display returns the correct search index.');
 
     $admin_user = $this->drupalCreateUser([
       'administer search_api',
@@ -330,8 +316,8 @@ class ViewsTest extends SimpletestWebTestBase {
 
     // Delete the page display for the view.
     $this->drupalGet('admin/structure/views/view/search_api_test_view');
-    $this->drupalPostForm(NULL, [], $this->t('Delete Page'));
-    $this->drupalPostForm(NULL, [], $this->t('Save'));
+    $this->submitForm([], $this->t('Delete Page'));
+    $this->submitForm([], $this->t('Save'));
 
     drupal_flush_all_caches();
 
@@ -356,27 +342,26 @@ class ViewsTest extends SimpletestWebTestBase {
    *   (optional) A string to append to the search path.
    */
   protected function checkResults(array $query, array $expected_results = NULL, $label = 'Search', $arguments = '') {
-    $this->drupalGet('search-api-test/' . $arguments, array('query' => $query));
+    $this->drupalGet('search-api-test/' . $arguments, ['query' => $query]);
 
     if (isset($expected_results)) {
       $count = count($expected_results);
-      $count_assert_message = "$label returned correct number of results.";
       if ($count) {
-        $this->assertText("Displaying $count search results", $count_assert_message);
+        $this->assertSession()->pageTextContains("Displaying $count search results");
       }
       else {
-        $this->assertNoText('search results', $count_assert_message);
+        $this->assertSession()->pageTextNotContains('search results');
       }
 
       $expected_results = array_combine($expected_results, $expected_results);
-      $actual_results = array();
+      $actual_results = [];
       foreach ($this->entities as $id => $entity) {
         $entity_label = Html::escape($entity->label());
-        if (strpos($this->getRawContent(), ">$entity_label<") !== FALSE) {
+        if (strpos($this->getSession()->getPage()->getContent(), ">$entity_label<") !== FALSE) {
           $actual_results[$id] = $id;
         }
       }
-      $this->assertEqual($expected_results, $actual_results, "$label returned correct results.");
+      $this->assertEquals($expected_results, $actual_results, "$label returned correct results.");
     }
   }
 
@@ -385,13 +370,13 @@ class ViewsTest extends SimpletestWebTestBase {
    */
   public function testViewsAdmin() {
     // Add some Dutch nodes.
-    foreach (array(1, 2, 3, 4, 5) as $id) {
+    foreach ([1, 2, 3, 4, 5] as $id) {
       $entity = EntityTestMulRevChanged::load($id);
-      $entity = $entity->addTranslation('nl', array(
+      $entity = $entity->addTranslation('nl', [
         'body' => "dutch node $id",
         'category' => "dutch category $id",
-        'keywords' => array("dutch $id A", "dutch $id B"),
-      ));
+        'keywords' => ["dutch $id A", "dutch $id B"],
+      ]);
       $entity->save();
     }
     $this->entities = EntityTestMulRevChanged::loadMultiple();
@@ -400,17 +385,17 @@ class ViewsTest extends SimpletestWebTestBase {
     // For viewing the user name and roles of the user associated with test
     // entities, the logged-in user needs to have the permission to administer
     // both users and permissions.
-    $permissions = array(
+    $permissions = [
       'administer search_api',
       'access administration pages',
       'administer views',
       'administer users',
       'administer permissions',
-    );
+    ];
     $this->drupalLogin($this->drupalCreateUser($permissions));
 
     $this->drupalGet('admin/structure/views/view/search_api_test_view');
-    $this->assertResponse(200);
+    $this->assertSession()->statusCodeEquals(200);
 
     // Set the user IDs associated with our test entities.
     $users[] = $this->createUser();
@@ -424,42 +409,42 @@ class ViewsTest extends SimpletestWebTestBase {
 
     // Switch to "Table" format.
     $this->clickLink($this->t('Unformatted list'));
-    $this->assertResponse(200);
-    $edit = array(
+    $this->assertSession()->statusCodeEquals(200);
+    $edit = [
       'style[type]' => 'table',
-    );
-    $this->drupalPostForm(NULL, $edit, $this->t('Apply'));
-    $this->assertResponse(200);
-    $this->drupalPostForm(NULL, array(), $this->t('Apply'));
-    $this->assertResponse(200);
+    ];
+    $this->submitForm($edit, $this->t('Apply'));
+    $this->assertSession()->statusCodeEquals(200);
+    $this->submitForm([], $this->t('Apply'));
+    $this->assertSession()->statusCodeEquals(200);
 
     // Add the "User ID" relationship.
     $this->clickLink($this->t('Add relationships'));
-    $edit = array(
+    $edit = [
       'name[search_api_datasource_database_search_index_entity_entity_test_mulrev_changed.user_id]' => 'search_api_datasource_database_search_index_entity_entity_test_mulrev_changed.user_id',
-    );
-    $this->drupalPostForm(NULL, $edit, $this->t('Add and configure relationships'));
-    $this->drupalPostForm(NULL, array(), $this->t('Apply'));
+    ];
+    $this->submitForm($edit, $this->t('Add and configure relationships'));
+    $this->submitForm([], $this->t('Apply'));
 
     // Add new fields. First check that the listing seems correct.
     $this->clickLink($this->t('Add fields'));
-    $this->assertResponse(200);
-    $this->assertText($this->t('Test entity - revisions and data table datasource'));
-    $this->assertText($this->t('Authored on'));
-    $this->assertText($this->t('Body (indexed field)'));
-    $this->assertText($this->t('Index Test index'));
-    $this->assertText($this->t('Item ID'));
-    $this->assertText($this->t('Excerpt'));
-    $this->assertText($this->t('The search result excerpted to show found search terms'));
-    $this->assertText($this->t('Relevance'));
-    $this->assertText($this->t('The relevance of this search result with respect to the query'));
-    $this->assertText($this->t('Language code'));
-    $this->assertText($this->t('The user language code.'));
-    $this->assertText($this->t('(No description available)'));
-    $this->assertNoText($this->t('Error: missing help'));
+    $this->assertSession()->statusCodeEquals(200);
+    $this->assertSession()->pageTextContains($this->t('Test entity - revisions and data table datasource'));
+    $this->assertSession()->pageTextContains($this->t('Authored on'));
+    $this->assertSession()->pageTextContains($this->t('Body (indexed field)'));
+    $this->assertSession()->pageTextContains($this->t('Index Test index'));
+    $this->assertSession()->pageTextContains($this->t('Item ID'));
+    $this->assertSession()->pageTextContains($this->t('Excerpt'));
+    $this->assertSession()->pageTextContains($this->t('The search result excerpted to show found search terms'));
+    $this->assertSession()->pageTextContains($this->t('Relevance'));
+    $this->assertSession()->pageTextContains($this->t('The relevance of this search result with respect to the query'));
+    $this->assertSession()->pageTextContains($this->t('Language code'));
+    $this->assertSession()->pageTextContains($this->t('The user language code.'));
+    $this->assertSession()->pageTextContains($this->t('(No description available)'));
+    $this->assertSession()->pageTextNotContains($this->t('Error: missing help'));
 
     // Then add some fields.
-    $fields = array(
+    $fields = [
       'views.counter',
       'search_api_datasource_database_search_index_entity_entity_test_mulrev_changed.id',
       'search_api_index_database_search_index.search_api_datasource',
@@ -469,13 +454,13 @@ class ViewsTest extends SimpletestWebTestBase {
       'search_api_datasource_database_search_index_entity_entity_test_mulrev_changed.user_id',
       'search_api_entity_user.name',
       'search_api_entity_user.roles',
-    );
-    $edit = array();
+    ];
+    $edit = [];
     foreach ($fields as $field) {
       $edit["name[$field]"] = $field;
     }
-    $this->drupalPostForm(NULL, $edit, $this->t('Add and configure fields'));
-    $this->assertResponse(200);
+    $this->submitForm($edit, $this->t('Add and configure fields'));
+    $this->assertSession()->statusCodeEquals(200);
 
     // @todo For some strange reason, the "roles" field form is not included
     //   automatically in the series of field forms shown to us by Views. Deal
@@ -500,31 +485,31 @@ class ViewsTest extends SimpletestWebTestBase {
 
     // Add click sorting for all fields where this is possible.
     $this->clickLink($this->t('Settings'), 0);
-    $edit = array(
+    $edit = [
       'style_options[info][search_api_datasource][sortable]' => 1,
       'style_options[info][category][sortable]' => 1,
       'style_options[info][keywords][sortable]' => 1,
-    );
-    $this->drupalPostForm(NULL, $edit, $this->t('Apply'));
+    ];
+    $this->submitForm($edit, $this->t('Apply'));
 
     // Add a filter for the "Name" field.
     $this->clickLink($this->t('Add filter criteria'));
-    $edit = array(
+    $edit = [
       'name[search_api_index_database_search_index.name]' => 'search_api_index_database_search_index.name',
-    );
-    $this->drupalPostForm(NULL, $edit, $this->t('Add and configure filter criteria'));
-    $this->submitPluginForm(array());
+    ];
+    $this->submitForm($edit, $this->t('Add and configure filter criteria'));
+    $this->submitPluginForm([]);
 
     // Save the view.
-    $this->drupalPostForm(NULL, array(), $this->t('Save'));
-    $this->assertResponse(200);
+    $this->submitForm([], $this->t('Save'));
+    $this->assertSession()->statusCodeEquals(200);
 
     // Check the results.
     $this->drupalGet('search-api-test');
-    $this->assertResponse(200);
+    $this->assertSession()->statusCodeEquals(200);
 
     foreach ($this->entities as $id => $entity) {
-      $fields = array(
+      $fields = [
         'search_api_datasource',
         'id',
         'body',
@@ -533,7 +518,7 @@ class ViewsTest extends SimpletestWebTestBase {
         'user_id',
         'user_id:name',
         'user_id:roles',
-      );
+      ];
       foreach ($fields as $field) {
         $field_entity = $entity;
         while (strpos($field, ':')) {
@@ -545,7 +530,7 @@ class ViewsTest extends SimpletestWebTestBase {
         }
         // Check that both the English and the Dutch entity are present in the
         // results, with their correct field values.
-        $entities = array($field_entity);
+        $entities = [$field_entity];
         if ($field_entity->hasTranslation('nl')) {
           $entities[] = $field_entity->getTranslation('nl');
         }
@@ -553,31 +538,30 @@ class ViewsTest extends SimpletestWebTestBase {
           if ($field != 'search_api_datasource') {
             $data = Utility::extractFieldValues($field_entity->get($field));
             if (!$data) {
-              $data = array('[EMPTY]');
+              $data = ['[EMPTY]'];
             }
           }
           else {
-            $data = array('entity:entity_test_mulrev_changed');
+            $data = ['entity:entity_test_mulrev_changed'];
           }
           $row_num = 2 * $id + $i - 1;
           $prefix = "#$row_num [$field] ";
           $text = $prefix . implode("|$prefix", $data);
-          $translated = $i ? ' [translated]' : '';
-          $this->assertText($text, "Correct value displayed for field $field on entity #$id (\"$text\")$translated");
+          $this->assertSession()->pageTextContains($text);
         }
       }
     }
 
     // Check that click-sorting works correctly.
-    $options = array(
-      'query' => array(
+    $options = [
+      'query' => [
         'order' => 'category',
         'sort' => 'asc',
-      ),
-    );
+      ],
+    ];
     $this->drupalGet('search-api-test', $options);
-    $this->assertResponse(200);
-    $ordered_categories = array(
+    $this->assertSession()->statusCodeEquals(200);
+    $ordered_categories = [
       '[EMPTY]',
       'article_category',
       'article_category',
@@ -588,17 +572,17 @@ class ViewsTest extends SimpletestWebTestBase {
       'dutch category 5',
       'item_category',
       'item_category',
-    );
+    ];
     foreach ($ordered_categories as $i => $category) {
       ++$i;
-      $this->assertText("#$i [category] $category", "Row #$i is in correct order.");
+      $this->assertSession()->pageTextContains("#$i [category] $category");
     }
     $options['query']['sort'] = 'desc';
     $this->drupalGet('search-api-test', $options);
-    $this->assertResponse(200);
+    $this->assertSession()->statusCodeEquals(200);
     foreach (array_reverse($ordered_categories) as $i => $category) {
       ++$i;
-      $this->assertText("#$i [category] $category", "Row #$i is in correct order.");
+      $this->assertSession()->pageTextContains("#$i [category] $category");
     }
   }
 
@@ -623,9 +607,9 @@ class ViewsTest extends SimpletestWebTestBase {
 
     switch ($field) {
       case 'counter':
-        $edit = array(
+        $edit = [
           'options[exclude]' => TRUE,
-        );
+        ];
         break;
 
       case 'id':
@@ -673,13 +657,13 @@ class ViewsTest extends SimpletestWebTestBase {
    */
   protected function submitPluginForm(array $edit) {
     $button_label = $this->t('Apply');
-    $buttons = $this->xpath('//input[starts-with(@value, :label)]', array(':label' => $button_label));
+    $buttons = $this->xpath('//input[starts-with(@value, :label)]', [':label' => $button_label]);
     if ($buttons) {
       $button_label = $buttons[0]['value'];
     }
 
-    $this->drupalPostForm(NULL, $edit, $button_label);
-    $this->assertResponse(200);
+    $this->submitForm($edit, $button_label);
+    $this->assertSession()->statusCodeEquals(200);
   }
 
 }
diff --git a/tests/src/Kernel/BackendTestBase.php b/tests/src/Kernel/BackendTestBase.php
index 2395d11..235b778 100644
--- a/tests/src/Kernel/BackendTestBase.php
+++ b/tests/src/Kernel/BackendTestBase.php
@@ -12,8 +12,8 @@ use Drupal\search_api\Entity\Server;
 use Drupal\search_api\IndexInterface;
 use Drupal\search_api\Query\QueryInterface;
 use Drupal\search_api\Query\ResultSetInterface;
-use Drupal\search_api\Tests\ExampleContentTrait;
 use Drupal\search_api\Utility\Utility;
+use Drupal\Tests\search_api\Functional\ExampleContentTrait;
 
 /**
  * Provides a base class for backend tests.
diff --git a/tests/src/Kernel/ContentEntityDatasourceTest.php b/tests/src/Kernel/ContentEntityDatasourceTest.php
index 79947f6..34146bd 100644
--- a/tests/src/Kernel/ContentEntityDatasourceTest.php
+++ b/tests/src/Kernel/ContentEntityDatasourceTest.php
@@ -7,7 +7,7 @@ use Drupal\entity_test\Entity\EntityTestMulRevChanged;
 use Drupal\KernelTests\KernelTestBase;
 use Drupal\language\Entity\ConfigurableLanguage;
 use Drupal\search_api\Entity\Index;
-use Drupal\search_api\Tests\ExampleContentTrait;
+use Drupal\Tests\search_api\Functional\ExampleContentTrait;
 
 /**
  * Tests correct functionality of the content entity datasource.
