diff --git a/core/lib/Drupal/Core/Batch/BatchStorage.php b/core/lib/Drupal/Core/Batch/BatchStorage.php
index 5742fd2..0454537 100644
--- a/core/lib/Drupal/Core/Batch/BatchStorage.php
+++ b/core/lib/Drupal/Core/Batch/BatchStorage.php
@@ -8,12 +8,18 @@
 namespace Drupal\Core\Batch;
 
 use Drupal\Core\Database\Connection;
+use Drupal\Core\Database\SchemaObjectExistsException;
 use Symfony\Component\HttpFoundation\Session\SessionInterface;
 use Drupal\Core\Access\CsrfTokenGenerator;
 
 class BatchStorage implements BatchStorageInterface {
 
   /**
+   * The database table name.
+   */
+  const TABLE_NAME = 'batch';
+
+  /**
    * The database connection.
    *
    * @var \Drupal\Core\Database\Connection
@@ -56,10 +62,17 @@ public function __construct(Connection $connection, SessionInterface $session, C
   public function load($id) {
     // Ensure that a session is started before using the CSRF token generator.
     $this->session->start();
-    $batch = $this->connection->query("SELECT batch FROM {batch} WHERE bid = :bid AND token = :token", array(
-      ':bid' => $id,
-      ':token' => $this->csrfToken->get($id),
-    ))->fetchField();
+    try {
+      $batch = $this->connection->query("SELECT batch FROM {batch} WHERE bid = :bid AND token = :token", array(
+        ':bid' => $id,
+        ':token' => $this->csrfToken->get($id),
+      ))->fetchField();
+    }
+    catch (\Exception $e) {
+      $this->catchException($e);
+      // If the table doesn't exist yet, there is no batch to load.
+      $batch = FALSE;
+    }
     if ($batch) {
       return unserialize($batch);
     }
@@ -70,29 +83,44 @@ public function load($id) {
    * {@inheritdoc}
    */
   public function delete($id) {
-    $this->connection->delete('batch')
-      ->condition('bid', $id)
-      ->execute();
+    try {
+      $this->connection->delete('batch')
+        ->condition('bid', $id)
+        ->execute();
+    }
+    catch (\Exception $e) {
+      $this->catchException($e);
+    }
   }
 
   /**
    * {@inheritdoc}
    */
   public function update(array $batch) {
-    $this->connection->update('batch')
-      ->fields(array('batch' => serialize($batch)))
-      ->condition('bid', $batch['id'])
-      ->execute();
+    try {
+      $this->connection->update('batch')
+        ->fields(array('batch' => serialize($batch)))
+        ->condition('bid', $batch['id'])
+        ->execute();
+    }
+    catch (\Exception $e) {
+      $this->catchException($e);
+    }
   }
 
   /**
    * {@inheritdoc}
    */
   public function cleanup() {
-    // Cleanup the batch table and the queue for failed batches.
-    $this->connection->delete('batch')
-      ->condition('timestamp', REQUEST_TIME - 864000, '<')
-      ->execute();
+    try {
+      // Cleanup the batch table and the queue for failed batches.
+      $this->connection->delete('batch')
+        ->condition('timestamp', REQUEST_TIME - 864000, '<')
+        ->execute();
+    }
+    catch (\Exception $e) {
+      $this->catchException($e);
+    }
   }
 
   /**
@@ -101,14 +129,99 @@ public function cleanup() {
   public function create(array $batch) {
     // Ensure that a session is started before using the CSRF token generator.
     $this->session->start();
-    $this->connection->insert('batch')
-      ->fields(array(
-        'bid' => $batch['id'],
-        'timestamp' => REQUEST_TIME,
-        'token' => $this->csrfToken->get($batch['id']),
-        'batch' => serialize($batch),
-      ))
-      ->execute();
+    try {
+      $this->connection->insert('batch')
+        ->fields(array(
+          'bid' => $batch['id'],
+          'timestamp' => REQUEST_TIME,
+          'token' => $this->csrfToken->get($batch['id']),
+          'batch' => serialize($batch),
+        ))
+        ->execute();
+    }
+    catch (\Exception $e) {
+      $this->catchException($e);
+    }
+  }
+
+  /**
+   * Check if the batch table exists and create it if not.
+   */
+  protected function ensureTableExists() {
+    try {
+      $database_schema = $this->connection->schema();
+      if (!$database_schema->tableExists(static::TABLE_NAME)) {
+        $schema_definition = $this->schemaDefinition();
+        $database_schema->createTable(static::TABLE_NAME, $schema_definition);
+        return TRUE;
+      }
+    }
+      // If another process has already created the batch table, attempting to
+      // recreate it will throw an exception. In this case just catch the
+      // exception and do nothing.
+    catch (SchemaObjectExistsException $e) {
+      return TRUE;
+    }
+    return FALSE;
+  }
+
+
+  /**
+   * Act on an exception when batch might be stale.
+   *
+   * If the table does not yet exist, that's fine, but if the table exists and
+   * yet the query failed, then the batch is stale and the exception needs
+   * to propagate.
+   *
+   * @param $e
+   *   The exception.
+   *
+   * @throws \Exception
+   */
+  protected function catchException(\Exception $e) {
+    if ($this->connection->schema()->tableExists(static::TABLE_NAME)) {
+      throw $e;
+    }
+  }
+
+  /**
+   * Defines the schema for the batch table.
+   */
+  public function schemaDefinition() {
+    return [
+      'description' => 'Stores details about batches (processes that run in multiple HTTP requests).',
+      'fields' => [
+        'bid' => [
+          'description' => 'Primary Key: Unique batch ID.',
+          // This is not a serial column, to allow both progressive and
+          // non-progressive batches. See batch_process().
+          'type' => 'int',
+          'unsigned' => TRUE,
+          'not null' => TRUE,
+        ],
+        'token' => [
+          'description' => "A string token generated against the current user's session id and the batch id, used to ensure that only the user who submitted the batch can effectively access it.",
+          'type' => 'varchar_ascii',
+          'length' => 64,
+          'not null' => TRUE,
+        ],
+        'timestamp' => [
+          'description' => 'A Unix timestamp indicating when this batch was submitted for processing. Stale batches are purged at cron time.',
+          'type' => 'int',
+          'not null' => TRUE,
+        ],
+        'batch' => [
+          'description' => 'A serialized array containing the processing data for the batch.',
+          'type' => 'blob',
+          'not null' => FALSE,
+          'size' => 'big',
+        ],
+      ],
+      'primary key' => ['bid'],
+      'indexes' => [
+        'token' => ['token'],
+      ],
+    ];
   }
 
 }
diff --git a/core/lib/Drupal/Core/Test/HttpClientMiddleware/TestHttpClientMiddleware.php b/core/lib/Drupal/Core/Test/HttpClientMiddleware/TestHttpClientMiddleware.php
index 1488189..0b2be3e 100644
--- a/core/lib/Drupal/Core/Test/HttpClientMiddleware/TestHttpClientMiddleware.php
+++ b/core/lib/Drupal/Core/Test/HttpClientMiddleware/TestHttpClientMiddleware.php
@@ -7,7 +7,9 @@
 
 namespace Drupal\Core\Test\HttpClientMiddleware;
 
+use Drupal\Core\Utility\Error;
 use Psr\Http\Message\RequestInterface;
+use Psr\Http\Message\ResponseInterface;
 
 /**
  * Overrides the User-Agent HTTP header for outbound HTTP requests.
@@ -31,7 +33,20 @@ public function __invoke() {
         if ($test_prefix = drupal_valid_test_ua()) {
           $request = $request->withHeader('User-Agent', drupal_generate_test_ua($test_prefix));
         }
-        return $handler($request, $options);
+        return $handler($request, $options)
+          ->then(function (ResponseInterface $response) use ($request, $options) {
+            $headers = $response->getHeaders();
+            foreach ($headers as $header_name => $header_values) {
+              if (preg_match('/^X-Drupal-Assertion-[0-9]+$/', $header_name, $matches)) {
+                foreach ($header_values as $header_value) {
+                  // Call \Drupal\simpletest\WebTestBase::error() with the parameters from
+                  // the header.
+                  $parameters = unserialize(urldecode($header_value));
+                  throw new \Exception($parameters[1] . ': ' . $parameters[0] . "\n" . Error::formatBacktrace([$parameters[2]]));
+                }
+              }
+            }
+          });
       };
     };
   }
diff --git a/core/modules/simpletest/src/BrowserTestBase.php b/core/modules/simpletest/src/BrowserTestBase.php
index d93ed8b..e6025bf 100644
--- a/core/modules/simpletest/src/BrowserTestBase.php
+++ b/core/modules/simpletest/src/BrowserTestBase.php
@@ -229,11 +229,17 @@
    */
   protected function initMink() {
     $driver = $this->getDefaultDriverInstance();
+
+    if ($driver instanceof GoutteDriver) {
+      $driver->getClient()->setClient(\Drupal::httpClient());
+    }
+
     $session = new Session($driver);
     $this->mink = new Mink();
     $this->mink->registerSession('default', $session);
     $this->mink->setDefaultSessionName('default');
     $this->registerSessions();
+
     return $session;
   }
 
diff --git a/core/modules/simpletest/tests/src/Functional/BrowserTestBaseTest.php b/core/modules/simpletest/tests/src/Functional/BrowserTestBaseTest.php
index c082d03..d660b1f 100644
--- a/core/modules/simpletest/tests/src/Functional/BrowserTestBaseTest.php
+++ b/core/modules/simpletest/tests/src/Functional/BrowserTestBaseTest.php
@@ -58,4 +58,9 @@ public function testForm() {
     $this->assertSame('green', $value);
   }
 
+  public function testError() {
+    $this->setExpectedException('\Exception', 'User notice: foo');
+    $this->drupalGet('test-error');
+  }
+
 }
diff --git a/core/modules/system/system.install b/core/modules/system/system.install
index afa5892..ac453c4 100644
--- a/core/modules/system/system.install
+++ b/core/modules/system/system.install
@@ -841,41 +841,6 @@ function system_install() {
  * Implements hook_schema().
  */
 function system_schema() {
-  $schema['batch'] = array(
-    'description' => 'Stores details about batches (processes that run in multiple HTTP requests).',
-    'fields' => array(
-      'bid' => array(
-        'description' => 'Primary Key: Unique batch ID.',
-        // This is not a serial column, to allow both progressive and
-        // non-progressive batches. See batch_process().
-        'type' => 'int',
-        'unsigned' => TRUE,
-        'not null' => TRUE,
-      ),
-      'token' => array(
-        'description' => "A string token generated against the current user's session id and the batch id, used to ensure that only the user who submitted the batch can effectively access it.",
-        'type' => 'varchar_ascii',
-        'length' => 64,
-        'not null' => TRUE,
-      ),
-      'timestamp' => array(
-        'description' => 'A Unix timestamp indicating when this batch was submitted for processing. Stale batches are purged at cron time.',
-        'type' => 'int',
-        'not null' => TRUE,
-      ),
-      'batch' => array(
-        'description' => 'A serialized array containing the processing data for the batch.',
-        'type' => 'blob',
-        'not null' => FALSE,
-        'size' => 'big',
-      ),
-    ),
-    'primary key' => array('bid'),
-    'indexes' => array(
-      'token' => array('token'),
-    ),
-  );
-
   $schema['flood'] = array(
     'description' => 'Flood controls the threshold of events, such as the number of contact attempts.',
     'fields' => array(
diff --git a/core/modules/system/tests/modules/test_page_test/src/Controller/Test.php b/core/modules/system/tests/modules/test_page_test/src/Controller/Test.php
index 7c817c2..64433c4 100644
--- a/core/modules/system/tests/modules/test_page_test/src/Controller/Test.php
+++ b/core/modules/system/tests/modules/test_page_test/src/Controller/Test.php
@@ -86,4 +86,11 @@ public function httpResponseException($code) {
     throw new HttpException($code);
   }
 
+  public function error() {
+    trigger_error('foo', E_USER_NOTICE);
+    return [
+      '#markup' => 'Content',
+    ];
+  }
+
 }
diff --git a/core/modules/system/tests/modules/test_page_test/test_page_test.routing.yml b/core/modules/system/tests/modules/test_page_test/test_page_test.routing.yml
index a00d084..a2d5ed5 100644
--- a/core/modules/system/tests/modules/test_page_test/test_page_test.routing.yml
+++ b/core/modules/system/tests/modules/test_page_test/test_page_test.routing.yml
@@ -50,3 +50,11 @@ test_page_test.http_response_exception:
     code: 200
   requirements:
     _access: 'TRUE'
+
+test_page_test.error:
+  path: '/test-error'
+  defaults:
+    _controller: '\Drupal\test_page_test\Controller\Test::error'
+    code: 200
+  requirements:
+    _access: 'TRUE'
