diff --git a/core/scripts/run-tests.php b/core/scripts/run-tests.php
new file mode 100644
index 0000000..7a2d6f6
--- /dev/null
+++ b/core/scripts/run-tests.php
@@ -0,0 +1,1305 @@
+<?php
+
+/**
+ * @file
+ * A test runner script for Drupal.
+ */
+
+use Composer\Autoload\ClassLoader;
+use Drupal\Component\Utility\Html;
+use Drupal\Component\Utility\Timer;
+use Drupal\Component\Uuid\Php;
+use Drupal\Core\Database\Database;
+use Drupal\Core\StreamWrapper\PublicStream;
+use Drupal\Core\Test\TestRunnerKernel;
+use Drupal\simpletest\Form\SimpletestResultsForm;
+use Drupal\simpletest\TestBase;
+use Symfony\Component\Console\Application;
+use Symfony\Component\Console\Command\Command;
+use Symfony\Component\Console\Input\InputArgument;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\HttpFoundation\Request;
+
+$autoloader = require_once __DIR__ . '/../../autoload.php';
+
+require_once __DIR__ . '/../modules/simpletest/simpletest.module';
+
+// Green.
+const SIMPLETEST_SCRIPT_COLOR_PASS = 32;
+// Red.
+const SIMPLETEST_SCRIPT_COLOR_FAIL = 31;
+// Brown.
+const SIMPLETEST_SCRIPT_COLOR_EXCEPTION = 33;
+// Restricting the chunk of queries prevents memory exhaustion.
+const SIMPLETEST_SCRIPT_SQLITE_VARIABLE_LIMIT = 350;
+// Exit status codes.
+const SIMPLETEST_SCRIPT_EXIT_SUCCESS = 0;
+const SIMPLETEST_SCRIPT_EXIT_FAILURE = 1;
+const SIMPLETEST_SCRIPT_EXIT_EXCEPTION = 2;
+
+/**
+ * Special exception used by DrupalRunTest.
+ */
+class DrupalRunTestException extends \RuntimeException {
+
+}
+
+/**
+ * Encapsulates all the configuration derived from CLI input.
+ */
+class DrupalRunTestConfig extends \ArrayObject {
+
+  /**
+   * Factory to create a config object based on input options.
+   *
+   * @param InputInterface $input
+   *   Console input object.
+   *
+   * @return static
+   *   Newly-created object.
+   */
+  public static function createFromInput(InputInterface $input) {
+    // Create a new array object with default values.
+    $config = new static([
+      'script' => '',
+      'help' => FALSE,
+      'list' => FALSE,
+      'clean' => FALSE,
+      'url' => '',
+      'sqlite' => NULL,
+      'dburl' => NULL,
+      'php' => '',
+      'concurrency' => 1,
+      'all' => FALSE,
+      'module' => NULL,
+      'class' => FALSE,
+      'file' => FALSE,
+      'directory' => NULL,
+      'color' => FALSE,
+      'verbose' => FALSE,
+      'keep-results' => FALSE,
+      'test_names' => array(),
+      'repeat' => 1,
+      'die-on-fail' => FALSE,
+      'browser' => FALSE,
+      // Used internally.
+      'test-id' => 0,
+      'execute-test' => '',
+      'xml' => '',
+      'non-html' => FALSE,
+    ]);
+    // Add in our input options.
+    $options = array_keys((array) $config);
+    foreach ($options as $option) {
+      if ($input->hasOption($option)) {
+        $config[$option] = $input->getOption($option);
+      }
+    }
+    // Gather our test_names.
+    if ($input->hasArgument('test_names')) {
+      $config['test_names'] = explode(',', $input->getArgument('test_names'));
+    }
+    // Get the script name.
+    $config['script'] = basename(__FILE__);
+    return $config;
+  }
+
+}
+
+/**
+ * Test runner console command.
+ */
+class DrupalRunTest extends Command {
+
+  protected $config;
+  protected $autoloader;
+  protected $input;
+  protected $output;
+  protected $php = '';
+  protected $container;
+  protected $test_ids = [];
+  protected $test_list;
+  protected $results_map = [
+    'pass' => 'Pass',
+    'fail' => 'Fail',
+    'exception' => 'Exception'
+  ];
+
+  /**
+   * Contructs a new object.
+   *
+   * @param \Composer\Autoload\ClassLoader $auto_loader
+   *   The class loader.
+   * @param string|null $name
+   *   (optional) The name of the command.
+   */
+  public function __construct(ClassLoader $auto_loader, $name = NULL) {
+    $this->autoloader = $auto_loader;
+    parent::__construct($name);
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function configure() {
+    $this->setName('run-tests')
+      ->setDescription('Run tests.')
+      ->addArgument(
+        'test_names', InputArgument::OPTIONAL, 'Comma-delimited list of tests to run. Can be test classes, files, or groups based on other options.'
+      )
+      ->addOption('php', NULL, InputOption::VALUE_REQUIRED, 'The absolute path to the PHP executable. Usually not needed.', '')
+      ->addOption('list', NULL, InputOption::VALUE_NONE, 'Display all available test groups.')
+      ->addOption('clean', NULL, InputOption::VALUE_NONE, 'Cleans up database tables or directories from previous, failed, tests and then exits (no tests are run).')
+      ->addOption('url', NULL, InputOption::VALUE_OPTIONAL, 'The base URL of the root directory of this Drupal checkout; e.g.: http://drupal.test/ Required unless the Drupal root directory maps exactly to: http://localhost:80/ Use a https:// URL to force all tests to be run under SSL.')
+      ->addOption('all', NULL, InputOption::VALUE_OPTIONAL, 'Run all available tests.')
+      ->addOption('module', NULL, InputOption::VALUE_OPTIONAL, "Run all tests belonging to the specified module name. (e.g., 'node')")
+      ->addOption('class', NULL, InputOption::VALUE_OPTIONAL, "Run tests identified by specific class names, instead of group names. A specific test method can be added, for example, 'Drupal\book\Tests\BookTest::testBookExport'.")
+      ->addOption('browser', NULL, InputOption::VALUE_NONE, 'Opens the results in the browser. This enforces --keep-results and if you want to also view any pages rendered in the simpletest browser you need to add --verbose to the command line.')
+      ->addOption('keep-results', NULL, InputOption::VALUE_NONE, 'Keeps detailed assertion results (in the database) after tests have completed. By default, assertion results are cleared.')
+      ->addOption('concurrency', NULL, InputOption::VALUE_REQUIRED, 'Run tests in parallel, up to [num] tests at a time.', 1)
+      ->addOption('repeat', NULL, InputOption::VALUE_REQUIRED, 'Number of times to repeat the test.', 1)
+      ->addOption('color', NULL, InputOption::VALUE_REQUIRED, 'Output text format results with color highlighting.', FALSE)
+      ->addOption('die-on-fail', NULL, InputOption::VALUE_REQUIRED, 'Exit test execution immediately upon any failed assertion. This allows to access the test site by changing settings.php to use the test database and configuration directories. Use in combination with --repeat for debugging random test failures.', FALSE)
+      ->addOption('xml', NULL, InputOption::VALUE_REQUIRED, 'If provided, test results will be written as xml files to this path. Specify a directory.', '')
+      ->addOption('non-html', NULL, InputOption::VALUE_REQUIRED, 'Removes escaping from output. Useful for reading results on the CLI.', FALSE)
+      ->addOption('dburl', NULL, InputOption::VALUE_REQUIRED, ' A URI denoting the database driver, credentials, server hostname, and database name to use in tests. Required when running tests without a Drupal installation that contains default database connection info in settings.php. Examples: mysql://username:password@localhost/databasename#table_prefix sqlite://localhost/relative/path/db.sqlite sqlite://localhost//absolute/path/db.sqlite', NULL)
+      ->addOption('execute-test', NULL, InputOption::VALUE_OPTIONAL, 'Execute a test. Used internally to add a new process.', FALSE)
+      ->addOption('test-id', NULL, InputOption::VALUE_OPTIONAL, 'Execute a test. Used internally to add a new process.')
+      ->addOption('sqlite', NULL, InputOption::VALUE_OPTIONAL, "A pathname to use for the SQLite database of the test runner. Required unless this script is executed with a working Drupalinstallation that has Simpletest module installed. A relative pathname is interpreted relative to the Drupal root directory. Note that ':memory:' cannot be used, because this script spawns sub-processes. However, you may use e.g. '/tmpfs/test.sqlite'", '')
+      ;
+  }
+
+  /**
+   * {@inheritdoc}
+   */
+  protected function execute(InputInterface $input, OutputInterface $output) {
+    try {
+      $this->input = $input;
+      $this->output = $output;
+      $this->config = DrupalRunTestConfig::createFromInput($input);
+
+      $this->validateConfig();
+      $this->simpletest_script_init();
+
+      try {
+        $request = Request::createFromGlobals();
+        $kernel = TestRunnerKernel::createFromRequest($request, $this->autoloader);
+        $kernel->prepareLegacyRequest($request);
+        $this->container = $kernel->getContainer();
+      }
+      catch (Exception $e) {
+        $this->output->writeln((string) $e);
+        return SIMPLETEST_SCRIPT_EXIT_EXCEPTION;
+      }
+
+      if ($input->getOption('execute-test')) {
+        $test_id = $this->config['test-id'];
+
+        $this->simpletest_script_setup_database($input);
+        return $this->simpletest_script_run_one_test($test_id, $this->config['execute-test']);
+      }
+
+      if ($input->getOption('list')) {
+        return $this->listTests($this->config['module']);
+      }
+
+      $this->simpletest_script_setup_database($input, TRUE);
+
+      if ($input->getOption('clean')) {
+        return $this->clean();
+      }
+
+      $this->test_list = $this->simpletest_script_get_test_list();
+
+      // Try to allocate unlimited time to run the tests.
+      \drupal_set_time_limit(0);
+      $this->simpletest_script_reporter_init();
+
+      $tests_to_run = array();
+      $input_repeat = $this->config['repeat'];
+      for ($i = 0; $i < $input_repeat; $i++) {
+        $tests_to_run = array_merge($tests_to_run, $this->test_list);
+      }
+
+      // Execute tests.
+      $status = $this->simpletest_script_execute_batch($tests_to_run);
+
+      // Stop the timer.
+      $this->simpletest_script_reporter_timer_stop();
+
+      // Display results before database is cleared.
+      if ($this->config['browser']) {
+        $this->simpletest_script_open_browser();
+      }
+      else {
+        $this->simpletest_script_reporter_display_results();
+      }
+
+      if ($this->config['xml']) {
+        $this->simpletest_script_reporter_write_xml_results();
+      }
+
+      // Clean up all test results.
+      if (!$this->config['keep-results']) {
+        try {
+          \simpletest_clean_results_table();
+        }
+        catch (Exception $e) {
+          $this->output->writeln((string) $e);
+          return SIMPLETEST_SCRIPT_EXIT_EXCEPTION;
+        }
+      }
+    }
+    catch (DrupalRunTestException $e) {
+      $this->output->writeln('exception: ' . $e->getMessage());
+      $status = $e->getCode();
+    }
+    // Test complete, exit.
+    $output->writeln('status code: ' . $status);
+    return $status;
+  }
+
+  /**
+   * Validate the configuration.
+   *
+   * @throws DrupalRunTestException
+   */
+  protected function validateConfig() {
+    // Validate the concurrency argument.
+    $concurrency = $this->config['concurrency'];
+    if (!is_numeric($concurrency) || $concurrency <= 0) {
+      $this->simpletest_script_print_error("--concurrency must be a strictly positive integer.");
+      throw new DrupalRunTestException('', SIMPLETEST_SCRIPT_EXIT_FAILURE);
+    }
+    // Force keep-results if browser is set.
+    if ($this->config['browser']) {
+      $this->config['keep-results'] = TRUE;
+    }
+  }
+
+  /**
+   * Supports the --list option.
+   *
+   * @param string $module
+   *
+   * @return int
+   *
+   * @throws DrupalRunTestException
+   */
+  protected function listTests($module = NULL) {
+    // Display all available tests.
+    $this->output->writeln('Available test groups & classes');
+    $this->output->writeln('-------------------------------');
+    $this->output->writeln('');
+    try {
+      $groups = \simpletest_test_get_all($module);
+    }
+    catch (Exception $e) {
+      $this->output->writeln((string) $e);
+      throw new DrupalRunTestException('', SIMPLETEST_SCRIPT_EXIT_EXCEPTION);
+    }
+    foreach ($groups as $group => $tests) {
+      $this->output->writeln($group);
+      foreach ($tests as $class => $info) {
+        $this->output->writeln(" - $class");
+      }
+    }
+    return SIMPLETEST_SCRIPT_EXIT_SUCCESS;
+  }
+
+  /**
+   * Support the --clean option.
+   *
+   * @return int
+   *   Exit code.
+   */
+  protected function clean() {
+    // Clean up left-over tables and directories.
+    try {
+      \simpletest_clean_environment();
+    }
+    catch (Exception $e) {
+      $this->output->writeln((string) $e);
+      return SIMPLETEST_SCRIPT_EXIT_EXCEPTION;
+    }
+    $this->output->writeln('');
+    $this->output->writeln('Environment cleaned.');
+
+    // Get the status messages and print them.
+    $messages = drupal_get_messages('status');
+    foreach ($messages['status'] as $text) {
+      $this->output->writeln(" - " . $text);
+    }
+    return SIMPLETEST_SCRIPT_EXIT_SUCCESS;
+  }
+
+  /**
+   * Sets up database connection info for running tests.
+   *
+   * If this script is executed from within a real Drupal installation, then
+   * this function essentially performs nothing (unless the --sqlite or --dburl
+   * parameters were passed).
+   *
+   * Otherwise, there are three database connections of concern:
+   * - --sqlite: The test runner connection, providing access to Simpletest
+   *   database tables for recording test IDs and assertion results.
+   * - --dburl: A database connection that is used as base connection info for
+   *   all tests; i.e., every test will spawn from this connection. In case this
+   *   connection uses e.g. SQLite, then all tests will run against SQLite. This
+   *   is exposed as $databases['default']['default'] to Drupal.
+   * - The actual database connection used within a test. This is the same as
+   *   --dburl, but uses an additional database table prefix. This is
+   *   $databases['default']['default'] within a test environment. The original
+   *   connection is retained in
+   *   $databases['simpletest_original_default']['default'] and restored after
+   *   each test.
+   *
+   * @param bool $new
+   *   Whether this process is a run-tests.sh master process. If TRUE, the
+   *   SQLite database file specified by --sqlite (if any) is set up. Otherwise,
+   *   database connections are prepared only.
+   */
+  protected function simpletest_script_setup_database($new = FALSE) {
+
+    // If there is an existing Drupal installation that contains a database
+    // connection info in settings.php, then $databases['default']['default']
+    // will hold the default database connection already. This connection is
+    // assumed to be valid, and this connection will be used in tests, so that
+    // they run against e.g. MySQL instead of SQLite.
+    // However, in case no Drupal installation exists, this default database
+    // connection can be set and/or overridden with the --dburl parameter.
+    $input_dburl = $this->config['dburl'];
+    if ($input_dburl) {
+      // Remove a possibly existing default connection (from settings.php).
+      Database::removeConnection('default');
+      try {
+        $databases['default']['default'] = Database::convertDbUrlToConnectionInfo($input_dburl, DRUPAL_ROOT);
+      }
+      catch (\InvalidArgumentException $e) {
+        $this->simpletest_script_print_error('Invalid --dburl. Reason: ' . $e->getMessage());
+        throw new DrupalRunTestException('Invalid --dburl. Reason: ' . $e->getMessage(), SIMPLETEST_SCRIPT_EXIT_FAILURE);
+      }
+    }
+    // Otherwise, try to use the default database connection from settings.php.
+    else {
+      $databases['default'] = Database::getConnectionInfo('default');
+    }
+
+    // If there is no default database connection for tests, we cannot continue.
+    if (!isset($databases['default']['default'])) {
+      $this->simpletest_script_print_error('Missing default database connection for tests. Use --dburl to specify one.');
+      throw new DrupalRunTestException('', SIMPLETEST_SCRIPT_EXIT_FAILURE);
+    }
+    Database::addConnectionInfo('default', 'default', $databases['default']['default']);
+
+    // If no --sqlite parameter has been passed, then Simpletest module is
+    // assumed to be installed, so the test runner database connection is the
+    // default database connection.
+    if (!$this->input->hasArgument('sqlite')) {
+      $sqlite = FALSE;
+      $databases['test-runner']['default'] = $databases['default']['default'];
+    }
+    // Otherwise, set up a SQLite connection for the test runner.
+    else {
+      $input_sqlite = $this->input->getArgument('sqlite');
+      if ($input_sqlite[0] === '/') {
+        $sqlite = $input_sqlite;
+      }
+      else {
+        $sqlite = DRUPAL_ROOT . '/' . $input_sqlite;
+      }
+      $databases['test-runner']['default'] = array(
+        'driver' => 'sqlite',
+        'database' => $sqlite,
+        'prefix' => array(
+          'default' => '',
+        ),
+      );
+      // Create the test runner SQLite database, unless it exists already.
+      if ($new && !file_exists($sqlite)) {
+        if (!is_dir(dirname($sqlite))) {
+          mkdir(dirname($sqlite));
+        }
+        touch($sqlite);
+      }
+    }
+
+    // Add the test runner database connection.
+    Database::addConnectionInfo('test-runner', 'default', $databases['test-runner']['default']);
+
+    // Create the Simpletest schema.
+    try {
+      $schema = Database::getConnection('default', 'test-runner')->schema();
+    }
+    catch (\PDOException $e) {
+      $this->simpletest_script_print_error($databases['test-runner']['default']['driver'] . ': ' . $e->getMessage());
+      throw new DrupalRunTestException('', SIMPLETEST_SCRIPT_EXIT_FAILURE);
+    }
+    if ($new && $sqlite) {
+      require_once DRUPAL_ROOT . '/' . drupal_get_path('module', 'simpletest') . '/simpletest.install';
+      foreach ($this->simpletest_schema() as $name => $table_spec) {
+        try {
+          if ($schema->tableExists($name)) {
+            $schema->dropTable($name);
+          }
+          $schema->createTable($name, $table_spec);
+        }
+        catch (Exception $e) {
+          $this->output->writeln((string) $e);
+          throw new DrupalRunTestException('', SIMPLETEST_SCRIPT_EXIT_EXCEPTION);
+        }
+      }
+    }
+    // Verify that the Simpletest database schema exists by checking one table.
+    try {
+      if (!$schema->tableExists('simpletest')) {
+        $this->simpletest_script_print_error('Missing Simpletest database schema. Either install Simpletest module or use the --sqlite parameter.');
+        throw new DrupalRunTestException('', SIMPLETEST_SCRIPT_EXIT_FAILURE);
+      }
+    }
+    catch (Exception $e) {
+      $this->output->writeln((string) $e);
+      throw new DrupalRunTestException('', SIMPLETEST_SCRIPT_EXIT_EXCEPTION);
+    }
+  }
+
+  /**
+   * Print error messages in appropriately alarming colors.
+   *
+   * Print error message prefixed with "  ERROR: " and displayed in fail color
+   * if color output is enabled.
+   *
+   * @param string $message
+   *   The message to print.
+   */
+  protected function simpletest_script_print_error($message) {
+    $this->simpletest_script_print("  ERROR: $message\n", SIMPLETEST_SCRIPT_COLOR_FAIL);
+  }
+
+  /**
+   * Print a message to the console.
+   *
+   * If color is enabled then the specified color code will be used.
+   *
+   * @param string $message
+   *   The message to print.
+   * @param int $color_code
+   *   The color code to use for coloring.
+   */
+  protected function simpletest_script_print($message, $color_code) {
+    // @todo: make the color part work.
+    /*    if ($in->hasArgument('color')) {
+
+      echo "\033[" . $color_code . "m" . $message . "\033[0m";
+      }
+      else { */
+    $this->output->writeln($message);
+  }
+
+  /**
+   * Bootstrap Drupal and run a single test.
+   */
+  protected function simpletest_script_run_one_test($test_id, $test_class) {
+    try {
+      if (strpos($test_class, '::') > 0) {
+        list($class_name, $method) = explode('::', $test_class, 2);
+        $methods = [$method];
+      }
+      else {
+        $class_name = $test_class;
+        // Use empty array to run all the test methods.
+        $methods = array();
+      }
+      $test = new $class_name($test_id);
+      if (is_subclass_of($test_class, '\PHPUnit_Framework_TestCase')) {
+        $status = $this->simpletest_script_run_phpunit($test_id, $test_class);
+      }
+      else {
+        $test->dieOnFail = (bool) $this->config['die-on-fail'];
+        $test->verbose = (bool) $this->config['verbose'];
+        $test->run($methods);
+        $this->simpletest_script_reporter_display_summary($test_class, $test->results);
+
+        $status = SIMPLETEST_SCRIPT_EXIT_SUCCESS;
+        // Finished, kill this runner.
+        if ($test->results['#fail'] || $test->results['#exception']) {
+          $status = SIMPLETEST_SCRIPT_EXIT_FAILURE;
+        }
+      }
+      return $status;
+    }
+    // DrupalTestCase::run() catches exceptions already, so this is only reached
+    // when an exception is thrown in the wrapping test runner environment.
+    catch (Exception $e) {
+      $this->output->writeln((string) $e);
+      throw new DrupalRunTestException('', SIMPLETEST_SCRIPT_EXIT_EXCEPTION);
+    }
+  }
+
+  /**
+   * Get list of tests based on options.
+   *
+   * If --all is specified then return all available tests, otherwise read list
+   * of tests.
+   *
+   * Will print error and exit if no valid tests were found.
+   *
+   * @return string[]
+   *   List of tests.
+   */
+  protected function simpletest_script_get_test_list() {
+
+    $this->test_list = array();
+    if ($this->input->hasOption('all') || $this->input->hasOption('module')) {
+      try {
+        $groups = \simpletest_test_get_all($this->config['module']);
+      }
+      catch (Exception $e) {
+        $this->output->writeln((string) $e);
+        throw new DrupalRunTestException('', SIMPLETEST_SCRIPT_EXIT_EXCEPTION);
+      }
+      $all_tests = array();
+      foreach ($groups as $group => $tests) {
+        $all_tests = array_merge($all_tests, array_keys($tests));
+      }
+      $this->test_list = $all_tests;
+    }
+    else {
+      if ($this->config['class']) {
+        $this->test_list = array();
+        foreach ($this->config['test_names'] as $test_class) {
+          list($class_name,) = explode('::', $test_class, 2);
+          if (class_exists($class_name)) {
+            $this->test_list[] = $test_class;
+          }
+          else {
+            try {
+              $groups = \simpletest_test_get_all();
+            }
+            catch (Exception $e) {
+              $this->output->writeln((string) $e);
+              throw new DrupalRunTestException('', SIMPLETEST_SCRIPT_EXIT_EXCEPTION);
+            }
+            $all_classes = array();
+            foreach ($groups as $group) {
+              $all_classes = array_merge($all_classes, array_keys($group));
+            }
+            $this->simpletest_script_print_error('Test class not found: ' . $class_name);
+            $this->simpletest_script_print_alternatives($class_name, $all_classes, 6);
+            throw new DrupalRunTestException('', SIMPLETEST_SCRIPT_EXIT_FAILURE);
+          }
+        }
+      }
+      elseif ($this->config['file']) {
+        // Extract test case class names from specified files.
+        foreach ($this->config['test_names'] as $file) {
+          if (!file_exists($file)) {
+            $this->simpletest_script_print_error('File not found: ' . $file);
+            throw new DrupalRunTestException('', SIMPLETEST_SCRIPT_EXIT_FAILURE);
+          }
+          $content = file_get_contents($file);
+          // Extract a potential namespace.
+          $namespace = FALSE;
+          if (preg_match('@^namespace ([^ ;]+)@m', $content, $matches)) {
+            $namespace = $matches[1];
+          }
+          // Extract all class names.
+          // Abstract classes are excluded on purpose.
+          preg_match_all('@^class ([^ ]+)@m', $content, $matches);
+          if (!$namespace) {
+            $this->test_list = array_merge($this->test_list, $matches[1]);
+          }
+          else {
+            foreach ($matches[1] as $class_name) {
+              $namespace_class = $namespace . '\\' . $class_name;
+              if (is_subclass_of($namespace_class, '\Drupal\simpletest\TestBase') || is_subclass_of($namespace_class, '\PHPUnit_Framework_TestCase')) {
+                $this->test_list[] = $namespace_class;
+              }
+            }
+          }
+        }
+      }
+      elseif ($this->config['directory']) {
+        // Extract test case class names from specified directory.
+        // Find all tests in the PSR-X structure; Drupal\$extension\Tests\*.php
+        // Since we do not want to hard-code too many structural file/directory
+        // assumptions about PSR-0/4 files and directories, we check for the
+        // minimal conditions only; i.e., a '*.php' file that has '/Tests/' in
+        // its path.
+        // Ignore anything from third party vendors.
+        $ignore = array('.', '..', 'vendor');
+        $files = [];
+        $input_directory = $this->config['directory'];
+        if ($input_directory[0] === '/') {
+          $directory = $input_directory;
+        }
+        else {
+          $directory = DRUPAL_ROOT . "/" . $input_directory;
+        }
+        foreach (file_scan_directory($directory, '/\.php$/', $ignore) as $file) {
+          // '/Tests/' can be contained anywhere in the file's path (there can
+          // be sub-directories below /Tests), but must be contained literally.
+          // Case-insensitive to match all Simpletest and PHPUnit tests:
+          //   ./lib/Drupal/foo/Tests/Bar/Baz.php
+          //   ./foo/src/Tests/Bar/Baz.php
+          //   ./foo/tests/Drupal/foo/Tests/FooTest.php
+          //   ./foo/tests/src/FooTest.php
+          // $file->filename doesn't give us a directory, so we use $file->uri
+          // Strip the drupal root directory and trailing slash off the URI.
+          $filename = substr($file->uri, strlen(DRUPAL_ROOT) + 1);
+          if (stripos($filename, '/Tests/')) {
+            $files[$filename] = $filename;
+          }
+        }
+        foreach ($files as $file) {
+          $content = file_get_contents($file);
+          // Extract a potential namespace.
+          $namespace = FALSE;
+          if (preg_match('@^namespace ([^ ;]+)@m', $content, $matches)) {
+            $namespace = $matches[1];
+          }
+          // Extract all class names.
+          // Abstract classes are excluded on purpose.
+          preg_match_all('@^class ([^ ]+)@m', $content, $matches);
+          if (!$namespace) {
+            $this->test_list = array_merge($this->test_list, $matches[1]);
+          }
+          else {
+            foreach ($matches[1] as $class_name) {
+              $namespace_class = $namespace . '\\' . $class_name;
+              if (is_subclass_of($namespace_class, '\Drupal\simpletest\TestBase') || is_subclass_of($namespace_class, '\PHPUnit_Framework_TestCase')) {
+                $this->test_list[] = $namespace_class;
+              }
+            }
+          }
+        }
+      }
+      else {
+        try {
+          $groups = \simpletest_test_get_all();
+        }
+        catch (Exception $e) {
+          $this->output->writeln((string) $e);
+          throw new DrupalRunTestException('', SIMPLETEST_SCRIPT_EXIT_EXCEPTION);
+        }
+        foreach ($this->config['test_names'] as $group_name) {
+          if (isset($groups[$group_name])) {
+            $this->test_list = array_merge($this->test_list, array_keys($groups[$group_name]));
+          }
+          else {
+            $this->simpletest_script_print_error('Test group not found: ' . $group_name);
+            $this->simpletest_script_print_alternatives($group_name, array_keys($groups));
+            throw new DrupalRunTestException('', SIMPLETEST_SCRIPT_EXIT_FAILURE);
+          }
+        }
+      }
+    }
+
+    if (empty($this->test_list)) {
+      $this->simpletest_script_print_error('No valid tests were specified.');
+      throw new DrupalRunTestException('', SIMPLETEST_SCRIPT_EXIT_FAILURE);
+    }
+    return $this->test_list;
+  }
+
+  /**
+   * Initialize the reporter.
+   */
+  protected function simpletest_script_reporter_init() {
+    $this->output->writeln('');
+    $this->output->writeln("Drupal test run");
+    $this->output->writeln("---------------");
+    $this->output->writeln('');
+
+    // Tell the user about what tests are to be run.
+    if ($this->config['all']) {
+      $this->output->writeln("All tests will run.");
+      $this->output->writeln('');
+    }
+    else {
+      $this->output->writeln("Tests to be run:");
+      foreach ($this->test_list as $class_name) {
+        $this->output->writeln("  - $class_name");
+      }
+      $this->output->writeln('');
+    }
+
+    $this->output->writeln("Test run started:");
+    // @todo: $_SERVER? Really? :-)
+    $this->output->writeln("  " . date('l, F j, Y - H:i', $_SERVER['REQUEST_TIME']));
+    Timer::start('run-tests');
+    $this->output->writeln('');
+
+    $this->output->writeln("Test summary");
+    $this->output->writeln("------------");
+    $this->output->writeln('');
+  }
+
+  /**
+   * Execute a batch of tests.
+   */
+  function simpletest_script_execute_batch($test_classes) {
+    $total_status = SIMPLETEST_SCRIPT_EXIT_SUCCESS;
+
+    // Multi-process execution.
+    $children = array();
+    while (!empty($test_classes) || !empty($children)) {
+      while (count($children) < $this->config['concurrency']) {
+        if (empty($test_classes)) {
+          break;
+        }
+
+        try {
+          $test_id = Database::getConnection('default', 'test-runner')
+            ->insert('simpletest_test_id')
+            ->useDefaults(array('test_id'))
+            ->execute();
+        }
+        catch (\Exception $e) {
+          $this->output->writeln((string) $e);
+          throw new DrupalRunTestException((string) $e, SIMPLETEST_SCRIPT_EXIT_EXCEPTION);
+        }
+        $this->test_ids[] = $test_id;
+
+        $test_class = array_shift($test_classes);
+        // Fork a child process.
+        $command = $this->simpletest_script_command($test_id, $test_class);
+        $process = proc_open($command, array(), $pipes, NULL, NULL, array('bypass_shell' => TRUE));
+
+        if (!is_resource($process)) {
+          $this->output->writeln("Unable to fork test process. Aborting.");
+          return SIMPLETEST_SCRIPT_EXIT_SUCCESS;
+        }
+
+        // Register our new child.
+        $children[] = array(
+          'process' => $process,
+          'test_id' => $test_id,
+          'class' => $test_class,
+          'pipes' => $pipes,
+        );
+      }
+
+      // Wait for children every 200ms.
+      usleep(200000);
+
+      // Check if some children finished.
+      foreach ($children as $cid => $child) {
+        $status = proc_get_status($child['process']);
+        if (empty($status['running'])) {
+          // The child exited, unregister it.
+          proc_close($child['process']);
+          if ($status['exitcode'] === SIMPLETEST_SCRIPT_EXIT_FAILURE) {
+            $total_status = max($status['exitcode'], $total_status);
+          }
+          elseif ($status['exitcode']) {
+            $message = 'FATAL ' . $child['class'] . ': test runner returned a non-zero error code (' . $status['exitcode'] . ').';
+            $this->output->writeln($message);
+            // Insert a fail for xml results.
+            TestBase::insertAssert($child['test_id'], $child['class'], FALSE, $message, 'run-tests.php check');
+            // Ensure that an error line is displayed for the class.
+            $this->simpletest_script_reporter_display_summary($child['class'], ['#pass' => 0, '#fail' => 1, '#exception' => 0, '#debug' => 0]);
+            if ($this->config['die-on-fail']) {
+              list($db_prefix,) = \simpletest_last_test_get($child['test_id']);
+              $test_directory = 'sites/simpletest/' . substr($db_prefix, 10);
+              $this->output->writeln('Simpletest database and files kept and test exited immediately on fail so should be reproducible if you change settings.php to use the database prefix ' . $db_prefix . ' and config directories in ' . $test_directory);
+              $this->config['keep-results'] = TRUE;
+              // Exit repeat loop immediately.
+              $this->config['repeat'] = -1;
+            }
+          }
+          // Free-up space by removing any potentially created resources.
+          if (!$this->config['keep-results']) {
+            $this->simpletest_script_cleanup($child['test_id'], $child['class'], $status['exitcode']);
+          }
+
+          // Remove this child.
+          unset($children[$cid]);
+        }
+      }
+    }
+    return $total_status;
+  }
+
+  /**
+   * Stop the test timer.
+   */
+  protected function simpletest_script_reporter_timer_stop() {
+    $this->output->writeln('');
+    $end = Timer::stop('run-tests');
+    $this->output->writeln("Test run duration: " . $this->container->get('date.formatter')->formatInterval($end['time'] / 1000));
+    $this->output->writeln('');
+  }
+
+  /**
+   * Display test results.
+   */
+  protected function simpletest_script_reporter_display_results() {
+    if ($this->config['verbose']) {
+      // Report results.
+      $this->output->writeln("Detailed test results");
+      $this->output->writeln("---------------------");
+
+      try {
+        $results = $this->simpletest_script_load_messages_by_test_id($this->test_ids);
+      }
+      catch (Exception $e) {
+        $this->output->writeln((string) $e);
+        throw new DrupalRunTestException('', SIMPLETEST_SCRIPT_EXIT_EXCEPTION);
+      }
+      $test_class = '';
+      foreach ($results as $result) {
+        if (isset($this->results_map[$result->status])) {
+          if ($result->test_class != $test_class) {
+            // Display test class every time results are for new test class.
+            $this->output->writeln('');
+            $this->output->writeln('');
+            $this->output->writeln("---- $result->test_class ----");
+            $this->output->writeln('');
+            $this->output->writeln('');
+            $test_class = $result->test_class;
+
+            // Print table header.
+            $this->output->writeln("Status    Group      Filename          Line Function                            ");
+            $this->output->writeln("--------------------------------------------------------------------------------");
+          }
+
+          $this->simpletest_script_format_result($result);
+        }
+      }
+    }
+  }
+
+  /**
+   * Return a command used to run a test in a separate process.
+   *
+   * @param int $test_id
+   *   The current test ID.
+   * @param string $test_class
+   *   The name of the test class to run.
+   */
+  protected function simpletest_script_command($test_id, $test_class) {
+    $command = escapeshellarg($this->php) . ' ' . escapeshellarg('./core/scripts/' . $this->config['script']);
+    $command .= ' run-tests';
+    $command .= ' --url ' . escapeshellarg($this->config['url']);
+    if (!empty($this->config['sqlite'])) {
+      $command .= ' --sqlite ' . escapeshellarg($this->config['sqlite']);
+    }
+    if (!empty($this->config['dburl'])) {
+      $command .= ' --dburl ' . escapeshellarg($this->config['dburl']);
+    }
+    $command .= ' --php ' . escapeshellarg($this->php);
+    $command .= " --test-id $test_id";
+    foreach (array('verbose', 'keep-results', 'color', 'die-on-fail') as $arg) {
+      if ($this->config[$arg]) {
+        $command .= ' --' . $arg;
+      }
+    }
+    // --execute-test and class name needs to come last.
+    $command .= ' --execute-test ' . escapeshellarg($test_class);
+    return $command;
+  }
+
+  /**
+   * Displays the assertion result summary for a single test class.
+   *
+   * @param string $class
+   *   The test class name that was run.
+   * @param string[] $results
+   *   The assertion results using #pass, #fail, #exception, #debug array keys.
+   */
+  protected function simpletest_script_reporter_display_summary($class, array $results) {
+    // Output all test results vertically aligned.
+    // Cut off the class name after 60 chars, and pad each group with 3 digits
+    // by default (more than 999 assertions are rare).
+    $output = vsprintf('%-60.60s %10s %9s %14s %12s', array(
+      $class,
+      $results['#pass'] . ' passes',
+      !$results['#fail'] ? '' : $results['#fail'] . ' fails',
+      !$results['#exception'] ? '' : $results['#exception'] . ' exceptions',
+      !$results['#debug'] ? '' : $results['#debug'] . ' messages',
+    ));
+
+    $status = ($results['#fail'] || $results['#exception'] ? 'fail' : 'pass');
+    $this->simpletest_script_print($output, $this->simpletest_script_color_code($status));
+  }
+
+  /**
+   * Get the color code associated with the specified status.
+   *
+   * @param string $status
+   *   The status string to get code for.
+   *
+   * @return int
+   *   Color code.
+   */
+  protected function simpletest_script_color_code($status) {
+    switch ($status) {
+      case 'pass':
+        return SIMPLETEST_SCRIPT_COLOR_PASS;
+
+      case 'fail':
+        return SIMPLETEST_SCRIPT_COLOR_FAIL;
+
+      case 'exception':
+        return SIMPLETEST_SCRIPT_COLOR_EXCEPTION;
+    }
+    // Default formatting.
+    return 0;
+  }
+
+  /**
+   * Removes all remnants of a test runner.
+   *
+   * In case a (e.g., fatal) error occurs after the test site has been fully
+   * setup and the error happens in many tests, the environment that executes
+   * the tests can easily run out of memory or disk space. This function ensures
+   * that all created resources are properly cleaned up after every executed
+   * test.
+   *
+   * This clean-up only exists in this script, since SimpleTest module itself
+   * does not use isolated sub-processes for each test being run, so a fatal
+   * error halts not only the test, but also the test runner (i.e., the parent
+   * site).
+   *
+   * @param int $test_id
+   *   The test ID of the test run.
+   * @param string $test_class
+   *   The class name of the test run.
+   * @param int $exitcode
+   *   The exit code of the test runner.
+   *
+   * @see simpletest_script_run_one_test()
+   */
+  protected function simpletest_script_cleanup($test_id, $test_class, $exitcode) {
+    if (is_subclass_of($test_class, '\PHPUnit_Framework_TestCase')) {
+      // PHPUnit test, move on.
+      return;
+    }
+    // Retrieve the last database prefix used for testing.
+    try {
+      list($db_prefix,) = \simpletest_last_test_get($test_id);
+    }
+    catch (Exception $e) {
+      $this->output->writeln((string) $e);
+      throw new DrupalRunTestException('', SIMPLETEST_SCRIPT_EXIT_EXCEPTION);
+    }
+
+    // If no database prefix was found, then the test was not set up correctly.
+    if (empty($db_prefix)) {
+      $this->output->writeln('');
+      $this->output->writeln("FATAL $test_class: Found no database prefix for test ID $test_id. (Check whether setUp() is invoked correctly.)");
+      return;
+    }
+
+    // Do not output verbose cleanup messages in case of a positive exitcode.
+    $output = !empty($exitcode);
+    $messages = array();
+
+    $messages[] = "- Found database prefix '$db_prefix' for test ID $test_id.";
+
+    // Read the log file in case any fatal errors caused the test to crash.
+    try {
+      \simpletest_log_read($test_id, $db_prefix, $test_class);
+    }
+    catch (Exception $e) {
+      $this->output->writeln((string) $e);
+      throw new DrupalRunTestException('', SIMPLETEST_SCRIPT_EXIT_EXCEPTION);
+    }
+
+    // Check whether a test site directory was setup already.
+    // @see \Drupal\simpletest\TestBase::prepareEnvironment()
+    $test_directory = DRUPAL_ROOT . '/sites/simpletest/' . substr($db_prefix, 10);
+    if (is_dir($test_directory)) {
+      // Output the error_log.
+      if (is_file($test_directory . '/error.log')) {
+        if ($errors = file_get_contents($test_directory . '/error.log')) {
+          $output = TRUE;
+          $messages[] = $errors;
+        }
+      }
+      // Delete the test site directory.
+      // simpletest_clean_temporary_directories() cannot be used here, since it
+      // would also delete file directories of other tests that are potentially
+      // running concurrently.
+      file_unmanaged_delete_recursive($test_directory, array('Drupal\simpletest\TestBase', 'filePreDeleteCallback'));
+      $messages[] = "- Removed test site directory.";
+    }
+
+    // Clear out all database tables from the test.
+    try {
+      $schema = Database::getConnection('default', 'default')->schema();
+      $count = 0;
+      foreach ($schema->findTables($db_prefix . '%') as $table) {
+        $schema->dropTable($table);
+        $count++;
+      }
+    }
+    catch (Exception $e) {
+      $this->output->writeln((string) $e);
+      throw new DrupalRunTestException('', SIMPLETEST_SCRIPT_EXIT_EXCEPTION);
+    }
+
+    if ($count) {
+      $messages[] = "- Removed $count leftover tables.";
+    }
+
+    if ($output) {
+      $this->output->write($messages, TRUE);
+      $this->output->writeln('');
+    }
+  }
+
+  /**
+   * Format the result to fit the default 80 character terminal size.
+   *
+   * @param $result
+   *   The result object to format.
+   */
+  protected function simpletest_script_format_result($result) {
+
+    $summary = sprintf("%-9.9s %-10.10s %-17.17s %4.4s %-35.35s\n", $this->results_map[$result->status], $result->message_group, basename($result->file), $result->line, $result->function);
+
+    $this->simpletest_script_print($summary, $this->simpletest_script_color_code($result->status));
+
+    $message = trim(strip_tags($result->message));
+    if ($this->config['non-html']) {
+      $message = Html::decodeEntities($message, ENT_QUOTES, 'UTF-8');
+    }
+    $lines = explode("\n", wordwrap($message), 76);
+    foreach ($lines as $line) {
+      $this->output->writeln("    $line");
+    }
+  }
+
+  /**
+   * Initialize script variables and perform general setup requirements.
+   */
+  protected function simpletest_script_init() {
+    $host = 'localhost';
+    $path = '';
+    $port = '80';
+
+    // Determine location of php command automatically, unless a command line
+    // argument is supplied.
+    if (!empty($this->config['php'])) {
+      $this->php = $this->config['php'];
+    }
+    elseif ($php_env = getenv('_')) {
+      // '_' is an environment variable set by the shell. It contains the
+      // command that was executed.
+      $this->php = $php_env;
+    }
+    elseif ($sudo = getenv('SUDO_COMMAND')) {
+      // 'SUDO_COMMAND' is an environment variable set by the sudo program.
+      // Extract only the PHP interpreter, not the rest of the command.
+      list($this->php,) = explode(' ', $sudo, 2);
+    }
+    else {
+      $this->simpletest_script_print_error('Unable to automatically determine the path to the PHP interpreter. Supply the --php command line argument.');
+      $this->simpletest_script_help();
+      throw new DrupalRunTestException(SIMPLETEST_SCRIPT_EXIT_FAILURE);
+    }
+
+    // Get URL from arguments.
+    $input_url = $this->config['url'];
+    if (!empty($input_url)) {
+      $parsed_url = parse_url($input_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'] : $port);
+      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';
+      }
+    }
+
+    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';
+    $_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';
+
+    if (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on') {
+      // Ensure that any and all environment variables are changed to https://.
+      foreach ($_SERVER as $key => $value) {
+        $_SERVER[$key] = str_replace('http://', 'https://', $_SERVER[$key]);
+      }
+    }
+
+    // @todo: Is this needed?
+    // chdir(realpath(__DIR__ . '/../..'));
+  }
+
+  /**
+   * Prints alternative test names.
+   *
+   * Searches the provided array of string values for close matches based on the
+   * Levenshtein algorithm.
+   *
+   * @param string $string
+   *   A string to test.
+   * @param string[] $array
+   *   A list of strings to search.
+   * @param int $degree
+   *   The matching strictness. Higher values return fewer matches. A value of
+   *   4 means that the function will return strings from $array if the
+   *   candidate string in $array would be identical to $string by changing 1/4
+   *   or fewer of its characters.
+   *
+   * @see http://php.net/manual/en/function.levenshtein.php
+   */
+  protected function simpletest_script_print_alternatives($string, array $array, $degree = 4) {
+    $alternatives = array();
+    foreach ($array as $item) {
+      $lev = levenshtein($string, $item);
+      if ($lev <= strlen($item) / $degree || FALSE !== strpos($string, $item)) {
+        $alternatives[] = $item;
+      }
+    }
+    if (!empty($alternatives)) {
+      $this->simpletest_script_print("  Did you mean?\n", SIMPLETEST_SCRIPT_COLOR_FAIL);
+      foreach ($alternatives as $alternative) {
+        $this->simpletest_script_print("  - $alternative\n", SIMPLETEST_SCRIPT_COLOR_FAIL);
+      }
+    }
+  }
+
+  /**
+   * Loads the simpletest messages from the database.
+   *
+   * Messages are ordered by test class and message id.
+   *
+   * @param array $test_ids
+   *   Array of test IDs of the messages to be loaded.
+   *
+   * @return array
+   *   Array of simpletest messages from the database.
+   */
+  protected function simpletest_script_load_messages_by_test_id($test_ids) {
+    $results = array();
+
+    // Sqlite has a maximum number of variables per query. If required, the
+    // database query is split into chunks.
+    if (count($test_ids) > SIMPLETEST_SCRIPT_SQLITE_VARIABLE_LIMIT && !empty($this->config['sqlite'])) {
+      $test_id_chunks = array_chunk($test_ids, SIMPLETEST_SCRIPT_SQLITE_VARIABLE_LIMIT);
+    }
+    else {
+      $test_id_chunks = array($test_ids);
+    }
+
+    foreach ($test_id_chunks as $test_id_chunk) {
+      try {
+        $result_chunk = Database::getConnection('default', 'test-runner')
+            ->query("SELECT * FROM {simpletest} WHERE test_id IN ( :test_ids[] ) ORDER BY test_class, message_id", array(
+              ':test_ids[]' => $test_id_chunk,
+            ))->fetchAll();
+      }
+      catch (Exception $e) {
+        $this->output->writeln((string) $e);
+        throw new DrupalRunTestException(SIMPLETEST_SCRIPT_EXIT_EXCEPTION);
+      }
+      if ($result_chunk) {
+        $results = array_merge($results, $result_chunk);
+      }
+    }
+
+    return $results;
+  }
+
+  /**
+   * Display test results.
+   */
+  protected function simpletest_script_open_browser() {
+    try {
+      $connection = Database::getConnection('default', 'test-runner');
+      $results = $connection->select('simpletest')
+        ->fields('simpletest')
+        ->condition('test_id', $this->test_ids, 'IN')
+        ->orderBy('test_class')
+        ->orderBy('message_id')
+        ->execute()
+        ->fetchAll();
+    }
+    catch (Exception $e) {
+      $this->output->writeln((string) $e);
+      throw new DrupalRunTestException('', SIMPLETEST_SCRIPT_EXIT_EXCEPTION);
+    }
+
+    // Get the results form.
+    $form = array();
+    SimpletestResultsForm::addResultForm($form, $results);
+
+    // Get the assets to make the details element collapsible and theme the
+    // result form.
+    $assets = new \Drupal\Core\Asset\AttachedAssets();
+    $assets->setLibraries(['core/drupal.collapse', 'system/admin', 'simpletest/drupal.simpletest']);
+    $resolver = $this->container->get('asset.resolver');
+    list($js_assets_header, $js_assets_footer) = $resolver->getJsAssets($assets, FALSE);
+    $js_collection_renderer = $this->container->get('asset.js.collection_renderer');
+    $js_assets_header = $js_collection_renderer->render($js_assets_header);
+    $js_assets_footer = $js_collection_renderer->render($js_assets_footer);
+    $css_assets = $this->container->get('asset.css.collection_renderer')->render($resolver->getCssAssets($assets, FALSE));
+
+    // Make the html page to write to disk.
+    $render_service = $this->container->get('renderer');
+    $html = '<head>' . $render_service->renderPlain($js_assets_header) . $render_service->renderPlain($css_assets) . '</head><body>' . $render_service->renderPlain($form) . $render_service->renderPlain($js_assets_footer) . '</body>';
+
+    // Ensure we have assets verbose directory - tests with no verbose output
+    // will not have created one.
+    $directory = PublicStream::basePath() . '/simpletest/verbose';
+    file_prepare_directory($directory, FILE_CREATE_DIRECTORY | FILE_MODIFY_PERMISSIONS);
+    $uuid = new Php();
+    $filename = $directory . '/results-' . $uuid->generate() . '.html';
+    file_put_contents($filename, $html);
+
+    // See if we can find an OS helper to open URLs in default browser.
+    $browser = FALSE;
+    if (shell_exec('which xdg-open')) {
+      $browser = 'xdg-open';
+    }
+    elseif (shell_exec('which open')) {
+      $browser = 'open';
+    }
+    elseif (substr(PHP_OS, 0, 3) == 'WIN') {
+      $browser = 'start';
+    }
+
+    if ($browser) {
+      shell_exec($browser . ' ' . escapeshellarg($filename));
+    }
+    else {
+      // Can't find assets valid browser.
+      print 'Open file://' . realpath($filename) . ' in your browser to see the verbose output.';
+    }
+  }
+
+}
+
+$app = new Application('Drupal', '8.0.x');
+$app->add(new DrupalRunTest($autoloader));
+$app->run();
