diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc
index ceb9982..9a64d69 100644
--- a/core/includes/install.core.inc
+++ b/core/includes/install.core.inc
@@ -22,6 +22,7 @@
 use Drupal\Core\StackMiddleware\ReverseProxyMiddleware;
 use Drupal\Core\Extension\ExtensionDiscovery;
 use Drupal\Core\DependencyInjection\ContainerBuilder;
+use Drupal\Core\SAPI\SAPIAdapter;
 use Drupal\Core\Url;
 use Drupal\language\Entity\ConfigurableLanguage;
 use Symfony\Cmf\Component\Routing\RouteObjectInterface;
@@ -90,7 +91,7 @@
  *
  * @see install_state_defaults()
  */
-function install_drupal($class_loader, $settings = array()) {
+function install_drupal($class_loader, $settings = array(), $sapi_adapter = NULL) {
   // Support the old way of calling this function with just a settings array.
   // @todo Remove this when Drush is updated in the Drupal testing
   //   infrastructure in https://www.drupal.org/node/2389243
@@ -109,7 +110,7 @@ function install_drupal($class_loader, $settings = array()) {
   try {
     // Begin the page request. This adds information about the current state of
     // the Drupal installation to the passed-in array.
-    install_begin_request($class_loader, $install_state);
+    install_begin_request($class_loader, $install_state, $sapi_adapter);
     // Based on the installation state, run the remaining tasks for this page
     // request, and collect any output.
     $output = install_run_tasks($install_state);
@@ -281,8 +282,11 @@ function install_state_defaults() {
  *   An array of information about the current installation state. This is
  *   modified with information gleaned from the beginning of the page request.
  */
-function install_begin_request($class_loader, &$install_state) {
+function install_begin_request($class_loader, &$install_state, $sapi_adapter = NULL) {
   $request = Request::createFromGlobals();
+  if (!isset($sapi_adapter)) {
+    $sapi_adapter = SAPIAdapter::createFromGlobals();
+  }
 
   // Add any installation parameters passed in via the URL.
   if ($install_state['interactive']) {
@@ -407,7 +411,7 @@ function install_begin_request($class_loader, &$install_state) {
   }
 
   // Only allow dumping the container once the hash salt has been created.
-  $kernel = InstallerKernel::createFromRequest($request, $class_loader, $environment, (bool) Settings::get('hash_salt', FALSE));
+  $kernel = InstallerKernel::createFromRequest($request, $class_loader, $environment, (bool) Settings::get('hash_salt', FALSE), $sapi_adapter);
   $kernel->setSitePath($site_path);
   $kernel->boot();
   $container = $kernel->getContainer();
diff --git a/core/lib/Drupal/Core/DrupalKernel.php b/core/lib/Drupal/Core/DrupalKernel.php
index ba2c0fb..af98413 100644
--- a/core/lib/Drupal/Core/DrupalKernel.php
+++ b/core/lib/Drupal/Core/DrupalKernel.php
@@ -19,6 +19,7 @@
 use Drupal\Core\Http\TrustedHostsRequestFactory;
 use Drupal\Core\Installer\InstallerRedirectTrait;
 use Drupal\Core\Language\Language;
+use Drupal\Core\SAPI\SAPIAdapter;
 use Drupal\Core\Site\Settings;
 use Drupal\Core\Test\TestDatabase;
 use Symfony\Cmf\Component\Routing\RouteObjectInterface;
@@ -214,15 +215,11 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
   protected $serviceProviders;
 
   /**
-   * Whether the PHP environment has been initialized.
+   * The SAPI adapter.
    *
-   * This legacy phase can only be booted once because it sets session INI
-   * settings. If a session has already been started, re-generating these
-   * settings would break the session.
-   *
-   * @var bool
+   * @var \Drupal\Core\SAPI\SAPIAdapterInterface.
    */
-  protected static $isEnvironmentInitialized = FALSE;
+  protected $sapiAdapter;
 
   /**
    * The site directory.
@@ -252,18 +249,22 @@ class DrupalKernel implements DrupalKernelInterface, TerminableInterface {
    * @param bool $allow_dumping
    *   (optional) FALSE to stop the container from being written to or read
    *   from disk. Defaults to TRUE.
-   * @param string $app_root
-   *   (optional) The path to the application root as a string. If not supplied,
-   *   the application root will be computed.
+   * @param \Drupal\Core\SAPI\SAPIAdapterInterface $sapi_adapter
+   *   (optional) A SAPI adapter to use in order to prepare the execution
+   *   environment. A suitable adapter is selected automatically if omitted.
    *
    * @return static
    *
    * @throws \Symfony\Component\HttpKernel\Exception\BadRequestHttpException
    *   In case the host name in the request is not trusted.
    */
-  public static function createFromRequest(Request $request, $class_loader, $environment, $allow_dumping = TRUE, $app_root = NULL) {
-    $kernel = new static($environment, $class_loader, $allow_dumping, $app_root);
-    static::bootEnvironment($app_root);
+   public static function createFromRequest(Request $request, $class_loader, $environment, $allow_dumping = TRUE, $sapi_adapter = NULL) {
+    if (!isset($sapi_adapter)) {
+      $sapi_adapter = SAPIAdapter::createFromGlobals();
+    }
+    $kernel = new static($environment, $class_loader, $allow_dumping, $sapi_adapter);
+    $core_root = dirname(dirname(substr(__DIR__, 0, -strlen(__NAMESPACE__))));
+    $sapi_adapter->prepareEnvironment($core_root, $environment);
     $kernel->initializeSettings($request);
     return $kernel;
   }
@@ -280,18 +281,21 @@ public static function createFromRequest(Request $request, $class_loader, $envir
    * @param bool $allow_dumping
    *   (optional) FALSE to stop the container from being written to or read
    *   from disk. Defaults to TRUE.
-   * @param string $app_root
-   *   (optional) The path to the application root as a string. If not supplied,
-   *   the application root will be computed.
+   * @param \Drupal\Core\SAPI\SAPIAdapterInterface $sapi_adapter
+   *   (optional) A SAPI adapter to use in order to prepare the execution
+   *   environment. A suitable adapter is selected automatically if omitted.
    */
-  public function __construct($environment, $class_loader, $allow_dumping = TRUE, $app_root = NULL) {
+  public function __construct($environment, $class_loader, $allow_dumping = TRUE, $sapi_adapter = NULL) {
     $this->environment = $environment;
     $this->classLoader = $class_loader;
     $this->allowDumping = $allow_dumping;
-    if ($app_root === NULL) {
-      $app_root = static::guessApplicationRoot();
+    if (isset($sapi_adapter)) {
+      $this->sapiAdapter = $sapi_adapter;
+    }
+    else {
+      $this->sapiAdapter = SAPIAdapter::createFromGlobals();
     }
-    $this->root = $app_root;
+    $this->root = dirname(dirname(substr(__DIR__, 0, -strlen(__NAMESPACE__))));
   }
 
   /**
@@ -638,7 +642,7 @@ public function terminate(Request $request, Response $response) {
    */
   public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = TRUE) {
     // Ensure sane PHP environment variables.
-    static::bootEnvironment();
+    $this->sapiAdapter->prepareEnvironment($this->root, $this->environment);static::bootEnvironment();
 
     try {
       $this->initializeSettings($request);
diff --git a/core/lib/Drupal/Core/SAPI/CliSAPIAdapter.php b/core/lib/Drupal/Core/SAPI/CliSAPIAdapter.php
new file mode 100644
index 0000000..3255a19
--- /dev/null
+++ b/core/lib/Drupal/Core/SAPI/CliSAPIAdapter.php
@@ -0,0 +1,32 @@
+<?php
+/**
+ * @file
+ * Contains \Drupal\Core\SAPI\CliSAPIAdapter.
+ */
+
+namespace Drupal\Core\SAPI;
+
+/**
+ * A SAPI adapter used when run from the command line.
+ */
+class CliSAPIAdapter extends SAPIAdapter {
+
+  /**
+   * {@inheritdocs}
+   */
+  static function createFromGlobals() {
+    return new static();
+  }
+
+  /**
+   * {@inheritdocs}
+   */
+  public function prepareEnvironment($root, $environment) {
+    parent::prepareEnvironment($root, $environment);
+
+    // Simpletest child site is never run in CLI.
+    if (!defined('DRUPAL_TEST_IN_CHILD_SITE')) {
+      define('DRUPAL_TEST_IN_CHILD_SITE', FALSE);
+    }
+  }
+}
diff --git a/core/lib/Drupal/Core/SAPI/HttpSAPIAdapter.php b/core/lib/Drupal/Core/SAPI/HttpSAPIAdapter.php
new file mode 100644
index 0000000..88b5d6b
--- /dev/null
+++ b/core/lib/Drupal/Core/SAPI/HttpSAPIAdapter.php
@@ -0,0 +1,74 @@
+<?php
+/**
+ * @file
+ * Contains \Drupal\Core\SAPI\HttpSAPIAdapter.
+ */
+
+namespace Drupal\Core\SAPI;
+
+/**
+ * A SAPI adapter used when application is served over HTTP.
+ */
+class HttpSAPIAdapter extends SAPIAdapter {
+
+  /**
+   * {@inheritdocs}
+   */
+  static function createFromGlobals() {
+    return new static();
+  }
+
+  /**
+   * {@inheritdocs}
+   */
+  public function prepareEnvironment($root, $environment) {
+    parent::prepareEnvironment($root, $environment);
+
+    // Enforce E_STRICT, but allow users to set levels not part of E_STRICT.
+    error_reporting(E_STRICT | E_ALL);
+
+    // Override PHP settings required for Drupal to work properly.
+    // sites/default/default.settings.php contains more runtime settings.
+    // The .htaccess file contains settings that cannot be changed at runtime.
+
+    // Use session cookies, not transparent sessions that puts the session id in
+    // the query string.
+    ini_set('session.use_cookies', '1');
+    ini_set('session.use_only_cookies', '1');
+    ini_set('session.use_trans_sid', '0');
+    // Don't send HTTP headers using PHP's session handler.
+    // Send an empty string to disable the cache limiter.
+    ini_set('session.cache_limiter', '');
+    // Use httponly session cookies.
+    ini_set('session.cookie_httponly', '1');
+
+    // Indicate that code is operating in a test child site.
+    if (!defined('DRUPAL_TEST_IN_CHILD_SITE')) {
+      if ($test_prefix = drupal_valid_test_ua()) {
+        // Only code that interfaces directly with tests should rely on this
+        // constant; e.g., the error/exception handler conditionally adds further
+        // error information into HTTP response headers that are consumed by
+        // Simpletest's internal browser.
+        define('DRUPAL_TEST_IN_CHILD_SITE', TRUE);
+
+        // Web tests are to be conducted with runtime assertions active.
+        assert_options(ASSERT_ACTIVE, TRUE);
+        // Now synchronize PHP 5 and 7's handling of assertions as much as
+        // possible.
+        \Drupal\Component\Assertion\Handle::register();
+
+        // Log fatal errors to the test site directory.
+        ini_set('log_errors', 1);
+        ini_set('error_log', $root . '/sites/simpletest/' . substr($test_prefix, 10) . '/error.log');
+      }
+      else {
+        // Ensure that no other code defines this.
+        define('DRUPAL_TEST_IN_CHILD_SITE', FALSE);
+      }
+    }
+
+    // Set the Drupal custom error handler.
+    set_error_handler('_drupal_error_handler');
+    set_exception_handler('_drupal_exception_handler');
+  }
+}
diff --git a/core/lib/Drupal/Core/SAPI/NullSAPIAdapter.php b/core/lib/Drupal/Core/SAPI/NullSAPIAdapter.php
new file mode 100644
index 0000000..670a99f
--- /dev/null
+++ b/core/lib/Drupal/Core/SAPI/NullSAPIAdapter.php
@@ -0,0 +1,30 @@
+<?php
+/**
+ * @file
+ * Contains \Drupal\Core\NullSAPIAdapter.
+ */
+
+namespace Drupal\Core\SAPI;
+
+/**
+ * A SAPI adapter for testing purposes.
+ *
+ * Use this adapter only when a new kernel is instantiated insid an environment
+ * which has been prepared already by a production SAPI adapter.
+ */
+class NullSAPIAdapter implements SAPIAdapterInterface {
+
+  /**
+   * {@inheritdocs}
+   */
+  static function createFromGlobals() {
+    return new static();
+  }
+
+  /**
+   * {@inheritdocs}
+   */
+  public function prepareEnvironment($core_root, $environment) {
+  }
+
+}
diff --git a/core/lib/Drupal/Core/SAPI/SAPIAdapter.php b/core/lib/Drupal/Core/SAPI/SAPIAdapter.php
new file mode 100644
index 0000000..22ea440
--- /dev/null
+++ b/core/lib/Drupal/Core/SAPI/SAPIAdapter.php
@@ -0,0 +1,46 @@
+<?php
+/**
+ * @file
+ * Contains \Drupal\Core\SAPI\SAPIAdapter.
+ */
+
+namespace Drupal\Core\SAPI;
+
+use Drupal\Component\Utility\Unicode;
+
+/**
+ * Common base class for all production level SAPI adapters.
+ */
+class SAPIAdapter implements SAPIAdapterInterface {
+
+  /**
+   * {@inheritdoc}
+   */
+  public static function createFromGlobals() {
+    if (PHP_SAPI === 'cli') {
+      return CliSAPIAdapter::createFromGlobals();
+    }
+    else {
+      return HttpSAPIAdapter::createFromGlobals();
+    }
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function prepareEnvironment($root, $environment) {
+    // Include our bootstrap file.
+    require_once $root . '/core/includes/bootstrap.inc';
+
+    // Set sane locale settings, to ensure consistent string, dates, times and
+    // numbers handling.
+    setlocale(LC_ALL, 'C');
+
+    // Detect string handling method.
+    Unicode::check();
+
+    // Enforce E_STRICT, but allow users to set levels not part of E_STRICT.
+    error_reporting(E_STRICT | E_ALL);
+  }
+
+}
diff --git a/core/lib/Drupal/Core/SAPI/SAPIAdapterInterface.php b/core/lib/Drupal/Core/SAPI/SAPIAdapterInterface.php
new file mode 100644
index 0000000..801995d
--- /dev/null
+++ b/core/lib/Drupal/Core/SAPI/SAPIAdapterInterface.php
@@ -0,0 +1,34 @@
+<?php
+/**
+ * @file
+ * Contains \Drupal\Core\SAPI\SAPIAdapterInterface.
+ */
+
+namespace Drupal\Core\SAPI;
+
+/**
+ * Interface for SAPI adapters.
+ *
+ * A SAPI adapter prepares the global scope as needed by the application
+ * depending on the current server API. E.g., it is responsible for setting up
+ * important ini-settings and for installing error/exception handlers.
+ */
+interface SAPIAdapterInterface {
+  /**
+   * Creates a new SAPI adapter suitable for the current execution environment.
+   *
+   * @return static
+   */
+  public static function createFromGlobals();
+
+  /**
+   * Sets up the global scope as needed by the application.
+   *
+   * @param string $root
+   *   The app root.
+   * @param string $environment
+   *   String indicating the environment, e.g., 'prod' or 'dev'.
+   */
+  public function prepareEnvironment($root, $environment);
+
+}
diff --git a/core/lib/Drupal/Core/Test/TestRunnerKernel.php b/core/lib/Drupal/Core/Test/TestRunnerKernel.php
index f76e82c..1b373e7 100644
--- a/core/lib/Drupal/Core/Test/TestRunnerKernel.php
+++ b/core/lib/Drupal/Core/Test/TestRunnerKernel.php
@@ -15,18 +15,17 @@ class TestRunnerKernel extends DrupalKernel {
   /**
    * {@inheritdoc}
    */
-  public static function createFromRequest(Request $request, $class_loader, $environment = 'test_runner', $allow_dumping = TRUE, $app_root = NULL) {
-    return parent::createFromRequest($request, $class_loader, $environment, $allow_dumping, $app_root);
+  public static function createFromRequest(Request $request, $class_loader, $environment = 'test_runner', $allow_dumping = TRUE, $sapi_adapter = NULL) {
+    return parent::createFromRequest($request, $class_loader, $environment, $allow_dumping, $sapi_adapter);
   }
 
   /**
    * {@inheritdoc}
    */
-  public function __construct($environment, $class_loader, $allow_dumping = FALSE, $app_root = NULL) {
-    // Force $allow_dumping to FALSE, because the test runner kernel should
-    // always have to rebuild its container, and potentially avoid isolation
-    // issues against the tests.
-    parent::__construct($environment, $class_loader, FALSE, $app_root);
+  public function __construct($environment, $class_loader, $allow_dumping = TRUE, $sapi_adapter = NULL) {
+    // Forcibly disable container dumping.
+    $allow_dumping = FALSE;
+    parent::__construct($environment, $class_loader, $allow_dumping, $sapi_adapter);
 
     // Prime the module list and corresponding Extension objects.
     // @todo Remove System module. Needed because
diff --git a/core/lib/Drupal/Core/Update/UpdateKernel.php b/core/lib/Drupal/Core/Update/UpdateKernel.php
index 4608f18..8911850 100644
--- a/core/lib/Drupal/Core/Update/UpdateKernel.php
+++ b/core/lib/Drupal/Core/Update/UpdateKernel.php
@@ -54,9 +54,8 @@ protected function cacheDrupalContainer(array $container_definition) {
    * {@inheritdoc}
    */
   public function handle(Request $request, $type = self::MASTER_REQUEST, $catch = TRUE) {
+    $this->sapiAdapter->prepareEnvironment($this->root, $this->environment);
     try {
-      static::bootEnvironment();
-
       // First boot up basic things, like loading the include files.
       $this->initializeSettings($request);
       $this->boot();
diff --git a/core/modules/simpletest/src/BrowserTestBase.php b/core/modules/simpletest/src/BrowserTestBase.php
index d4b73ef..abd347c 100644
--- a/core/modules/simpletest/src/BrowserTestBase.php
+++ b/core/modules/simpletest/src/BrowserTestBase.php
@@ -2,7 +2,28 @@
 
 namespace Drupal\simpletest;
 
-use Drupal\Tests\BrowserTestBase as BaseBrowserTestBase;
+use Behat\Mink\Driver\GoutteDriver;
+use Behat\Mink\Element\Element;
+use Behat\Mink\Mink;
+use Behat\Mink\Session;
+use Drupal\Component\Utility\SafeMarkup;
+use Drupal\Component\Utility\UrlHelper;
+use Drupal\Core\Cache\Cache;
+use Drupal\Core\Database\ConnectionNotDefinedException;
+use Drupal\Core\Database\Database;
+use Drupal\Core\DrupalKernel;
+use Drupal\Core\SAPI\NullSAPIAdapter;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\Session\AnonymousUserSession;
+use Drupal\Core\Session\UserSession;
+use Drupal\Core\Site\Settings;
+use Drupal\Core\StreamWrapper\StreamWrapperInterface;
+use Drupal\Core\Test\TestRunnerKernel;
+use Drupal\Core\Url;
+use Drupal\user\Entity\Role;
+use Drupal\user\Entity\User;
+use Drupal\user\UserInterface;
+use Symfony\Component\HttpFoundation\Request;
 
 /**
  * Provides a test case for functional Drupal tests.
@@ -19,5 +40,1311 @@
  * @deprecated in Drupal 8.1.x, will be removed before Drupal 9.0.
  *   Use Drupal\Tests\BrowserTestBase instead.
  */
-abstract class BrowserTestBase extends BaseBrowserTestBase {
+abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase {
+
+  use RandomGeneratorTrait;
+  use SessionTestTrait;
+
+  /**
+   * Class loader.
+   *
+   * @var object
+   */
+  protected $classLoader;
+
+  /**
+   * The site directory of this test run.
+   *
+   * @var string
+   */
+  protected $siteDirectory;
+
+  /**
+   * The database prefix of this test run.
+   *
+   * @var string
+   */
+  protected $databasePrefix;
+
+  /**
+   * The site directory of the original parent site.
+   *
+   * @var string
+   */
+  protected $originalSiteDirectory;
+
+  /**
+   * Time limit in seconds for the test.
+   *
+   * @var int
+   */
+  protected $timeLimit = 500;
+
+  /**
+   * The public file directory for the test environment.
+   *
+   * This is set in BrowserTestBase::prepareEnvironment().
+   *
+   * @var string
+   */
+  protected $publicFilesDirectory;
+
+  /**
+   * The private file directory for the test environment.
+   *
+   * This is set in BrowserTestBase::prepareEnvironment().
+   *
+   * @var string
+   */
+  protected $privateFilesDirectory;
+
+  /**
+   * The temp file directory for the test environment.
+   *
+   * This is set in BrowserTestBase::prepareEnvironment().
+   *
+   * @var string
+   */
+  protected $tempFilesDirectory;
+
+  /**
+   * The translation file directory for the test environment.
+   *
+   * This is set in BrowserTestBase::prepareEnvironment().
+   *
+   * @var string
+   */
+  protected $translationFilesDirectory;
+
+  /**
+   * The DrupalKernel instance used in the test.
+   *
+   * @var \Drupal\Core\DrupalKernel
+   */
+  protected $kernel;
+
+  /**
+   * The dependency injection container used in the test.
+   *
+   * @var \Symfony\Component\DependencyInjection\ContainerInterface
+   */
+  protected $container;
+
+  /**
+   * The config importer that can be used in a test.
+   *
+   * @var \Drupal\Core\Config\ConfigImporter
+   */
+  protected $configImporter;
+
+  /**
+   * The profile to install as a basis for testing.
+   *
+   * @var string
+   */
+  protected $profile = 'testing';
+
+  /**
+   * The current user logged in using the Mink controlled browser.
+   *
+   * @var \Drupal\user\UserInterface
+   */
+  protected $loggedInUser = FALSE;
+
+  /**
+   * The root user.
+   *
+   * @var \Drupal\Core\Session\UserSession
+   */
+  protected $rootUser;
+
+  /**
+   * The config directories used in this test.
+   *
+   * @var array
+   */
+  protected $configDirectories = array();
+
+  /**
+   * An array of custom translations suitable for drupal_rewrite_settings().
+   *
+   * @var array
+   */
+  protected $customTranslations;
+
+  /*
+   * Mink class for the default driver to use.
+   *
+   * Shoud be a fully qualified class name that implements
+   * Behat\Mink\Driver\DriverInterface.
+   *
+   * Value can be overridden using the environment variable MINK_DRIVER_CLASS.
+   *
+   * @var string.
+   */
+  protected $minkDefaultDriverClass = GoutteDriver::class;
+
+  /*
+   * Mink default driver params.
+   *
+   * If it's an array its contents are used as constructor params when default
+   * Mink driver class is instantiated.
+   *
+   * Can be overridden using the environment variable MINK_DRIVER_ARGS. In this
+   * case that variable should be a JSON array, for example:
+   * '["firefox", null, "http://localhost:4444/wd/hub"]'.
+   *
+   *
+   * @var array
+   */
+  protected $minkDefaultDriverArgs;
+
+  /**
+   * Mink session manager.
+   *
+   * @var \Behat\Mink\Mink
+   */
+  protected $mink;
+
+  /**
+   * {@inheritdoc}
+   *
+   * Browser tests are run in separate processes to prevent collisions between
+   * code that may be loaded by tests.
+   */
+  protected $runTestInSeparateProcess = TRUE;
+
+  /**
+   * {@inheritdoc}
+   */
+  protected $preserveGlobalState = FALSE;
+
+  /**
+   * Initializes Mink sessions.
+   */
+  protected function initMink() {
+    $driver = $this->getDefaultDriverInstance();
+
+    if ($driver instanceof GoutteDriver) {
+      $driver->getClient()->setClient(\Drupal::httpClient());
+    }
+
+    $session = new Session($driver);
+    $this->mink = new Mink();
+    $this->mink->registerSession('default', $session);
+    $this->mink->setDefaultSessionName('default');
+    $this->registerSessions();
+
+    return $session;
+  }
+
+  /**
+   * Gets an instance of the default Mink driver.
+   *
+   * @return Behat\Mink\Driver\DriverInterface
+   *   Instance of default Mink driver.
+   *
+   * @throws \InvalidArgumentException
+   *   When provided default Mink driver class can't be instantiated.
+   */
+  protected function getDefaultDriverInstance() {
+    // Get default driver params from environment if availables.
+    if ($arg_json = getenv('MINK_DRIVER_ARGS')) {
+      $this->minkDefaultDriverArgs = json_decode($arg_json);
+    }
+
+    // Get and check default driver class from environment if availables.
+    if ($minkDriverClass = getenv('MINK_DRIVER_CLASS')) {
+      if (class_exists($minkDriverClass)) {
+        $this->minkDefaultDriverClass = $minkDriverClass;
+      }
+      else {
+        throw new \InvalidArgumentException("Can't instantiate provided $minkDriverClass class by environment as default driver class.");
+      }
+    }
+
+    if (is_array($this->minkDefaultDriverArgs)) {
+       // Use ReflectionClass to instantiate class with received params.
+      $reflector = new ReflectionClass($this->minkDefaultDriverClass);
+      $driver = $reflector->newInstanceArgs($this->minkDefaultDriverArgs);
+    }
+    else {
+      $driver =  new $this->minkDefaultDriverClass();
+    }
+    return $driver;
+  }
+
+  /**
+   * Registers additional Mink sessions.
+   *
+   * Tests wishing to use a different driver or change the default driver should
+   * override this method.
+   *
+   * @code
+   *   // Register a new session that uses the MinkPonyDriver.
+   *   $pony = new MinkPonyDriver();
+   *   $session = new Session($pony);
+   *   $this->mink->registerSession('pony', $session);
+   * @endcode
+   */
+  protected function registerSessions() {}
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function setUp() {
+    global $base_url;
+    parent::setUp();
+
+    // Get and set the domain of the environment we are running our test
+    // coverage against.
+    $base_url = getenv('SIMPLETEST_BASE_URL');
+    if (!$base_url) {
+      throw new \Exception(
+        'You must provide a SIMPLETEST_BASE_URL environment variable to run some PHPUnit based functional tests.'
+      );
+    }
+
+    // Setup $_SERVER variable.
+    $parsed_url = parse_url($base_url);
+    $host = $parsed_url['host'] . (isset($parsed_url['port']) ? ':' . $parsed_url['port'] : '');
+    $path = isset($parsed_url['path']) ? rtrim(rtrim($parsed_url['path']), '/') : '';
+    $port = isset($parsed_url['port']) ? $parsed_url['port'] : 80;
+
+    // If the passed URL schema is 'https' then setup the $_SERVER variables
+    // properly so that testing will run under HTTPS.
+    if ($parsed_url['scheme'] === 'https') {
+      $_SERVER['HTTPS'] = 'on';
+    }
+    $_SERVER['HTTP_HOST'] = $host;
+    $_SERVER['REMOTE_ADDR'] = '127.0.0.1';
+    $_SERVER['SERVER_ADDR'] = '127.0.0.1';
+    $_SERVER['SERVER_PORT'] = $port;
+    $_SERVER['SERVER_SOFTWARE'] = NULL;
+    $_SERVER['SERVER_NAME'] = 'localhost';
+    $_SERVER['REQUEST_URI'] = $path . '/';
+    $_SERVER['REQUEST_METHOD'] = 'GET';
+    $_SERVER['SCRIPT_NAME'] = $path . '/index.php';
+    $_SERVER['SCRIPT_FILENAME'] = $path . '/index.php';
+    $_SERVER['PHP_SELF'] = $path . '/index.php';
+    $_SERVER['HTTP_USER_AGENT'] = 'Drupal command line';
+
+    // Install Drupal test site.
+    $this->prepareEnvironment();
+    $this->installDrupal();
+
+    // Setup Mink.
+    $session = $this->initMink();
+
+    // In order to debug web tests you need to either set a cookie, have the
+    // Xdebug session in the URL or set an environment variable in case of CLI
+    // requests. If the developer listens to connection when running tests, by
+    // default the cookie is not forwarded to the client side, so you cannot
+    // debug the code running on the test site. In order to make debuggers work
+    // this bit of information is forwarded. Make sure that the debugger listens
+    // to at least three external connections.
+    $request = \Drupal::request();
+    $cookie_params = $request->cookies;
+    if ($cookie_params->has('XDEBUG_SESSION')) {
+      $session->setCookie('XDEBUG_SESSION', $cookie_params->get('XDEBUG_SESSION'));
+    }
+    // For CLI requests, the information is stored in $_SERVER.
+    $server = $request->server;
+    if ($server->has('XDEBUG_CONFIG')) {
+      // $_SERVER['XDEBUG_CONFIG'] has the form "key1=value1 key2=value2 ...".
+      $pairs = explode(' ', $server->get('XDEBUG_CONFIG'));
+      foreach ($pairs as $pair) {
+        list($key, $value) = explode('=', $pair);
+        // Account for key-value pairs being separated by multiple spaces.
+        if (trim($key) == 'idekey') {
+          $session->setCookie('XDEBUG_SESSION', trim($value));
+        }
+      }
+    }
+  }
+
+  /**
+   * Ensures test files are deletable within file_unmanaged_delete_recursive().
+   *
+   * Some tests chmod generated files to be read only. During
+   * BrowserTestBase::cleanupEnvironment() and other cleanup operations,
+   * these files need to get deleted too.
+   *
+   * @param string $path
+   *   The file path.
+   */
+  public static function filePreDeleteCallback($path) {
+    $success = @chmod($path, 0700);
+    if (!$success) {
+      trigger_error("Can not make $path writable whilst cleaning up test directory. The webserver and phpunit are probably not being run by the same user.");
+    }
+  }
+
+  /**
+   * Clean up the Simpletest environment.
+   */
+  protected function cleanupEnvironment() {
+    // Remove all prefixed tables.
+    $original_connection_info = Database::getConnectionInfo('simpletest_original_default');
+    $original_prefix = $original_connection_info['default']['prefix']['default'];
+    $test_connection_info = Database::getConnectionInfo('default');
+    $test_prefix = $test_connection_info['default']['prefix']['default'];
+    if ($original_prefix != $test_prefix) {
+      $tables = Database::getConnection()->schema()->findTables('%');
+      foreach ($tables as $table) {
+        if (Database::getConnection()->schema()->dropTable($table)) {
+          unset($tables[$table]);
+        }
+      }
+    }
+
+    // Delete test site directory.
+    file_unmanaged_delete_recursive($this->siteDirectory, array($this, 'filePreDeleteCallback'));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function tearDown() {
+    parent::tearDown();
+
+    // Destroy the testing kernel.
+    if (isset($this->kernel)) {
+      $this->cleanupEnvironment();
+      $this->kernel->shutdown();
+    }
+
+    // Ensure that internal logged in variable is reset.
+    $this->loggedInUser = FALSE;
+
+    if ($this->mink) {
+      $this->mink->stopSessions();
+    }
+  }
+
+  /**
+   * Returns Mink session.
+   *
+   * @param string $name
+   *   (optional) Name of the session. Defaults to the active session.
+   *
+   * @return \Behat\Mink\Session
+   *   The active Mink session object.
+   */
+  public function getSession($name = NULL) {
+    return $this->mink->getSession($name);
+  }
+
+  /**
+   * Returns WebAssert object.
+   *
+   * @param string $name
+   *   (optional) Name of the session. Defaults to the active session.
+   *
+   * @return \Drupal\simpletest\WebAssert
+   *   A new web-assert option for asserting the presence of elements with.
+   */
+  public function assertSession($name = NULL) {
+    return new WebAssert($this->getSession($name));
+  }
+
+  /**
+   * Prepare for a request to testing site.
+   *
+   * The testing site is protected via a SIMPLETEST_USER_AGENT cookie that is
+   * checked by drupal_valid_test_ua().
+   *
+   * @see drupal_valid_test_ua()
+   */
+  protected function prepareRequest() {
+    $session = $this->getSession();
+    $session->setCookie('SIMPLETEST_USER_AGENT', drupal_generate_test_ua($this->databasePrefix));
+  }
+
+  /**
+   * Retrieves a Drupal path or an absolute path.
+   *
+   * @param string $path
+   *   Drupal path or URL to load into Mink controlled browser.
+   * @param array $options
+   *   (optional) Options to be forwarded to the url generator.
+   *
+   * @return string
+   *   The retrieved HTML string, also available as $this->getRawContent()
+   */
+  protected function drupalGet($path, array $options = array()) {
+    $options['absolute'] = TRUE;
+
+    // The URL generator service is not necessarily available yet; e.g., in
+    // interactive installer tests.
+    if ($this->container->has('url_generator')) {
+      if (UrlHelper::isExternal($path)) {
+        $url = Url::fromUri($path, $options)->toString();
+      }
+      else {
+        // This is needed for language prefixing.
+        $options['path_processing'] = TRUE;
+        $url = Url::fromUri('base:/' . $path, $options)->toString();
+      }
+    }
+    else {
+      $url = $this->getAbsoluteUrl($path);
+    }
+    $session = $this->getSession();
+
+    $this->prepareRequest();
+    $session->visit($url);
+    $out = $session->getPage()->getContent();
+
+    // Ensure that any changes to variables in the other thread are picked up.
+    $this->refreshVariables();
+
+    return $out;
+  }
+
+  /**
+   * Takes a path and returns an absolute path.
+   *
+   * @param string $path
+   *   A path from the Mink controlled browser content.
+   *
+   * @return string
+   *   The $path with $base_url prepended, if necessary.
+   */
+  protected function getAbsoluteUrl($path) {
+    global $base_url, $base_path;
+
+    $parts = parse_url($path);
+    if (empty($parts['host'])) {
+      // Ensure that we have a string (and no xpath object).
+      $path = (string) $path;
+      // Strip $base_path, if existent.
+      $length = strlen($base_path);
+      if (substr($path, 0, $length) === $base_path) {
+        $path = substr($path, $length);
+      }
+      // Ensure that we have an absolute path.
+      if (empty($path) || $path[0] !== '/') {
+        $path = '/' . $path;
+      }
+      // Finally, prepend the $base_url.
+      $path = $base_url . $path;
+    }
+    return $path;
+  }
+
+  /**
+   * Creates a user with a given set of permissions.
+   *
+   * @param array $permissions
+   *   (optional) Array of permission names to assign to user. Note that the
+   *   user always has the default permissions derived from the
+   *   "authenticated users" role.
+   * @param string $name
+   *   (optional) The user name.
+   *
+   * @return \Drupal\user\Entity\User|false
+   *   A fully loaded user object with passRaw property, or FALSE if account
+   *   creation fails.
+   */
+  protected function drupalCreateUser(array $permissions = array(), $name = NULL) {
+    // Create a role with the given permission set, if any.
+    $rid = FALSE;
+    if ($permissions) {
+      $rid = $this->drupalCreateRole($permissions);
+      if (!$rid) {
+        return FALSE;
+      }
+    }
+
+    // Create a user assigned to that role.
+    $edit = array();
+    $edit['name'] = !empty($name) ? $name : $this->randomMachineName();
+    $edit['mail'] = $edit['name'] . '@example.com';
+    $edit['pass'] = user_password();
+    $edit['status'] = 1;
+    if ($rid) {
+      $edit['roles'] = array($rid);
+    }
+
+    $account = User::create($edit);
+    $account->save();
+
+    $this->assertNotNull($account->id(), SafeMarkup::format('User created with name %name and pass %pass', array('%name' => $edit['name'], '%pass' => $edit['pass'])));
+    if (!$account->id()) {
+      return FALSE;
+    }
+
+    // Add the raw password so that we can log in as this user.
+    $account->passRaw = $edit['pass'];
+    return $account;
+  }
+
+  /**
+   * Creates a role with specified permissions.
+   *
+   * @param array $permissions
+   *   Array of permission names to assign to role.
+   * @param string $rid
+   *   (optional) The role ID (machine name). Defaults to a random name.
+   * @param string $name
+   *   (optional) The label for the role. Defaults to a random string.
+   * @param int $weight
+   *   (optional) The weight for the role. Defaults NULL so that entity_create()
+   *   sets the weight to maximum + 1.
+   *
+   * @return string
+   *   Role ID of newly created role, or FALSE if role creation failed.
+   */
+  protected function drupalCreateRole(array $permissions, $rid = NULL, $name = NULL, $weight = NULL) {
+    // Generate a random, lowercase machine name if none was passed.
+    if (!isset($rid)) {
+      $rid = strtolower($this->randomMachineName(8));
+    }
+    // Generate a random label.
+    if (!isset($name)) {
+      // In the role UI role names are trimmed and random string can start or
+      // end with a space.
+      $name = trim($this->randomString(8));
+    }
+
+    // Check the all the permissions strings are valid.
+    if (!$this->checkPermissions($permissions)) {
+      return FALSE;
+    }
+
+    // Create new role.
+    /* @var \Drupal\user\RoleInterface $role */
+    $role = Role::create(array(
+      'id' => $rid,
+      'label' => $name,
+    ));
+    if (!is_null($weight)) {
+      $role->set('weight', $weight);
+    }
+    $result = $role->save();
+
+    $this->assertSame($result, SAVED_NEW, SafeMarkup::format('Created role ID @rid with name @name.', array(
+      '@name' => var_export($role->label(), TRUE),
+      '@rid' => var_export($role->id(), TRUE),
+    )));
+
+    if ($result === SAVED_NEW) {
+      // Grant the specified permissions to the role, if any.
+      if (!empty($permissions)) {
+        user_role_grant_permissions($role->id(), $permissions);
+        $assigned_permissions = entity_load('user_role', $role->id())->getPermissions();
+        $missing_permissions = array_diff($permissions, $assigned_permissions);
+        if ($missing_permissions) {
+          $this->fail(SafeMarkup::format('Failed to create permissions: @perms', array('@perms' => implode(', ', $missing_permissions))));
+        }
+      }
+      return $role->id();
+    }
+
+    return FALSE;
+  }
+
+  /**
+   * Checks whether a given list of permission names is valid.
+   *
+   * @param array $permissions
+   *   The permission names to check.
+   *
+   * @return bool
+   *   TRUE if the permissions are valid, FALSE otherwise.
+   */
+  protected function checkPermissions(array $permissions) {
+    $available = array_keys(\Drupal::service('user.permissions')->getPermissions());
+    $valid = TRUE;
+    foreach ($permissions as $permission) {
+      if (!in_array($permission, $available)) {
+        $this->fail(SafeMarkup::format('Invalid permission %permission.', array('%permission' => $permission)));
+        $valid = FALSE;
+      }
+    }
+    return $valid;
+  }
+
+  /**
+   * Logs in a user using the Mink controlled browser.
+   *
+   * If a user is already logged in, then the current user is logged out before
+   * logging in the specified user.
+   *
+   * Please note that neither the current user nor the passed-in user object is
+   * populated with data of the logged in user. If you need full access to the
+   * user object after logging in, it must be updated manually. If you also need
+   * access to the plain-text password of the user (set by drupalCreateUser()),
+   * e.g. to log in the same user again, then it must be re-assigned manually.
+   * For example:
+   * @code
+   *   // Create a user.
+   *   $account = $this->drupalCreateUser(array());
+   *   $this->drupalLogin($account);
+   *   // Load real user object.
+   *   $pass_raw = $account->passRaw;
+   *   $account = User::load($account->id());
+   *   $account->passRaw = $pass_raw;
+   * @endcode
+   *
+   * @param \Drupal\Core\Session\AccountInterface $account
+   *   User object representing the user to log in.
+   *
+   * @see drupalCreateUser()
+   */
+  protected function drupalLogin(AccountInterface $account) {
+    if ($this->loggedInUser) {
+      $this->drupalLogout();
+    }
+
+    $this->drupalGet('user');
+    $this->assertSession()->statusCodeEquals(200);
+    $this->submitForm(array(
+      'name' => $account->getUsername(),
+      'pass' => $account->passRaw,
+    ), t('Log in'));
+
+    // @see BrowserTestBase::drupalUserIsLoggedIn()
+    $account->sessionId = $this->getSession()->getCookie($this->getSessionName());
+    $this->assertTrue($this->drupalUserIsLoggedIn($account), SafeMarkup::format('User %name successfully logged in.', array('name' => $account->getUsername())));
+
+    $this->loggedInUser = $account;
+    $this->container->get('current_user')->setAccount($account);
+  }
+
+  /**
+   * Logs a user out of the Mink controlled browser and confirms.
+   *
+   * Confirms logout by checking the login page.
+   */
+  protected function drupalLogout() {
+    // Make a request to the logout page, and redirect to the user page, the
+    // idea being if you were properly logged out you should be seeing a login
+    // screen.
+    $assert_session = $this->assertSession();
+    $this->drupalGet('user/logout', array('query' => array('destination' => 'user')));
+    $assert_session->statusCodeEquals(200);
+    $assert_session->fieldExists('name');
+    $assert_session->fieldExists('pass');
+
+    // @see BrowserTestBase::drupalUserIsLoggedIn()
+    unset($this->loggedInUser->sessionId);
+    $this->loggedInUser = FALSE;
+    $this->container->get('current_user')->setAccount(new AnonymousUserSession());
+  }
+
+  /**
+   * Fills and submits a form.
+   *
+   * @param array $edit
+   *   Field data in an associative array. Changes the current input fields
+   *   (where possible) to the values indicated.
+   *
+   *   A checkbox can be set to TRUE to be checked and should be set to FALSE to
+   *   be unchecked.
+   * @param string $submit
+   *   Value of the submit button whose click is to be emulated. For example,
+   *   t('Save'). The processing of the request depends on this value. For
+   *   example, a form may have one button with the value t('Save') and another
+   *   button with the value t('Delete'), and execute different code depending
+   *   on which one is clicked.
+   * @param string $form_html_id
+   *   (optional) HTML ID of the form to be submitted. On some pages
+   *   there are many identical forms, so just using the value of the submit
+   *   button is not enough. For example: 'trigger-node-presave-assign-form'.
+   *   Note that this is not the Drupal $form_id, but rather the HTML ID of the
+   *   form, which is typically the same thing but with hyphens replacing the
+   *   underscores.
+   */
+  protected function submitForm(array $edit, $submit, $form_html_id = NULL) {
+    $assert_session = $this->assertSession();
+
+    // Get the form.
+    if (isset($form_html_id)) {
+      $form = $assert_session->elementExists('xpath', "//form[@id='$form_html_id']");
+      $submit_button = $assert_session->buttonExists($submit, $form);
+    }
+    else {
+      $submit_button = $assert_session->buttonExists($submit);
+      $form = $assert_session->elementExists('xpath', './ancestor::form', $submit_button);
+    }
+
+    // Edit the form values.
+    foreach ($edit as $name => $value) {
+      $field = $assert_session->fieldExists($name, $form);
+      $field->setValue($value);
+    }
+
+    // Submit form.
+    $this->prepareRequest();
+    $submit_button->press();
+
+    // Ensure that any changes to variables in the other thread are picked up.
+    $this->refreshVariables();
+  }
+
+  /**
+   * Helper function to get the options of select field.
+   *
+   * @param \Behat\Mink\Element\NodeElement|string $select
+   *   Name, ID, or Label of select field to assert.
+   * @param \Behat\Mink\Element\Element $container
+   *   (optional) Container element to check against. Defaults to current page.
+   *
+   * @return array
+   *   Associative array of option keys and values.
+   */
+  protected function getOptions($select, Element $container = NULL) {
+    if (is_string($select)) {
+      $select = $this->assertSession()->selectExists($select, $container);
+    }
+    $options = [];
+    /* @var \Behat\Mink\Element\NodeElement $option */
+    foreach ($select->findAll('xpath', '//option') as $option) {
+      $label = $option->getText();
+      $value = $option->getAttribute('value') ?: $label;
+      $options[$value] = $label;
+    }
+    return $options;
+  }
+
+  /**
+   * Installs Drupal into the Simpletest site.
+   */
+  public function installDrupal() {
+    // Define information about the user 1 account.
+    $this->rootUser = new UserSession(array(
+      'uid' => 1,
+      'name' => 'admin',
+      'mail' => 'admin@example.com',
+      'passRaw' => $this->randomMachineName(),
+    ));
+
+    // The child site derives its session name from the database prefix when
+    // running web tests.
+    $this->generateSessionName($this->databasePrefix);
+
+    // Get parameters for install_drupal() before removing global variables.
+    $parameters = $this->installParameters();
+
+    // Prepare installer settings that are not install_drupal() parameters.
+    // Copy and prepare an actual settings.php, so as to resemble a regular
+    // installation.
+    // Not using File API; a potential error must trigger a PHP warning.
+    $directory = DRUPAL_ROOT . '/' . $this->siteDirectory;
+    copy(DRUPAL_ROOT . '/sites/default/default.settings.php', $directory . '/settings.php');
+
+    // All file system paths are created by System module during installation.
+    // @see system_requirements()
+    // @see TestBase::prepareEnvironment()
+    $settings['settings']['file_public_path'] = (object) array(
+      'value' => $this->publicFilesDirectory,
+      'required' => TRUE,
+    );
+    $this->writeSettings($settings);
+    // Allow for test-specific overrides.
+    $settings_testing_file = DRUPAL_ROOT . '/' . $this->originalSiteDirectory . '/settings.testing.php';
+    if (file_exists($settings_testing_file)) {
+      // Copy the testing-specific settings.php overrides in place.
+      copy($settings_testing_file, $directory . '/settings.testing.php');
+      // Add the name of the testing class to settings.php and include the
+      // testing specific overrides.
+      file_put_contents($directory . '/settings.php', "\n\$test_class = '" . get_class($this) . "';\n" . 'include DRUPAL_ROOT . \'/\' . $site_path . \'/settings.testing.php\';' . "\n", FILE_APPEND);
+    }
+    $settings_services_file = DRUPAL_ROOT . '/' . $this->originalSiteDirectory . '/testing.services.yml';
+    if (file_exists($settings_services_file)) {
+      // Copy the testing-specific service overrides in place.
+      copy($settings_services_file, $directory . '/services.yml');
+    }
+
+    // Since Drupal is bootstrapped already, install_begin_request() will not
+    // bootstrap into DRUPAL_BOOTSTRAP_CONFIGURATION (again). Hence, we have to
+    // reload the newly written custom settings.php manually.
+    Settings::initialize(DRUPAL_ROOT, $directory, $this->classLoader);
+
+    // Execute the non-interactive installer.
+    $sapi_adapter = new NullSAPIAdapter();
+    require_once DRUPAL_ROOT . '/core/includes/install.core.inc';
+    install_drupal($this->classLoader, $parameters, $sapi_adapter);
+
+    // Import new settings.php written by the installer.
+    Settings::initialize(DRUPAL_ROOT, $directory, $this->classLoader);
+    foreach ($GLOBALS['config_directories'] as $type => $path) {
+      $this->configDirectories[$type] = $path;
+    }
+
+    // After writing settings.php, the installer removes write permissions from
+    // the site directory. To allow drupal_generate_test_ua() to write a file
+    // containing the private key for drupal_valid_test_ua(), the site directory
+    // has to be writable.
+    // TestBase::restoreEnvironment() will delete the entire site directory. Not
+    // using File API; a potential error must trigger a PHP warning.
+    chmod($directory, 0777);
+
+    $request = \Drupal::request();
+    $this->kernel = DrupalKernel::createFromRequest($request, $this->classLoader, 'prod', TRUE, $sapi_adapter);
+    $this->kernel->prepareLegacyRequest($request);
+    // Force the container to be built from scratch instead of loaded from the
+    // disk. This forces us to not accidentally load the parent site.
+    $container = $this->kernel->rebuildContainer();
+
+    $config = $container->get('config.factory');
+
+    // Manually create and configure private and temporary files directories.
+    // While these could be preset/enforced in settings.php like the public
+    // files directory above, some tests expect them to be configurable in the
+    // UI. If declared in settings.php, they would no longer be configurable.
+    file_prepare_directory($this->privateFilesDirectory, FILE_CREATE_DIRECTORY);
+    file_prepare_directory($this->tempFilesDirectory, FILE_CREATE_DIRECTORY);
+    $config->getEditable('system.file')
+      ->set('path.private', $this->privateFilesDirectory)
+      ->set('path.temporary', $this->tempFilesDirectory)
+      ->save();
+
+    // Manually configure the test mail collector implementation to prevent
+    // tests from sending out emails and collect them in state instead.
+    // While this should be enforced via settings.php prior to installation,
+    // some tests expect to be able to test mail system implementations.
+    $config->getEditable('system.mail')
+      ->set('interface.default', 'test_mail_collector')
+      ->save();
+
+    // By default, verbosely display all errors and disable all production
+    // environment optimizations for all tests to avoid needless overhead and
+    // ensure a sane default experience for test authors.
+    // @see https://www.drupal.org/node/2259167
+    $config->getEditable('system.logging')
+      ->set('error_level', 'verbose')
+      ->save();
+    $config->getEditable('system.performance')
+      ->set('css.preprocess', FALSE)
+      ->set('js.preprocess', FALSE)
+      ->save();
+
+    // Collect modules to install.
+    $class = get_class($this);
+    $modules = array();
+    while ($class) {
+      if (property_exists($class, 'modules')) {
+        $modules = array_merge($modules, $class::$modules);
+      }
+      $class = get_parent_class($class);
+    }
+    if ($modules) {
+      $modules = array_unique($modules);
+      $success = $container->get('module_installer')->install($modules, TRUE);
+      $this->assertTrue($success, SafeMarkup::format('Enabled modules: %modules', array('%modules' => implode(', ', $modules))));
+      $this->rebuildContainer();
+    }
+
+    // Reset/rebuild all data structures after enabling the modules, primarily
+    // to synchronize all data structures and caches between the test runner and
+    // the child site.
+    // Affects e.g. StreamWrapperManagerInterface::getWrappers().
+    // @see \Drupal\Core\DrupalKernel::bootCode()
+    // @todo Test-specific setUp() methods may set up further fixtures; find a
+    //   way to execute this after setUp() is done, or to eliminate it entirely.
+    $this->resetAll();
+    $this->kernel->prepareLegacyRequest($request);
+  }
+
+  /**
+   * Returns the parameters that will be used when Simpletest installs Drupal.
+   *
+   * @see install_drupal()
+   * @see install_state_defaults()
+   */
+  protected function installParameters() {
+    $connection_info = Database::getConnectionInfo();
+    $driver = $connection_info['default']['driver'];
+    $connection_info['default']['prefix'] = $connection_info['default']['prefix']['default'];
+    unset($connection_info['default']['driver']);
+    unset($connection_info['default']['namespace']);
+    unset($connection_info['default']['pdo']);
+    unset($connection_info['default']['init_commands']);
+    $parameters = array(
+      'interactive' => FALSE,
+      'parameters' => array(
+        'profile' => $this->profile,
+        'langcode' => 'en',
+      ),
+      'forms' => array(
+        'install_settings_form' => array(
+          'driver' => $driver,
+          $driver => $connection_info['default'],
+        ),
+        'install_configure_form' => array(
+          'site_name' => 'Drupal',
+          'site_mail' => 'simpletest@example.com',
+          'account' => array(
+            'name' => $this->rootUser->name,
+            'mail' => $this->rootUser->getEmail(),
+            'pass' => array(
+              'pass1' => $this->rootUser->passRaw,
+              'pass2' => $this->rootUser->passRaw,
+            ),
+          ),
+          // form_type_checkboxes_value() requires NULL instead of FALSE values
+          // for programmatic form submissions to disable a checkbox.
+          'update_status_module' => array(
+            1 => NULL,
+            2 => NULL,
+          ),
+        ),
+      ),
+    );
+    return $parameters;
+  }
+
+  /**
+   * Generates a database prefix for running tests.
+   *
+   * The database prefix is used by prepareEnvironment() to setup a public files
+   * directory for the test to be run, which also contains the PHP error log,
+   * which is written to in case of a fatal error. Since that directory is based
+   * on the database prefix, all tests (even unit tests) need to have one, in
+   * order to access and read the error log.
+   *
+   * The generated database table prefix is used for the Drupal installation
+   * being performed for the test. It is also used by the cookie value of
+   * SIMPLETEST_USER_AGENT by the Mink controlled browser. During early Drupal
+   * bootstrap, the cookie is parsed, and if it matches, all database queries
+   * use the database table prefix that has been generated here.
+   *
+   * @see drupal_valid_test_ua()
+   * @see BrowserTestBase::prepareEnvironment()
+   */
+  private function prepareDatabasePrefix() {
+    // Ensure that the generated test site directory does not exist already,
+    // which may happen with a large amount of concurrent threads and
+    // long-running tests.
+    do {
+      $suffix = mt_rand(100000, 999999);
+      $this->siteDirectory = 'sites/simpletest/' . $suffix;
+      $this->databasePrefix = 'simpletest' . $suffix;
+    } while (is_dir(DRUPAL_ROOT . '/' . $this->siteDirectory));
+  }
+
+  /**
+   * Changes the database connection to the prefixed one.
+   *
+   * @see BrowserTestBase::prepareEnvironment()
+   */
+  private function changeDatabasePrefix() {
+    if (empty($this->databasePrefix)) {
+      $this->prepareDatabasePrefix();
+    }
+
+    // If the test is run with argument dburl then use it.
+    $db_url = getenv('SIMPLETEST_DB');
+    if (!empty($db_url)) {
+      $database = Database::convertDbUrlToConnectionInfo($db_url, DRUPAL_ROOT);
+      Database::addConnectionInfo('default', 'default', $database);
+    }
+
+    // Clone the current connection and replace the current prefix.
+    $connection_info = Database::getConnectionInfo('default');
+    if (is_null($connection_info)) {
+      throw new \InvalidArgumentException('There is no database connection so no tests can be run. You must provide a SIMPLETEST_DB environment variable to run PHPUnit based functional tests outside of run-tests.sh.');
+    }
+    else {
+      Database::renameConnection('default', 'simpletest_original_default');
+      foreach ($connection_info as $target => $value) {
+        // Replace the full table prefix definition to ensure that no table
+        // prefixes of the test runner leak into the test.
+        $connection_info[$target]['prefix'] = array(
+          'default' => $value['prefix']['default'] . $this->databasePrefix,
+        );
+      }
+      Database::addConnectionInfo('default', 'default', $connection_info['default']);
+    }
+  }
+
+  /**
+   * Prepares the current environment for running the test.
+   *
+   * Also sets up new resources for the testing environment, such as the public
+   * filesystem and configuration directories.
+   *
+   * This method is private as it must only be called once by
+   * BrowserTestBase::setUp() (multiple invocations for the same test would have
+   * unpredictable consequences) and it must not be callable or overridable by
+   * test classes.
+   */
+  protected function prepareEnvironment() {
+    // Bootstrap Drupal so we can use Drupal's built in functions.
+    $this->classLoader = require __DIR__ . '/../../../../autoload.php';
+    $request = Request::createFromGlobals();
+    $kernel = TestRunnerKernel::createFromRequest($request, $this->classLoader);
+    // TestRunnerKernel expects the working directory to be DRUPAL_ROOT.
+    chdir(DRUPAL_ROOT);
+    $kernel->prepareLegacyRequest($request);
+    $this->prepareDatabasePrefix();
+
+    $this->originalSiteDirectory = $kernel->findSitePath($request);
+
+    // Create test directory ahead of installation so fatal errors and debug
+    // information can be logged during installation process.
+    file_prepare_directory($this->siteDirectory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
+
+    // Prepare filesystem directory paths.
+    $this->publicFilesDirectory = $this->siteDirectory . '/files';
+    $this->privateFilesDirectory = $this->siteDirectory . '/private';
+    $this->tempFilesDirectory = $this->siteDirectory . '/temp';
+    $this->translationFilesDirectory = $this->siteDirectory . '/translations';
+
+    // Ensure the configImporter is refreshed for each test.
+    $this->configImporter = NULL;
+
+    // Unregister all custom stream wrappers of the parent site.
+    $wrappers = \Drupal::service('stream_wrapper_manager')->getWrappers(StreamWrapperInterface::ALL);
+    foreach ($wrappers as $scheme => $info) {
+      stream_wrapper_unregister($scheme);
+    }
+
+    // Reset statics.
+    drupal_static_reset();
+
+    // Ensure there is no service container.
+    $this->container = NULL;
+    \Drupal::unsetContainer();
+
+    // Unset globals.
+    unset($GLOBALS['config_directories']);
+    unset($GLOBALS['config']);
+    unset($GLOBALS['conf']);
+
+    // Log fatal errors.
+    ini_set('log_errors', 1);
+    ini_set('error_log', DRUPAL_ROOT . '/' . $this->siteDirectory . '/error.log');
+
+    // Change the database prefix.
+    $this->changeDatabasePrefix();
+
+    // After preparing the environment and changing the database prefix, we are
+    // in a valid test environment.
+    drupal_valid_test_ua($this->databasePrefix);
+
+    // Reset settings.
+    new Settings(array(
+      // For performance, simply use the database prefix as hash salt.
+      'hash_salt' => $this->databasePrefix,
+    ));
+
+    drupal_set_time_limit($this->timeLimit);
+  }
+
+  /**
+   * Returns the database connection to the site running Simpletest.
+   *
+   * @return \Drupal\Core\Database\Connection
+   *   The database connection to use for inserting assertions.
+   */
+  public static function getDatabaseConnection() {
+    // Check whether there is a test runner connection.
+    // @see run-tests.sh
+    try {
+      $connection = Database::getConnection('default', 'test-runner');
+    }
+    catch (ConnectionNotDefinedException $e) {
+      // Check whether there is a backup of the original default connection.
+      // @see BrowserTestBase::prepareEnvironment()
+      try {
+        $connection = Database::getConnection('default', 'simpletest_original_default');
+      }
+      catch (ConnectionNotDefinedException $e) {
+        // If BrowserTestBase::prepareEnvironment() or
+        // BrowserTestBase::restoreEnvironment() failed, the test-specific
+        // database connection does not exist yet/anymore, so fall back to the
+        // default of the (UI) test runner.
+        $connection = Database::getConnection('default', 'default');
+      }
+    }
+    return $connection;
+  }
+
+  /**
+   * Rewrites the settings.php file of the test site.
+   *
+   * @param array $settings
+   *   An array of settings to write out, in the format expected by
+   *   drupal_rewrite_settings().
+   *
+   * @see drupal_rewrite_settings()
+   */
+  protected function writeSettings(array $settings) {
+    include_once DRUPAL_ROOT . '/core/includes/install.inc';
+    $filename = $this->siteDirectory . '/settings.php';
+
+    // system_requirements() removes write permissions from settings.php
+    // whenever it is invoked.
+    // Not using File API; a potential error must trigger a PHP warning.
+    chmod($filename, 0666);
+    drupal_rewrite_settings($settings, $filename);
+  }
+
+  /**
+   * Rebuilds \Drupal::getContainer().
+   *
+   * Use this to build a new kernel and service container. For example, when the
+   * list of enabled modules is changed via the Mink controlled browser, in
+   * which case the test process still contains an old kernel and service
+   * container with an old module list.
+   *
+   * @see BrowserTestBase::prepareEnvironment()
+   * @see BrowserTestBase::restoreEnvironment()
+   *
+   * @todo Fix https://www.drupal.org/node/2021959 so that module enable/disable
+   *   changes are immediately reflected in \Drupal::getContainer(). Until then,
+   *   tests can invoke this workaround when requiring services from newly
+   *   enabled modules to be immediately available in the same request.
+   */
+  protected function rebuildContainer() {
+    // Rebuild the kernel and bring it back to a fully bootstrapped state.
+    $this->container = $this->kernel->rebuildContainer();
+
+    // Make sure the url generator has a request object, otherwise calls to
+    // $this->drupalGet() will fail.
+    $this->prepareRequestForGenerator();
+  }
+
+  /**
+   * Creates a mock request and sets it on the generator.
+   *
+   * This is used to manipulate how the generator generates paths during tests.
+   * It also ensures that calls to $this->drupalGet() will work when running
+   * from run-tests.sh because the url generator no longer looks at the global
+   * variables that are set there but relies on getting this information from a
+   * request object.
+   *
+   * @param bool $clean_urls
+   *   Whether to mock the request using clean urls.
+   * @param array $override_server_vars
+   *   An array of server variables to override.
+   *
+   * @return Request
+   *   The mocked request object.
+   */
+  protected function prepareRequestForGenerator($clean_urls = TRUE, $override_server_vars = array()) {
+    $request = Request::createFromGlobals();
+    $server = $request->server->all();
+    if (basename($server['SCRIPT_FILENAME']) != basename($server['SCRIPT_NAME'])) {
+      // We need this for when the test is executed by run-tests.sh.
+      // @todo Remove this once run-tests.sh has been converted to use a Request
+      //   object.
+      $cwd = getcwd();
+      $server['SCRIPT_FILENAME'] = $cwd . '/' . basename($server['SCRIPT_NAME']);
+      $base_path = rtrim($server['REQUEST_URI'], '/');
+    }
+    else {
+      $base_path = $request->getBasePath();
+    }
+    if ($clean_urls) {
+      $request_path = $base_path ? $base_path . '/user' : 'user';
+    }
+    else {
+      $request_path = $base_path ? $base_path . '/index.php/user' : '/index.php/user';
+    }
+    $server = array_merge($server, $override_server_vars);
+
+    $request = Request::create($request_path, 'GET', array(), array(), array(), $server);
+    // Ensure the request time is REQUEST_TIME to ensure that API calls
+    // in the test use the right timestamp.
+    $request->server->set('REQUEST_TIME', REQUEST_TIME);
+    $this->container->get('request_stack')->push($request);
+
+    // The request context is normally set by the router_listener from within
+    // its KernelEvents::REQUEST listener. In the Simpletest parent site this
+    // event is not fired, therefore it is necessary to updated the request
+    // context manually here.
+    $this->container->get('router.request_context')->fromRequest($request);
+
+    return $request;
+  }
+
+  /**
+   * Resets all data structures after having enabled new modules.
+   *
+   * This method is called by \Drupal\simpletest\BrowserTestBase::setUp() after
+   * enabling the requested modules. It must be called again when additional
+   * modules are enabled later.
+   */
+  protected function resetAll() {
+    // Clear all database and static caches and rebuild data structures.
+    drupal_flush_all_caches();
+    $this->container = \Drupal::getContainer();
+
+    // Reset static variables and reload permissions.
+    $this->refreshVariables();
+  }
+
+  /**
+   * Refreshes in-memory configuration and state information.
+   *
+   * Useful after a page request is made that changes configuration or state in
+   * a different thread.
+   *
+   * In other words calling a settings page with $this->submitForm() with a
+   * changed value would update configuration to reflect that change, but in the
+   * thread that made the call (thread running the test) the changed values
+   * would not be picked up.
+   *
+   * This method clears the cache and loads a fresh copy.
+   */
+  protected function refreshVariables() {
+    // Clear the tag cache.
+    // @todo Replace drupal_static() usage within classes and provide a
+    //   proper interface for invoking reset() on a cache backend:
+    //   https://www.drupal.org/node/2311945.
+    drupal_static_reset('Drupal\Core\Cache\CacheBackendInterface::tagCache');
+    drupal_static_reset('Drupal\Core\Cache\DatabaseBackend::deletedTags');
+    drupal_static_reset('Drupal\Core\Cache\DatabaseBackend::invalidatedTags');
+    foreach (Cache::getBins() as $backend) {
+      if (is_callable(array($backend, 'reset'))) {
+        $backend->reset();
+      }
+    }
+
+    $this->container->get('config.factory')->reset();
+    $this->container->get('state')->resetCache();
+  }
+
+  /**
+   * Returns whether a given user account is logged in.
+   *
+   * @param \Drupal\user\UserInterface $account
+   *   The user account object to check.
+   *
+   * @return bool
+   *   Return TRUE if the user is logged in, FALSE otherwise.
+   */
+  protected function drupalUserIsLoggedIn(UserInterface $account) {
+    $logged_in = FALSE;
+
+    if (isset($account->sessionId)) {
+      $session_handler = $this->container->get('session_handler.storage');
+      $logged_in = (bool) $session_handler->read($account->sessionId);
+    }
+
+    return $logged_in;
+  }
+
+  /**
+   * Prevents serializing any properties.
+   *
+   * Browser tests are run in a separate process. To do this PHPUnit creates a
+   * script to run the test. If it fails, the test result object will contain a
+   * stack trace which includes the test object. It will attempt to serialize
+   * it. Returning an empty array prevents it from serializing anything it
+   * should not.
+   *
+   * @return array
+   *   An empty array.
+   *
+   * @see vendor/phpunit/phpunit/src/Util/PHP/Template/TestCaseMethod.tpl.dist
+   */
+  public function __sleep() {
+    return [];
+  }
 }
diff --git a/core/modules/simpletest/src/InstallerTestBase.php b/core/modules/simpletest/src/InstallerTestBase.php
index f1f3a11..f535133 100644
--- a/core/modules/simpletest/src/InstallerTestBase.php
+++ b/core/modules/simpletest/src/InstallerTestBase.php
@@ -4,6 +4,7 @@
 
 use Drupal\Core\DrupalKernel;
 use Drupal\Core\Language\Language;
+use Drupal\Core\SAPI\NullSAPIAdapter;
 use Drupal\Core\Session\UserSession;
 use Drupal\Core\Site\Settings;
 use Symfony\Component\DependencyInjection\ContainerBuilder;
@@ -147,7 +148,8 @@ protected function setUp() {
       // WebTestBase::tearDown() will delete the entire test site directory.
       // Not using File API; a potential error must trigger a PHP warning.
       chmod($this->container->get('app.root') . '/' . $this->siteDirectory, 0777);
-      $this->kernel = DrupalKernel::createFromRequest($request, $class_loader, 'prod', FALSE);
+      $sapi_adapter = new NullSAPIAdapter();
+      $this->kernel = DrupalKernel::createFromRequest($request, $class_loader, 'prod', FALSE, $sapi_adapter);
       $this->kernel->prepareLegacyRequest($request);
       $this->container = $this->kernel->getContainer();
 
diff --git a/core/modules/simpletest/src/KernelTestBase.php b/core/modules/simpletest/src/KernelTestBase.php
index 96c5cf6..42a6646 100644
--- a/core/modules/simpletest/src/KernelTestBase.php
+++ b/core/modules/simpletest/src/KernelTestBase.php
@@ -12,9 +12,10 @@
 use Drupal\Core\Extension\ExtensionDiscovery;
 use Drupal\Core\KeyValueStore\KeyValueMemoryFactory;
 use Drupal\Core\Language\Language;
+use Drupal\Core\SAPI\NullSAPIAdapter;
 use Drupal\Core\Site\Settings;
-use Symfony\Component\DependencyInjection\Parameter;
 use Drupal\Core\StreamWrapper\StreamWrapperInterface;
+use Symfony\Component\DependencyInjection\Parameter;
 use Symfony\Component\DependencyInjection\Reference;
 use Symfony\Component\HttpFoundation\Request;
 
@@ -176,7 +177,8 @@ protected function setUp() {
 
     // Bootstrap a new kernel.
     $class_loader = require DRUPAL_ROOT . '/autoload.php';
-    $this->kernel = new DrupalKernel('testing', $class_loader, FALSE);
+    $sapi_adapter = new NullSAPIAdapter();
+    $this->kernel = new DrupalKernel('testing', $class_loader, FALSE, $sapi_adapter);
     $request = Request::create('/');
     $site_path = DrupalKernel::findSitePath($request);
     $this->kernel->setSitePath($site_path);
diff --git a/core/modules/simpletest/src/WebTestBase.php b/core/modules/simpletest/src/WebTestBase.php
index 3622642..36b0946 100644
--- a/core/modules/simpletest/src/WebTestBase.php
+++ b/core/modules/simpletest/src/WebTestBase.php
@@ -2,14 +2,13 @@
 
 namespace Drupal\simpletest;
 
-use Drupal\block\Entity\Block;
 use Drupal\Component\FileCache\FileCacheFactory;
 use Drupal\Component\Serialization\Json;
 use Drupal\Component\Utility\Html;
 use Drupal\Component\Utility\NestedArray;
+use Drupal\Component\Utility\SafeMarkup;
 use Drupal\Component\Utility\UrlHelper;
 use Drupal\Core\Cache\Cache;
-use Drupal\Component\Utility\SafeMarkup;
 use Drupal\Core\Database\Database;
 use Drupal\Core\DrupalKernel;
 use Drupal\Core\Entity\EntityInterface;
@@ -18,6 +17,7 @@
 use Drupal\Core\Extension\MissingDependencyException;
 use Drupal\Core\Render\Element;
 use Drupal\Core\Serialization\Yaml;
+use Drupal\Core\SAPI\NullSAPIAdapter;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\Session\AnonymousUserSession;
 use Drupal\Core\Session\UserSession;
@@ -27,6 +27,7 @@
 use Drupal\system\Tests\Cache\AssertPageCacheContextsAndTagsTrait;
 use Drupal\Tests\TestFileCreationTrait;
 use Drupal\Tests\XdebugRequestTrait;
+use Drupal\block\Entity\Block;
 use Symfony\Component\DependencyInjection\ContainerInterface;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\Yaml\Yaml as SymfonyYaml;
@@ -176,6 +177,11 @@
   protected $sessionId = NULL;
 
   /**
+   * Whether the files were copied to the test files directory.
+   */
+  protected $generatedTestFiles = FALSE;
+
+  /**
    * The maximum number of redirects to follow when handling responses.
    */
   protected $maximumRedirects = 5;
@@ -232,6 +238,12 @@
    * @var \Symfony\Component\Classloader\Classloader
    */
   protected $classLoader;
+  /**
+   * The SAPI adapter.
+   *
+   * @var \Drupal\Core\SAPI\SAPIAdapterInterface.
+   */
+  protected $sapiAdapter;
 
   /**
    * Constructor for \Drupal\simpletest\WebTestBase.
@@ -240,6 +252,7 @@ function __construct($test_id = NULL) {
     parent::__construct($test_id);
     $this->skipClasses[__CLASS__] = TRUE;
     $this->classLoader = require DRUPAL_ROOT . '/autoload.php';
+    $this->sapiAdapter = new NullSAPIAdapter();
   }
 
   /**
@@ -336,6 +349,99 @@ protected function findBlockInstance(Block $block) {
   }
 
   /**
+   * Gets a list of files that can be used in tests.
+   *
+   * The first time this method is called, it will call
+   * simpletest_generate_file() to generate binary and ASCII text files in the
+   * public:// directory. It will also copy all files in
+   * core/modules/simpletest/files to public://. These contain image, SQL, PHP,
+   * JavaScript, and HTML files.
+   *
+   * All filenames are prefixed with their type and have appropriate extensions:
+   * - text-*.txt
+   * - binary-*.txt
+   * - html-*.html and html-*.txt
+   * - image-*.png, image-*.jpg, and image-*.gif
+   * - javascript-*.txt and javascript-*.script
+   * - php-*.txt and php-*.php
+   * - sql-*.txt and sql-*.sql
+   *
+   * Any subsequent calls will not generate any new files, or copy the files
+   * over again. However, if a test class adds a new file to public:// that
+   * is prefixed with one of the above types, it will get returned as well, even
+   * on subsequent calls.
+   *
+   * @param $type
+   *   File type, possible values: 'binary', 'html', 'image', 'javascript',
+   *   'php', 'sql', 'text'.
+   * @param $size
+   *   (optional) File size in bytes to match. Defaults to NULL, which will not
+   *   filter the returned list by size.
+   *
+   * @return
+   *   List of files in public:// that match the filter(s).
+   */
+  protected function drupalGetTestFiles($type, $size = NULL) {
+    if (empty($this->generatedTestFiles)) {
+      // Generate binary test files.
+      $lines = array(64, 1024);
+      $count = 0;
+      foreach ($lines as $line) {
+        simpletest_generate_file('binary-' . $count++, 64, $line, 'binary');
+      }
+
+      // Generate ASCII text test files.
+      $lines = array(16, 256, 1024, 2048, 20480);
+      $count = 0;
+      foreach ($lines as $line) {
+        simpletest_generate_file('text-' . $count++, 64, $line, 'text');
+      }
+
+      // Copy other test files from simpletest.
+      $original = drupal_get_path('module', 'simpletest') . '/files';
+      $files = file_scan_directory($original, '/(html|image|javascript|php|sql)-.*/');
+      foreach ($files as $file) {
+        file_unmanaged_copy($file->uri, PublicStream::basePath());
+      }
+
+      $this->generatedTestFiles = TRUE;
+    }
+
+    $files = array();
+    // Make sure type is valid.
+    if (in_array($type, array('binary', 'html', 'image', 'javascript', 'php', 'sql', 'text'))) {
+      $files = file_scan_directory('public://', '/' . $type . '\-.*/');
+
+      // If size is set then remove any files that are not of that size.
+      if ($size !== NULL) {
+        foreach ($files as $file) {
+          $stats = stat($file->uri);
+          if ($stats['size'] != $size) {
+            unset($files[$file->uri]);
+          }
+        }
+      }
+    }
+    usort($files, array($this, 'drupalCompareFiles'));
+    return $files;
+  }
+
+  /**
+   * Compare two files based on size and file name.
+   */
+  protected function drupalCompareFiles($file1, $file2) {
+    $compare_size = filesize($file1->uri) - filesize($file2->uri);
+    if ($compare_size) {
+      // Sort by file size.
+      return $compare_size;
+    }
+    else {
+      // The files were the same size, so sort alphabetically.
+      return strnatcmp($file1->name, $file2->name);
+    }
+  }
+
+  /**
    * Log in a user with the internal browser.
    *
    * If a user is already logged in, then the current user is logged out before
@@ -485,7 +591,7 @@ protected function setUp() {
    */
   protected function doInstall() {
     require_once DRUPAL_ROOT . '/core/includes/install.core.inc';
-    install_drupal($this->classLoader, $this->installParameters());
+    install_drupal($this->classLoader, $this->installParameters(), $this->sapiAdapter);
   }
 
   /**
@@ -524,6 +630,10 @@ protected function prepareSettings() {
       'value' => $this->originalProfile,
       'required' => TRUE,
     ];
+    $settings['settings']['apcu_ensure_unique_prefix'] = (object) [
+      'value' => FALSE,
+      'required' => TRUE,
+    ];
     $this->writeSettings($settings);
     // Allow for test-specific overrides.
     $settings_testing_file = DRUPAL_ROOT . '/' . $this->originalSite . '/settings.testing.php';
@@ -770,7 +880,7 @@ protected function initUserSession() {
    *   The container.
    */
   protected function initKernel(Request $request) {
-    $this->kernel = DrupalKernel::createFromRequest($request, $this->classLoader, 'prod', TRUE);
+    $this->kernel = DrupalKernel::createFromRequest($request, $this->classLoader, 'prod', TRUE, $this->sapiAdapter);
     $this->kernel->prepareLegacyRequest($request);
     // Force the container to be built from scratch instead of loaded from the
     // disk. This forces us to not accidentally load the parent site.
@@ -1071,7 +1181,9 @@ protected function curlInitialize() {
     }
     // We set the user agent header on each request so as to use the current
     // time and a new uniqid.
-    curl_setopt($this->curlHandle, CURLOPT_USERAGENT, drupal_generate_test_ua($this->databasePrefix));
+    if (preg_match('/simpletest\d+/', $this->databasePrefix, $matches)) {
+      curl_setopt($this->curlHandle, CURLOPT_USERAGENT, drupal_generate_test_ua($matches[0]));
+    }
   }
 
   /**
diff --git a/core/rebuild.php b/core/rebuild.php
index 4e69eab..49e57ae 100644
--- a/core/rebuild.php
+++ b/core/rebuild.php
@@ -12,10 +12,11 @@
 
 use Drupal\Component\Utility\Crypt;
 use Drupal\Core\DrupalKernel;
+use Drupal\Core\SAPI\SAPIAdapter;
 use Drupal\Core\Site\Settings;
-use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
 use Symfony\Component\HttpFoundation\Request;
 use Symfony\Component\HttpFoundation\Response;
+use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
 
 // Change the directory to the Drupal root.
 chdir('..');
@@ -25,8 +26,8 @@
 
 $request = Request::createFromGlobals();
 // Manually resemble early bootstrap of DrupalKernel::boot().
-require_once __DIR__ . '/includes/bootstrap.inc';
-DrupalKernel::bootEnvironment();
+$sapi_adapter = SAPIAdapter::createFromGlobals();
+$sapi_adapter->prepareEnvironment(__DIR__ . '/../', 'prod');
 
 try {
   Settings::initialize(dirname(__DIR__), DrupalKernel::findSitePath($request), $autoloader);
diff --git a/core/tests/Drupal/KernelTests/Core/DrupalKernel/DrupalKernelTest.php b/core/tests/Drupal/KernelTests/Core/DrupalKernel/DrupalKernelTest.php
index 7778a5a..d506923 100644
--- a/core/tests/Drupal/KernelTests/Core/DrupalKernel/DrupalKernelTest.php
+++ b/core/tests/Drupal/KernelTests/Core/DrupalKernel/DrupalKernelTest.php
@@ -3,6 +3,7 @@
 namespace Drupal\KernelTests\Core\DrupalKernel;
 
 use Drupal\Core\DrupalKernel;
+use Drupal\Core\SAPI\NullSAPIAdapter;
 use Drupal\KernelTests\KernelTestBase;
 use Symfony\Component\HttpFoundation\Request;
 
@@ -44,7 +45,8 @@ protected function setUp() {
   protected function getTestKernel(Request $request, array $modules_enabled = NULL) {
     // Manually create kernel to avoid replacing settings.
     $class_loader = require $this->root . '/autoload.php';
-    $kernel = DrupalKernel::createFromRequest($request, $class_loader, 'testing');
+    $sapi_adapter = new NullSAPIAdapter();
+    $kernel = DrupalKernel::createFromRequest($request, $class_loader, 'testing', TRUE, $sapi_adapter);
     $this->setSetting('container_yamls', []);
     $this->setSetting('hash_salt', $this->databasePrefix);
     if (isset($modules_enabled)) {
@@ -146,6 +148,7 @@ public function testCompileDIC() {
   public function testRepeatedBootWithDifferentEnvironment() {
     $request = Request::createFromGlobals();
     $class_loader = require $this->root . '/autoload.php';
+    $sapi_adapter = new NullSAPIAdapter();
 
     $environments = [
       'testing1',
@@ -155,7 +158,7 @@ public function testRepeatedBootWithDifferentEnvironment() {
     ];
 
     foreach ($environments as $environment) {
-      $kernel = DrupalKernel::createFromRequest($request, $class_loader, $environment);
+      $kernel = DrupalKernel::createFromRequest($request, $class_loader, $environment, TRUE, $sapi_adapter);
       $this->setSetting('container_yamls', []);
       $this->setSetting('hash_salt', $this->databasePrefix);
       $kernel->boot();
diff --git a/core/tests/Drupal/KernelTests/Core/EventSubscriber/IgnoreReplicaSubscriberTest.php b/core/tests/Drupal/KernelTests/Core/EventSubscriber/IgnoreReplicaSubscriberTest.php
index 5131537..7b6c5fe 100644
--- a/core/tests/Drupal/KernelTests/Core/EventSubscriber/IgnoreReplicaSubscriberTest.php
+++ b/core/tests/Drupal/KernelTests/Core/EventSubscriber/IgnoreReplicaSubscriberTest.php
@@ -3,12 +3,13 @@
 namespace Drupal\KernelTests\Core\EventSubscriber;
 
 use Drupal\Core\Database\Database;
-use Drupal\Core\EventSubscriber\ReplicaDatabaseIgnoreSubscriber;
 use Drupal\Core\DrupalKernel;
+use Drupal\Core\EventSubscriber\ReplicaDatabaseIgnoreSubscriber;
+use Drupal\Core\SAPI\NullSAPIAdapter;
 use Drupal\KernelTests\KernelTestBase;
 use Symfony\Component\HttpFoundation\Request;
-use Symfony\Component\HttpKernel\HttpKernelInterface;
 use Symfony\Component\HttpKernel\Event\GetResponseEvent;
+use Symfony\Component\HttpKernel\HttpKernelInterface;
 
 /**
  * Tests that ReplicaDatabaseIgnoreSubscriber functions correctly.
@@ -29,7 +30,8 @@ function testSystemInitIgnoresSecondaries() {
 
     db_ignore_replica();
     $class_loader = require \Drupal::root() . '/autoload.php';
-    $kernel = new DrupalKernel('testing', $class_loader, FALSE);
+    $sapi_adapter = new NullSAPIAdapter();
+    $kernel = new DrupalKernel('testing', $class_loader, FALSE, $sapi_adapter);
     $event = new GetResponseEvent($kernel, Request::create('http://example.com'), HttpKernelInterface::MASTER_REQUEST);
     $subscriber = new ReplicaDatabaseIgnoreSubscriber();
     $subscriber->checkReplicaServer($event);
diff --git a/core/tests/Drupal/KernelTests/KernelTestBase.php b/core/tests/Drupal/KernelTests/KernelTestBase.php
index 8c2492a..e68cb29 100644
--- a/core/tests/Drupal/KernelTests/KernelTestBase.php
+++ b/core/tests/Drupal/KernelTests/KernelTestBase.php
@@ -13,7 +13,7 @@
 use Drupal\Core\DrupalKernel;
 use Drupal\Core\Entity\Sql\SqlEntityStorageInterface;
 use Drupal\Core\Extension\ExtensionDiscovery;
-use Drupal\Core\Language\Language;
+use Drupal\Core\SAPI\NullSAPIAdapter;
 use Drupal\Core\Site\Settings;
 use Drupal\Core\Test\TestDatabase;
 use Drupal\simpletest\AssertContentTrait;
@@ -115,6 +115,13 @@
   protected $classLoader;
 
   /**
+   * The SAPI adapter.
+   *
+   * @var \Drupal\Core\SAPI\SAPIAdapterInterface.
+   */
+  protected $sapiAdapter;
+
+  /**
    * @var string
    */
   protected $siteDirectory;
@@ -248,6 +255,7 @@ protected function bootEnvironment() {
     \Drupal::unsetContainer();
 
     $this->classLoader = require $this->root . '/autoload.php';
+    $this->sapiAdapter = new NullSAPIAdapter();
 
     require_once $this->root . '/core/includes/bootstrap.inc';
 
@@ -348,7 +356,7 @@ private function bootKernel() {
     }
 
     // Bootstrap the kernel. Do not use createFromRequest() to retain Settings.
-    $kernel = new DrupalKernel('testing', $this->classLoader, FALSE);
+    $kernel = new DrupalKernel('testing', $this->classLoader, FALSE, $this->sapiAdapter);
     $kernel->setSitePath($this->siteDirectory);
     // Boot the precompiled container. The kernel will enhance it with synthetic
     // services.
@@ -496,7 +504,7 @@ protected function getDatabaseConnectionInfo() {
    */
   private function getCompiledContainerBuilder(array $modules) {
     if (!isset(self::$initialContainerBuilder)) {
-      $kernel = new DrupalKernel('testing', $this->classLoader, FALSE);
+      $kernel = new DrupalKernel('testing', $this->classLoader, FALSE, $this->sapiAdapter);
       $kernel->setSitePath($this->siteDirectory);
       if ($modules && $extensions = $this->getExtensionsForModules($modules)) {
         $kernel->updateModules($extensions, $extensions);
diff --git a/core/tests/Drupal/Tests/Core/DrupalKernel/DiscoverServiceProvidersTest.php b/core/tests/Drupal/Tests/Core/DrupalKernel/DiscoverServiceProvidersTest.php
index 164da9d..ee38af7 100644
--- a/core/tests/Drupal/Tests/Core/DrupalKernel/DiscoverServiceProvidersTest.php
+++ b/core/tests/Drupal/Tests/Core/DrupalKernel/DiscoverServiceProvidersTest.php
@@ -4,6 +4,7 @@
 
 use Composer\Autoload\ClassLoader;
 use Drupal\Core\DrupalKernel;
+use Drupal\Core\SAPI\NullSAPIAdapter;
 use Drupal\Core\Site\Settings;
 use Drupal\Tests\UnitTestCase;
 
@@ -25,7 +26,8 @@ public function testDiscoverServiceCustom() {
       ),
     ));
 
-    $kernel = new DrupalKernel('prod', new ClassLoader());
+    $sapi_handler = new NullSAPIAdapter();
+    $kernel = new DrupalKernel('prod', new \Composer\Autoload\ClassLoader(), TRUE, $sapi_handler);
     $kernel->discoverServiceProviders();
 
     $expect = array(
@@ -45,7 +47,8 @@ public function testDiscoverServiceCustom() {
    */
   public function testDiscoverServiceNoContainerYamls() {
     new Settings([]);
-    $kernel = new DrupalKernel('prod', new ClassLoader());
+    $sapi_handler = new NullSAPIAdapter();
+    $kernel = new DrupalKernel('prod', new \Composer\Autoload\ClassLoader(), TRUE, $sapi_handler);
     $kernel->discoverServiceProviders();
 
     $expect = [
