diff --git a/composer.json b/composer.json
index 4c0f43c..8057c96 100644
--- a/composer.json
+++ b/composer.json
@@ -5,6 +5,9 @@
   "license": "GPL-2.0+",
   "require": {
     "php": ">=5.4.5",
+    "behat/mink": "~1.6",
+    "behat/mink-goutte-driver": "dev-master",
+    "fabpot/goutte": "dev-master",
     "sdboyer/gliph": "0.1.*",
     "symfony/class-loader": "2.6.*",
     "symfony/css-selector": "2.6.*",
diff --git a/composer.lock b/composer.lock
index c5627d5..5533083 100644
--- a/composer.lock
+++ b/composer.lock
@@ -4,9 +4,171 @@
         "Read more about it at http://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file",
         "This file is @generated automatically"
     ],
-    "hash": "fce47406c708047e654d8274620405e8",
+    "hash": "6b033e8299ae36868b1a2505a5c7257c",
     "packages": [
         {
+            "name": "behat/mink",
+            "version": "v1.6.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/Behat/Mink.git",
+                "reference": "090900a0049c441f1e072bbd837db4079b2250c5"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/Behat/Mink/zipball/090900a0049c441f1e072bbd837db4079b2250c5",
+                "reference": "090900a0049c441f1e072bbd837db4079b2250c5",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.1",
+                "symfony/css-selector": "~2.0"
+            },
+            "suggest": {
+                "behat/mink-browserkit-driver": "extremely fast headless driver for Symfony\\Kernel-based apps (Sf2, Silex)",
+                "behat/mink-goutte-driver": "fast headless driver for any app without JS emulation",
+                "behat/mink-selenium2-driver": "slow, but JS-enabled driver for any app (requires Selenium2)",
+                "behat/mink-zombie-driver": "fast and JS-enabled headless driver for any app (requires node.js)"
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.6.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-0": {
+                    "Behat\\Mink": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Konstantin Kudryashov",
+                    "email": "ever.zet@gmail.com",
+                    "homepage": "http://everzet.com"
+                }
+            ],
+            "description": "Web acceptance testing framework for PHP 5.3",
+            "homepage": "http://mink.behat.org/",
+            "keywords": [
+                "browser",
+                "testing",
+                "web"
+            ],
+            "time": "2014-09-26 09:25:05"
+        },
+        {
+            "name": "behat/mink-browserkit-driver",
+            "version": "v1.2.0",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/Behat/MinkBrowserKitDriver.git",
+                "reference": "aed8f4a596b79014a75254c3e337511c33e38cbd"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/Behat/MinkBrowserKitDriver/zipball/aed8f4a596b79014a75254c3e337511c33e38cbd",
+                "reference": "aed8f4a596b79014a75254c3e337511c33e38cbd",
+                "shasum": ""
+            },
+            "require": {
+                "behat/mink": "~1.6@dev",
+                "php": ">=5.3.1",
+                "symfony/browser-kit": "~2.0",
+                "symfony/dom-crawler": "~2.0"
+            },
+            "require-dev": {
+                "silex/silex": "~1.2"
+            },
+            "type": "mink-driver",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.2.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-0": {
+                    "Behat\\Mink\\Driver": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Konstantin Kudryashov",
+                    "email": "ever.zet@gmail.com",
+                    "homepage": "http://everzet.com"
+                }
+            ],
+            "description": "Symfony2 BrowserKit driver for Mink framework",
+            "homepage": "http://mink.behat.org/",
+            "keywords": [
+                "Mink",
+                "Symfony2",
+                "browser",
+                "testing"
+            ],
+            "time": "2014-09-26 11:35:19"
+        },
+        {
+            "name": "behat/mink-goutte-driver",
+            "version": "dev-master",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/Behat/MinkGoutteDriver.git",
+                "reference": "2bf327b4166694ecaa8ae7f956cb6ae252ecf03e"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/Behat/MinkGoutteDriver/zipball/2bf327b4166694ecaa8ae7f956cb6ae252ecf03e",
+                "reference": "2bf327b4166694ecaa8ae7f956cb6ae252ecf03e",
+                "shasum": ""
+            },
+            "require": {
+                "behat/mink": "~1.6@dev",
+                "behat/mink-browserkit-driver": "~1.2@dev",
+                "fabpot/goutte": "~1.0.4|~2.0",
+                "php": ">=5.3.1"
+            },
+            "type": "mink-driver",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "1.1.x-dev"
+                }
+            },
+            "autoload": {
+                "psr-0": {
+                    "Behat\\Mink\\Driver": "src/"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Konstantin Kudryashov",
+                    "email": "ever.zet@gmail.com",
+                    "homepage": "http://everzet.com"
+                }
+            ],
+            "description": "Goutte driver for Mink framework",
+            "homepage": "http://mink.behat.org/",
+            "keywords": [
+                "browser",
+                "goutte",
+                "headless",
+                "testing"
+            ],
+            "time": "2014-10-09 09:21:12"
+        },
+        {
             "name": "doctrine/annotations",
             "version": "v1.2.1",
             "source": {
@@ -567,6 +729,55 @@
             "time": "2014-11-06 08:59:44"
         },
         {
+            "name": "fabpot/goutte",
+            "version": "dev-master",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/FriendsOfPHP/Goutte.git",
+                "reference": "65ab61eae03d670b93a9044ad2328eb81aa1bde5"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/FriendsOfPHP/Goutte/zipball/65ab61eae03d670b93a9044ad2328eb81aa1bde5",
+                "reference": "65ab61eae03d670b93a9044ad2328eb81aa1bde5",
+                "shasum": ""
+            },
+            "require": {
+                "guzzlehttp/guzzle": ">=4,<6",
+                "php": ">=5.4.0",
+                "symfony/browser-kit": "~2.1",
+                "symfony/css-selector": "~2.1",
+                "symfony/dom-crawler": "~2.1"
+            },
+            "type": "application",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.0-dev"
+                }
+            },
+            "autoload": {
+                "psr-4": {
+                    "Goutte\\": "Goutte"
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                }
+            ],
+            "description": "A simple PHP Web Scraper",
+            "homepage": "https://github.com/FriendsOfPHP/Goutte",
+            "keywords": [
+                "scraper"
+            ],
+            "time": "2014-11-28 09:48:17"
+        },
+        {
             "name": "guzzlehttp/guzzle",
             "version": "5.0.3",
             "source": {
@@ -1642,6 +1853,61 @@
             "time": "2014-10-20 20:55:17"
         },
         {
+            "name": "symfony/browser-kit",
+            "version": "v2.6.0",
+            "target-dir": "Symfony/Component/BrowserKit",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/BrowserKit.git",
+                "reference": "de6a5b6d09a67f5b53ce423a320b75afb335cca0"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/BrowserKit/zipball/de6a5b6d09a67f5b53ce423a320b75afb335cca0",
+                "reference": "de6a5b6d09a67f5b53ce423a320b75afb335cca0",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3",
+                "symfony/dom-crawler": "~2.0"
+            },
+            "require-dev": {
+                "symfony/css-selector": "~2.0",
+                "symfony/process": "~2.0"
+            },
+            "suggest": {
+                "symfony/process": ""
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.6-dev"
+                }
+            },
+            "autoload": {
+                "psr-0": {
+                    "Symfony\\Component\\BrowserKit\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Symfony Community",
+                    "homepage": "http://symfony.com/contributors"
+                },
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                }
+            ],
+            "description": "Symfony BrowserKit Component",
+            "homepage": "http://symfony.com",
+            "time": "2014-10-26 07:46:28"
+        },
+        {
             "name": "symfony/class-loader",
             "version": "v2.6.0",
             "target-dir": "Symfony/Component/ClassLoader",
@@ -1856,6 +2122,59 @@
             "time": "2014-11-28 10:00:40"
         },
         {
+            "name": "symfony/dom-crawler",
+            "version": "v2.6.0",
+            "target-dir": "Symfony/Component/DomCrawler",
+            "source": {
+                "type": "git",
+                "url": "https://github.com/symfony/DomCrawler.git",
+                "reference": "9a67f1344e70a18e5d5e755ed442066351bed936"
+            },
+            "dist": {
+                "type": "zip",
+                "url": "https://api.github.com/repos/symfony/DomCrawler/zipball/9a67f1344e70a18e5d5e755ed442066351bed936",
+                "reference": "9a67f1344e70a18e5d5e755ed442066351bed936",
+                "shasum": ""
+            },
+            "require": {
+                "php": ">=5.3.3"
+            },
+            "require-dev": {
+                "symfony/css-selector": "~2.0"
+            },
+            "suggest": {
+                "symfony/css-selector": ""
+            },
+            "type": "library",
+            "extra": {
+                "branch-alias": {
+                    "dev-master": "2.6-dev"
+                }
+            },
+            "autoload": {
+                "psr-0": {
+                    "Symfony\\Component\\DomCrawler\\": ""
+                }
+            },
+            "notification-url": "https://packagist.org/downloads/",
+            "license": [
+                "MIT"
+            ],
+            "authors": [
+                {
+                    "name": "Symfony Community",
+                    "homepage": "http://symfony.com/contributors"
+                },
+                {
+                    "name": "Fabien Potencier",
+                    "email": "fabien@symfony.com"
+                }
+            ],
+            "description": "Symfony DomCrawler Component",
+            "homepage": "http://symfony.com",
+            "time": "2014-11-20 13:24:23"
+        },
+        {
             "name": "symfony/event-dispatcher",
             "version": "v2.6.0",
             "target-dir": "Symfony/Component/EventDispatcher",
@@ -2524,6 +2843,8 @@
     "aliases": [],
     "minimum-stability": "stable",
     "stability-flags": {
+        "behat/mink-goutte-driver": 20,
+        "fabpot/goutte": 20,
         "doctrine/common": 20,
         "phpunit/phpunit-mock-objects": 20
     },
diff --git a/core/includes/bootstrap.inc b/core/includes/bootstrap.inc
index 1cd178c..1072f78 100644
--- a/core/includes/bootstrap.inc
+++ b/core/includes/bootstrap.inc
@@ -113,7 +113,9 @@
  * @see http://php.net/manual/reserved.variables.server.php
  * @see http://php.net/manual/function.time.php
  */
-define('REQUEST_TIME', (int) $_SERVER['REQUEST_TIME']);
+if (!defined('REQUEST_TIME')) {
+  define('REQUEST_TIME', (int) $_SERVER['REQUEST_TIME']);
+}
 
 /**
  * Regular expression to match PHP function names.
@@ -141,7 +143,9 @@
  *
  * This strips two levels of directories off the current directory.
  */
-define('DRUPAL_ROOT', dirname(dirname(__DIR__)));
+if (!defined('DRUPAL_ROOT')) {
+  define('DRUPAL_ROOT', dirname(dirname(__DIR__)));
+}
 
 /**
  * Returns the appropriate configuration directory.
@@ -978,7 +982,9 @@ function drupal_valid_test_ua($new_prefix = NULL) {
 
   // Perform a basic check on the User-Agent HTTP request header first. Any
   // inbound request that uses the simpletest UA header needs to be validated.
-  if (isset($_SERVER['HTTP_USER_AGENT']) && preg_match("/^(simpletest\d+);(.+);(.+);(.+)$/", $_SERVER['HTTP_USER_AGENT'], $matches)) {
+  $http_user_agent = isset($_SERVER['HTTP_USER_AGENT']) ? $_SERVER['HTTP_USER_AGENT'] : NULL;
+  $user_agent = isset($_COOKIE['SIMPLETEST_USER_AGENT']) ? $_COOKIE['SIMPLETEST_USER_AGENT'] : $http_user_agent;
+  if (isset($user_agent) && preg_match("/^(simpletest\d+);(.+);(.+);(.+)$/", $user_agent, $matches)) {
     list(, $prefix, $time, $salt, $hmac) = $matches;
     $check_string =  $prefix . ';' . $time . ';' . $salt;
     // Read the hash salt prepared by drupal_generate_test_ua().
diff --git a/core/includes/install.core.inc b/core/includes/install.core.inc
index 968192e..12ff582 100644
--- a/core/includes/install.core.inc
+++ b/core/includes/install.core.inc
@@ -280,7 +280,8 @@ function install_begin_request(&$install_state) {
   // The user agent header is used to pass a database prefix in the request when
   // running tests. However, for security reasons, it is imperative that no
   // installation be permitted using such a prefix.
-  if ($install_state['interactive'] && strpos($request->server->get('HTTP_USER_AGENT'), 'simpletest') !== FALSE && !drupal_valid_test_ua()) {
+  $user_agent = $request->cookies->get('SIMPLETEST_USER_AGENT') ?: $request->server->get('HTTP_USER_AGENT');
+  if ($install_state['interactive'] && strpos($user_agent, 'simpletest') !== FALSE && !drupal_valid_test_ua()) {
     header($request->server->get('SERVER_PROTOCOL') . ' 403 Forbidden');
     exit;
   }
diff --git a/core/modules/simpletest/src/BrowserTestBase.php b/core/modules/simpletest/src/BrowserTestBase.php
new file mode 100644
index 0000000..0e6ea56
--- /dev/null
+++ b/core/modules/simpletest/src/BrowserTestBase.php
@@ -0,0 +1,1363 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\simpletest\BrowserTestBase.
+ */
+
+namespace Drupal\simpletest;
+
+use Behat\Mink\Exception\Exception;
+use Behat\Mink\Mink;
+use Behat\Mink\Session;
+use Behat\Mink\Element\Element;
+use Behat\Mink\Element\NodeElement;
+use Behat\Mink\Driver\GoutteDriver;
+use Drupal\Component\Utility\Crypt;
+use Drupal\Component\Utility\Random;
+use Drupal\Core\Cache\Cache;
+use Drupal\Core\Database\ConnectionNotDefinedException;
+use Drupal\Core\Database\Database;
+use Drupal\Core\DrupalKernel;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\Core\Session\AnonymousUserSession;
+use Drupal\Component\Utility\String;
+use Drupal\Core\Session\UserSession;
+use Drupal\Core\Site\Settings;
+use Drupal\Core\Test\TestRunnerKernel;
+use Symfony\Component\HttpFoundation\Request;
+
+/**
+ * Test case for typical Drupal tests.
+ *
+ * @ingroup testing
+ */
+abstract class BrowserTestBase extends \PHPUnit_Framework_TestCase {
+
+  /**
+   * Class loader.
+   *
+   * @var object
+   */
+  protected $classLoader;
+
+  /**
+   * The site directory of this test run.
+   *
+   * @var string
+   */
+  protected $siteDirectory = NULL;
+
+  /**
+   * The database prefix of this test run.
+   *
+   * @var string
+   */
+  protected $databasePrefix = NULL;
+
+  /**
+   * The site directory of the original parent site.
+   *
+   * @var string
+   */
+  protected $originalSite;
+
+  /**
+   * Time limit in seconds for the test.
+   */
+  protected $timeLimit = 500;
+
+  /**
+   * The public file directory for the test environment.
+   *
+   * This is set in BrowserTestBase::prepareEnvironment().
+   *
+   * @var string
+   */
+  protected $public_files_directory;
+
+  /**
+   * The private file directory for the test environment.
+   *
+   * This is set in BrowserTestBase::prepareEnvironment().
+   *
+   * @var string
+   */
+  protected $private_files_directory;
+
+  /**
+   * The temp file directory for the test environment.
+   *
+   * This is set in BrowserTestBase::prepareEnvironment().
+   *
+   * @var string
+   */
+  protected $temp_files_directory;
+
+  /**
+   * The translation file directory for the test environment.
+   *
+   * This is set in BrowserTestBase::prepareEnvironment().
+   *
+   * @var string
+   */
+  protected $translation_files_directory;
+
+  /**
+   * 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 used in a test.
+   *
+   * @var \Drupal\Core\Config\ConfigImporter
+   */
+  protected $configImporter;
+
+  /**
+   * The random generator.
+   *
+   * @var \Drupal\Component\Utility\Random
+   */
+  protected $randomGenerator;
+
+  /**
+   * The profile to install as a basis for testing.
+   *
+   * @var string
+   */
+  protected $profile = 'testing';
+
+  /**
+   * The current session name, if available.
+   *
+   * @var string
+   */
+  protected $session_name = NULL;
+
+  /**
+   * The current user logged in using the internal browser.
+   *
+   * @var bool
+   */
+  protected $loggedInUser = FALSE;
+
+  /**
+   * The root user.
+   *
+   * @var UserSession
+   */
+  protected $root_user;
+
+  /**
+   * The config directories used in this test.
+   */
+  protected $configDirectories = array();
+
+  /**
+   * An array of custom translations suitable for drupal_rewrite_settings().
+   *
+   * @var array
+   */
+  protected $customTranslations;
+
+  /**
+   * @var Mink
+   */
+  protected $mink;
+
+  /**
+   * Test ID.
+   *
+   * @var string
+   */
+  protected $testId;
+
+  /**
+   * Constructor for \Drupal\simpletest\BrowserTestBase.
+   */
+  public function __construct($test_id = NULL) {
+    parent::__construct($test_id);
+    $this->skipClasses[__CLASS__] = TRUE;
+  }
+
+  /**
+   * Initializes mink sessions.
+   */
+  protected function initMink() {
+    $driver = new GoutteDriver();
+    $session = new Session($driver);
+    $this->mink = new Mink();
+    $this->mink->registerSession('goutte', $session);
+    $this->mink->setDefaultSessionName('goutte');
+    $this->registerSessions();
+    return $session;
+  }
+
+  /**
+   * 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}
+   */
+  public 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 \InvalidArgumentException('You must provide a SIMPLETEST_BASE_URL environment variable to run 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 ($path == '/') {
+      $path = '';
+    }
+    // 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.
+   */
+  public static function filePreDeleteCallback($path) {
+    chmod($path, 0700);
+  }
+
+  /**
+   * 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($test_prefix . '%');
+      $prefix_length = strlen($test_prefix);
+      foreach ($tables as $table) {
+        if (Database::getConnection()->schema()->dropTable(substr($table, $prefix_length))) {
+          unset($tables[$table]);
+        }
+      }
+    }
+
+    // Delete test site directory.
+    file_unmanaged_delete_recursive($this->siteDirectory, array($this, 'filePreDeleteCallback'));
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public 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|null $name
+   *   Name of the session OR active session will be used.
+   *
+   * @return \Behat\Mink\Session
+   *   The active mink session object.
+   */
+  public function getSession($name = NULL) {
+    return $this->mink->getSession($name);
+  }
+
+  /**
+   * Returns Mink assert session.
+   *
+   * @param string|null $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 internal browser
+   * @param array $options
+   *   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')) {
+      $url = $this->container->get('url_generator')->generateFromPath($path, $options);
+    }
+    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 internal 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 ($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
+   *   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
+   *   The user name.
+   *
+   * @return \Drupal\user\Entity\User|false
+   *   A fully loaded user object with pass_raw 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 = entity_create('user', $edit);
+    $account->save();
+
+    $this->assertNotNull($account->id(), String::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->pass_raw = $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 = entity_create('user_role', array(
+      'id' => $rid,
+      'label' => $name,
+    ));
+    if (!is_null($weight)) {
+      $role->set('weight', $weight);
+    }
+    $result = $role->save();
+
+    $this->assertSame($result, SAVED_NEW, String::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(String::format('Failed to create permissions: @perms', array('@perms' => implode(', ', $missing_permissions))));
+        }
+      }
+      return $role->id();
+    }
+    else {
+      return FALSE;
+    }
+  }
+
+  /**
+   * Generates a unique random string containing letters and numbers.
+   *
+   * Do not use this method when testing unvalidated user input. Instead, use
+   * \Drupal\simpletest\TestBase::randomString().
+   *
+   * @param int $length
+   *   Length of random string to generate.
+   *
+   * @return string
+   *   Randomly generated unique string.
+   *
+   * @see \Drupal\Component\Utility\Random::name()
+   */
+  public function randomMachineName($length = 8) {
+    return $this->getRandomGenerator()->name($length, TRUE);
+  }
+
+  /**
+   * Generates a pseudo-random string of ASCII characters of codes 32 to 126.
+   *
+   * Do not use this method when special characters are not possible (e.g., in
+   * machine or file names that have already been validated); instead, use
+   * \Drupal\simpletest\TestBase::randomMachineName(). If $length is greater
+   * than 2 the random string will include at least one ampersand ('&')
+   * character to ensure coverage for special characters and avoid the
+   * introduction of random test failures.
+   *
+   * @param int $length
+   *   Length of random string to generate.
+   *
+   * @return string
+   *   Pseudo-randomly generated unique string including special characters.
+   *
+   * @see \Drupal\Component\Utility\Random::string()
+   */
+  public function randomString($length = 8) {
+    if ($length < 3) {
+      return $this->getRandomGenerator()->string($length, TRUE, array($this, 'randomStringValidate'));
+    }
+
+    // To prevent the introduction of random test failures, ensure that the
+    // returned string contains a character that needs to be escaped in HTML by
+    // injecting an ampersand into it.
+    $replacement_pos = floor($length / 2);
+    // Remove 1 from the length to account for the ampersand character.
+    $string = $this->getRandomGenerator()->string($length - 1, TRUE, array($this, 'randomStringValidate'));
+    return substr_replace($string, '&', $replacement_pos, 0);
+  }
+
+  /**
+   * 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(String::format('Invalid permission %permission.', array('%permission' => $permission)));
+        $valid = FALSE;
+      }
+    }
+    return $valid;
+  }
+
+  /**
+   * Logs in a user using the mink 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->pass_raw;
+   *   $account = user_load($account->id());
+   *   $account->pass_raw = $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->pass_raw,
+    ), t('Log in'));
+
+    // @see BrowserTestBase::drupalUserIsLoggedIn()
+    $account->session_id = $this->getSession()->getCookie(session_name());
+    $this->assertTrue($this->drupalUserIsLoggedIn($account), String::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 internal 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->session_id);
+    $this->loggedInUser = FALSE;
+    $this->container->get('current_user')->setAccount(new AnonymousUserSession());
+  }
+
+  /**
+   * Fills and submit 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($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 NodeElement|string $select
+   *   Name, ID, or Label of select field to assert.
+   * @param 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;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function run(\PHPUnit_Framework_TestResult $result = NULL) {
+    if ($result === NULL) {
+      $result = $this->createResult();
+    }
+
+    parent::run($result);
+    return $result;
+  }
+
+  /**
+   * Override to use Mink exceptions.
+   *
+   * @return mixed
+   *   Either a test result or NULL.
+   * @throws \Exception When exception was thrown inside the test.
+   */
+  protected function runTest() {
+    try {
+      return parent::runTest();
+    }
+    catch (Exception $e) {
+      throw new \PHPUnit_Framework_AssertionFailedError($e->getMessage());
+    }
+  }
+
+  /**
+   * Generates a unique random string containing letters and numbers.
+   *
+   * Do not use this method when testing unvalidated user input. Instead, use
+   * \Drupal\simpletest\TestBase::randomString().
+   *
+   * @param int $length
+   *   Length of random string to generate.
+   *
+   * @return string
+   *   Randomly generated unique string.
+   *
+   * @see \Drupal\Component\Utility\Random::name()
+   */
+  public function randomName($length = 8) {
+    return $this->getRandomGenerator()->name($length, TRUE);
+  }
+
+  /**
+   * Gets the random generator for the utility methods.
+   *
+   * @return \Drupal\Component\Utility\Random
+   *   The random generator
+   */
+  protected function getRandomGenerator() {
+    if (!is_object($this->randomGenerator)) {
+      $this->randomGenerator = new Random();
+    }
+    return $this->randomGenerator;
+  }
+
+  /**
+   * Installs drupal into the simpletest site.
+   */
+  public function installDrupal() {
+    // Define information about the user 1 account.
+    $this->root_user = new UserSession(array(
+      'uid' => 1,
+      'name' => 'admin',
+      'mail' => 'admin@example.com',
+      'pass_raw' => $this->randomName(),
+    ));
+
+    // Some tests (SessionTest and SessionHttpsTest) need to examine whether the
+    // proper session cookies were set on a response. Because the child site
+    // uses the same session name as the test runner, it is necessary to make
+    // that available to test-methods.
+    $this->session_name = session_name();
+
+    // 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');
+    copy(DRUPAL_ROOT . '/sites/default/default.services.yml', $directory . '/services.yml');
+
+    // 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->public_files_directory,
+      'required' => TRUE,
+    );
+    $this->writeSettings($settings);
+    // Allow for test-specific overrides.
+    $settings_testing_file = DRUPAL_ROOT . '/' . $this->originalSite . '/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->originalSite . '/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.
+    require_once DRUPAL_ROOT . '/core/includes/install.core.inc';
+    install_drupal($parameters);
+
+    // 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);
+    $this->kernel->prepareLegacyRequest($request);
+    // Force the container to be built from scratch instead of loaded from the
+    // disk. This forces us to not accidently 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->private_files_directory, FILE_CREATE_DIRECTORY);
+    file_prepare_directory($this->temp_files_directory, FILE_CREATE_DIRECTORY);
+    $config->get('system.file')
+      ->set('path.private', $this->private_files_directory)
+      ->set('path.temporary', $this->temp_files_directory)
+      ->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->get('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://drupal.org/node/2259167
+    $config->get('system.logging')
+      ->set('error_level', 'verbose')
+      ->save();
+    $config->get('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, String::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. file_get_stream_wrappers().
+    // @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->root_user->name,
+            'mail' => $this->root_user->getEmail(),
+            'pass' => array(
+              'pass1' => $this->root_user->pass_raw,
+              'pass2' => $this->root_user->pass_raw,
+            ),
+          ),
+          // 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 as user agent HTTP header
+   * value by the cURL-based browser of DrupalWebTestCase, which is sent to the
+   * Drupal installation of the test. During early Drupal bootstrap, the user
+   * agent HTTP header is parsed, and if it matches, all database queries use
+   * the database table prefix that has been generated here.
+   *
+   * @see WebTestBase::curlInitialize()
+   * @see drupal_valid_test_ua()
+   * @see \Drupal\simpletest\TestBase::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 TestBase::prepareEnvironment()
+   */
+  private function changeDatabasePrefix() {
+    if (empty($this->databasePrefix)) {
+      $this->prepareDatabasePrefix();
+    }
+
+    // Clone the current connection and replace the current prefix.
+    $connection_info = Database::getConnectionInfo('default');
+    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.
+   *
+   * Backups various current environment variables and resets them, so they do
+   * not interfere with the Drupal site installation in which tests are executed
+   * and can be restored in TestBase::restoreEnvironment().
+   *
+   * 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 TestBase::run()
+   * (multiple invocations for the same test would have unpredictable
+   * consequences) and it must not be callable or overridable by test classes.
+   *
+   * @see TestBase::beforePrepareEnvironment()
+   */
+  protected function prepareEnvironment() {
+    // Bootstrap Drupal so we can use Drupal's built in functions.
+    $this->classLoader = require __DIR__ . '/../../../vendor/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();
+
+    // 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->public_files_directory = $this->siteDirectory . '/files';
+    $this->private_files_directory = $this->siteDirectory . '/private';
+    $this->temp_files_directory = $this->siteDirectory . '/temp';
+    $this->translation_files_directory = $this->siteDirectory . '/translations';
+
+    // Ensure the configImporter is refreshed for each test.
+    $this->configImporter = NULL;
+
+    // Unregister all custom stream wrappers of the parent site.
+    // Availability of Drupal stream wrappers varies by test base class:
+    // - UnitTestBase operates in a completely empty environment.
+    // - KernelTestBase supports and maintains stream wrappers in a custom
+    //   way.
+    // - WebTestBase re-initializes Drupal stream wrappers after installation.
+    // The original stream wrappers are restored after the test run.
+    // @see TestBase::restoreEnvironment()
+    $wrappers = file_get_stream_wrappers();
+    foreach ($wrappers as $scheme => $info) {
+      stream_wrapper_unregister($scheme);
+    }
+
+    // Reset statics.
+    drupal_static_reset();
+
+    // Ensure there is no service container.
+    $this->container = NULL;
+    \Drupal::setContainer(NULL);
+
+    // 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);
+    conf_path(FALSE, TRUE);
+
+    // 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
+    // @todo Convert Simpletest UI runner to create + use this connection, too.
+    try {
+      $connection = Database::getConnection('default', 'test-runner');
+    }
+    catch (ConnectionNotDefinedException $e) {
+      // Check whether there is a backup of the original default connection.
+      // @see TestBase::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';
+
+    error_log($filename);
+
+    // 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 internal 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() {
+    // Maintain the current global request object.
+    $request = \Drupal::request();
+    // 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);
+    $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\WebTestBase::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->drupalPostForm() 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($account) {
+    if (!isset($account->session_id)) {
+      return FALSE;
+    }
+    // The session ID is hashed before being stored in the database.
+    // @see \Drupal\Core\Session\SessionHandler::read()
+    return (bool) db_query("SELECT sid FROM {users_field_data} u INNER JOIN {sessions} s ON u.uid = s.uid AND u.default_langcode = 1 WHERE s.sid = :sid", array(':sid' => Crypt::hashBase64($account->session_id)))->fetchField();
+  }
+
+}
diff --git a/core/modules/simpletest/src/Form/SimpletestTestForm.php b/core/modules/simpletest/src/Form/SimpletestTestForm.php
index b55de4c..44c3530 100644
--- a/core/modules/simpletest/src/Form/SimpletestTestForm.php
+++ b/core/modules/simpletest/src/Form/SimpletestTestForm.php
@@ -186,6 +186,7 @@ public function buildForm(array $form, FormStateInterface $form_state) {
    * {@inheritdoc}
    */
   public function submitForm(array &$form, FormStateInterface $form_state) {
+    global $base_url;
     // Test discovery does not run upon form submission.
     simpletest_classloader_register();
 
@@ -216,6 +217,7 @@ public function submitForm(array &$form, FormStateInterface $form_state) {
       }
     }
     if (!empty($tests_list)) {
+      putenv('SIMPLETEST_BASE_URL=' . $base_url);
       $test_id = simpletest_run_tests($tests_list, 'drupal');
       $form_state->setRedirect(
         'simpletest.result_form',
diff --git a/core/modules/simpletest/src/WebAssert.php b/core/modules/simpletest/src/WebAssert.php
new file mode 100644
index 0000000..daea904
--- /dev/null
+++ b/core/modules/simpletest/src/WebAssert.php
@@ -0,0 +1,71 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\simpletest\WebAssert.
+ */
+
+namespace Drupal\simpletest;
+
+use Behat\Mink\WebAssert as MinkWebAssert;
+use Behat\Mink\Element\TraversableElement;
+use Behat\Mink\Exception\ElementNotFoundException;
+
+/**
+ * Defines a class with methods for asserting presence of elements during tests.
+ */
+class WebAssert extends MinkWebAssert {
+
+  /**
+   * Checks that specific button exists on the current page.
+   *
+   * @param string $button
+   *   One of id|name|label|value for the button.
+   * @param \Behat\Mink\Element\TraversableElement $container
+   *   The document to check against.
+   *
+   * @return \Behat\Mink\Element\NodeElement
+   *   The matching element.
+   *
+   * @throws \Behat\Mink\Exception\ElementNotFoundException
+   *   When the element doesn't exist.
+   */
+  public function buttonExists($button, TraversableElement $container = NULL) {
+    $container = $container ?: $this->session->getPage();
+    $node = $container->findButton($button);
+
+    if (NULL === $node) {
+      throw new ElementNotFoundException($this->session, 'button', 'id|name|label|value', $button);
+    }
+
+    return $node;
+  }
+
+  /**
+   * Checks that specific select field exists on the current page.
+   *
+   * @param string $select
+   *   One of id|name|label|value for the select field.
+   * @param \Behat\Mink\Element\TraversableElement $container
+   *   The document to check against.
+   *
+   * @return \Behat\Mink\Element\NodeElement
+   *   The matching element
+   *
+   * @throws \Behat\Mink\Exception\ElementNotFoundException
+   *   When the element doesn't exist.
+   */
+  public function selectExists($select, TraversableElement $container = NULL) {
+    $container = $container ?: $this->session->getPage();
+    $node = $container->find('named', array(
+      'select',
+      $this->session->getSelectorsHandler()->xpathLiteral($select),
+    ));
+
+    if (NULL === $node) {
+      throw new ElementNotFoundException($this->session, 'select', 'id|name|label|value', $select);
+    }
+
+    return $node;
+  }
+}
diff --git a/core/modules/simpletest/tests/simpletest_example/config/install/simpletest_test.settings.yml b/core/modules/simpletest/tests/simpletest_example/config/install/simpletest_test.settings.yml
new file mode 100644
index 0000000..c2e2313
--- /dev/null
+++ b/core/modules/simpletest/tests/simpletest_example/config/install/simpletest_test.settings.yml
@@ -0,0 +1 @@
+name: ''
diff --git a/core/modules/simpletest/tests/simpletest_example/config/schema/simpletest_test.schema.yml b/core/modules/simpletest/tests/simpletest_example/config/schema/simpletest_test.schema.yml
new file mode 100644
index 0000000..6d91dfa
--- /dev/null
+++ b/core/modules/simpletest/tests/simpletest_example/config/schema/simpletest_test.schema.yml
@@ -0,0 +1,9 @@
+# Schema for the configuration files of the Simpletest test module.
+
+simpletest_test.settings:
+  type: mapping
+  label: 'Testing the testers'
+  mapping:
+    name:
+      type: string
+      label: 'A test config value'
diff --git a/core/modules/simpletest/tests/simpletest_example/simpletest_example.info.yml b/core/modules/simpletest/tests/simpletest_example/simpletest_example.info.yml
new file mode 100644
index 0000000..27a72ba
--- /dev/null
+++ b/core/modules/simpletest/tests/simpletest_example/simpletest_example.info.yml
@@ -0,0 +1,6 @@
+name: Simpletest Example
+type: module
+description: 'Helper module for BrowserTestBaseTest.'
+package: Testing
+version: VERSION
+core: 8.x
diff --git a/core/modules/simpletest/tests/simpletest_example/simpletest_example.permissions.yml b/core/modules/simpletest/tests/simpletest_example/simpletest_example.permissions.yml
new file mode 100644
index 0000000..2e5ef16
--- /dev/null
+++ b/core/modules/simpletest/tests/simpletest_example/simpletest_example.permissions.yml
@@ -0,0 +1,2 @@
+simpletest_example access tests:
+  title: 'Access simpletest example tests'
diff --git a/core/modules/simpletest/tests/simpletest_example/simpletest_example.routing.yml b/core/modules/simpletest/tests/simpletest_example/simpletest_example.routing.yml
new file mode 100644
index 0000000..fbf622d
--- /dev/null
+++ b/core/modules/simpletest/tests/simpletest_example/simpletest_example.routing.yml
@@ -0,0 +1,12 @@
+simpletest.settings:
+  path: '/simpletest/hello'
+  defaults:
+      _controller: '\Drupal\simpletest_example\Controller\ExampleController::hello'
+  requirements:
+    _permission: 'simpletest_example access tests'
+simpletest.example_form:
+  path: '/simpletest/example-form'
+  defaults:
+    _form: '\Drupal\simpletest_example\Form\ExampleForm'
+  requirements:
+    _access: 'TRUE'
diff --git a/core/modules/simpletest/tests/simpletest_example/src/Controller/ExampleController.php b/core/modules/simpletest/tests/simpletest_example/src/Controller/ExampleController.php
new file mode 100644
index 0000000..631b917
--- /dev/null
+++ b/core/modules/simpletest/tests/simpletest_example/src/Controller/ExampleController.php
@@ -0,0 +1,29 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\simpletest_example\Controller\ExampleController.
+ */
+
+namespace Drupal\simpletest_example\Controller;
+
+use Drupal\Core\Controller\ControllerBase;
+
+/**
+ * Defines a test controller for testing \Drupal\simpletest\BrowserTestBase.
+ */
+class ExampleController extends ControllerBase {
+
+  /**
+   * Provides output for testing \Drupal\simpletest\BrowserTestBase.
+   *
+   * @return array
+   *   A render array.
+   */
+  public function hello() {
+    return array(
+      '#markup' => $this->t('Hello Drupal'),
+    );
+  }
+
+}
diff --git a/core/modules/simpletest/tests/simpletest_example/src/Form/ExampleForm.php b/core/modules/simpletest/tests/simpletest_example/src/Form/ExampleForm.php
new file mode 100644
index 0000000..1d2cb45
--- /dev/null
+++ b/core/modules/simpletest/tests/simpletest_example/src/Form/ExampleForm.php
@@ -0,0 +1,46 @@
+<?php
+
+/**
+ * @file
+ * Contains \Drupal\simpletest_example\Form\ExampleForm.
+ */
+
+namespace Drupal\simpletest_example\Form;
+
+use Drupal\Core\Form\ConfigFormBase;
+use Drupal\Core\Form\FormStateInterface;
+
+/**
+ * Provides an example form for testing \Drupal\simpletest\BrowserTestBase.
+ */
+class ExampleForm extends ConfigFormBase {
+
+  /**
+   * {@inheritdoc}
+   */
+  public function getFormId() {
+    return 'simpletest_test_example_form';
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function buildForm(array $form, FormStateInterface $form_state) {
+    $form['name'] = [
+      '#type' => 'textfield',
+      '#default_value' => \Drupal::config('simpletest_example.settings')->get('name'),
+    ];
+    return parent::buildForm($form, $form_state);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  public function submitForm(array &$form, FormStateInterface $form_state) {
+    // Normally this config object would be injected but this is a only a test
+    // so...who cares?
+    \Drupal::config('simpletest_example.settings')->set('name', $form_state->getValue('name'))->save();
+    parent::submitForm($form, $form_state);
+  }
+
+}
diff --git a/core/modules/simpletest/tests/src/Functional/BrowserTestBaseTest.php b/core/modules/simpletest/tests/src/Functional/BrowserTestBaseTest.php
new file mode 100644
index 0000000..644dea3
--- /dev/null
+++ b/core/modules/simpletest/tests/src/Functional/BrowserTestBaseTest.php
@@ -0,0 +1,62 @@
+<?php
+
+/**
+ * @file
+ * Definition of \Drupal\Tests\simpletest\Functional\BrowserTestBaseTest.
+ */
+
+namespace Drupal\Tests\simpletest\Functional;
+
+use Drupal\simpletest\BrowserTestBase;
+
+/**
+ * Tests BrowserTestBase functionality.
+ *
+ * @group simpletest
+ * @group modern
+ */
+class BrowserTestBaseTest extends BrowserTestBase {
+
+  /**
+   * Modules to enable.
+   *
+   * @var array
+   */
+  public static $modules = array('simpletest_example');
+
+  /**
+   * Tests basic page test.
+   */
+  public function testGoTo() {
+    $account = $this->drupalCreateUser(array('simpletest_example access tests'));
+    $this->drupalLogin($account);
+
+    // Visit a Drupal page that requires login.
+    $this->drupalGet('/simpletest/hello');
+    $this->assertSession()->statusCodeEquals(200);
+
+    // Test page contains some text.
+    $this->assertSession()->pageTextContains('Hello Drupal');
+  }
+
+  /**
+   * Tests basic form functionality.
+   */
+  public function testForm() {
+
+    // Ensure the proper response code for a _form route.
+    $this->drupalGet('simpletest/example-form');
+    $this->assertSession()->statusCodeEquals(200);
+
+    // Ensure the form and text field exist.
+    $this->assertSession()->elementExists('css', 'form#simpletest-test-example-form');
+    $this->assertSession()->fieldExists('name');
+
+    $edit = ['name' => 'Foobaz'];
+    $this->submitForm($edit, 'Save configuration', 'simpletest-test-example-form');
+
+    $this->drupalGet('/simpletest/example-form');
+    $this->assertSession()->fieldValueEquals('name', 'Foobaz');
+  }
+
+}
diff --git a/core/phpunit.xml.dist b/core/phpunit.xml.dist
index aa4acfd..78f2f0e 100644
--- a/core/phpunit.xml.dist
+++ b/core/phpunit.xml.dist
@@ -6,9 +6,10 @@
     <ini name="error_reporting" value="32767"/>
     <!-- Do not limit the amount of memory tests take to run. -->
     <ini name="memory_limit" value="-1"/>
+    <env name="SIMPLETEST_BASE_URL" value="http://localhost"/>
   </php>
   <testsuites>
-    <testsuite name="Drupal Unit Test Suite">
+    <testsuite name="unit">
       <directory>./tests</directory>
       <directory>./modules/*/tests</directory>
       <directory>../modules</directory>
@@ -20,6 +21,15 @@
       <!-- Exclude special-case files from config's test modules. -->
       <exclude>./modules/config/tests/config_test/src</exclude>
     </testsuite>
+    <testsuite name="functional">
+      <directory>./modules/*/tests/src/Functional</directory>
+      <!-- Exclude Composer's vendor directory so we don't run tests there. -->
+      <exclude>./vendor</exclude>
+      <!-- Exclude Drush tests. -->
+      <exclude>./drush/tests</exclude>
+      <!-- Exclude special-case files from config's test modules. -->
+      <exclude>./modules/config/tests/config_test/src</exclude>
+    </testsuite>
   </testsuites>
   <!-- Filter for coverage reports. -->
   <filter>
@@ -32,3 +42,4 @@
      </whitelist>
   </filter>
 </phpunit>
+
diff --git a/core/scripts/run-tests.sh b/core/scripts/run-tests.sh
index c552472..dbdce8e 100644
--- a/core/scripts/run-tests.sh
+++ b/core/scripts/run-tests.sh
@@ -337,6 +337,17 @@ function simpletest_script_init() {
     }
   }
 
+  if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] === 'on') {
+    $base_url = 'https://';
+  }
+  else {
+    $base_url = 'http://';
+  }
+  $base_url .= $host;
+  if ($path !== '') {
+    $base_url .= $path;
+  }
+  putenv('SIMPLETEST_BASE_URL=' . $base_url);
   $_SERVER['HTTP_HOST'] = $host;
   $_SERVER['REMOTE_ADDR'] = '127.0.0.1';
   $_SERVER['SERVER_ADDR'] = '127.0.0.1';
